├── .github └── workflows │ └── pages.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── build.zig ├── build.zig.zon ├── docs ├── _config.yml ├── advanced-optimizations.md ├── api-reference.md ├── c-api-guide.md ├── core-concepts.md ├── examples.md ├── getting-started.md ├── index.md └── performance-guide.md ├── examples ├── fruits.c ├── fruits.zig └── tests.zig ├── include └── coyote.h └── src ├── c_api.zig └── coyote.zig /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: read 11 | pages: write 12 | id-token: write 13 | 14 | concurrency: 15 | group: "pages" 16 | cancel-in-progress: false 17 | 18 | jobs: 19 | build: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | - name: Setup Pages 25 | uses: actions/configure-pages@v4 26 | - name: Build with Jekyll 27 | uses: actions/jekyll-build-pages@v1 28 | - name: Upload artifact 29 | uses: actions/upload-pages-artifact@v3 30 | 31 | deploy: 32 | environment: 33 | name: github-pages 34 | url: ${{ steps.deployment.outputs.page_url }} 35 | runs-on: ubuntu-latest 36 | needs: build 37 | steps: 38 | - name: Deploy to GitHub Pages 39 | id: deployment 40 | uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | zig-out/ 2 | .zig-cache/ 3 | .history/ 4 | .vscode/ 5 | target/ 6 | Cargo.lock 7 | *.o 8 | *.so 9 | *.a 10 | perf.data 11 | 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linuxy/coyote-ecs/bf4da66647e016cbb524ea0f1a718a455ad51951/.gitmodules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ian Applegate 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # coyote-ecs 2 | A fast and simple zig native ECS. 3 | 4 | Builds against zig 0.14.0 5 | 6 | 📚 [Documentation](https://linuxy.github.io/coyote-ecs/docs/) 7 | 8 | ```git clone --recursive https://github.com/linuxy/coyote-ecs.git``` 9 | 10 | To build: 11 | * zig build 12 | 13 | A more complete example: 14 | [coyote-snake](https://github.com/linuxy/coyote-snake) 15 | 16 | Benchmark: 17 | [coyote-bunnies](https://github.com/linuxy/coyote-bunnies) 18 | 19 | Define your components 20 | ```zig 21 | const std = @import("std"); 22 | const ecs = @import("coyote-ecs"); 23 | 24 | const World = ecs.World; 25 | const Cast = ecs.Cast; 26 | const Systems = ecs.Systems; 27 | 28 | pub const Apple = struct { 29 | color: u32 = 0, 30 | ripe: bool = false, 31 | harvested: bool = false, 32 | }; 33 | 34 | pub const Orange = struct { 35 | color: u32 = 0, 36 | ripe: bool = false, 37 | harvested: bool = false, 38 | }; 39 | 40 | pub const Pear = struct { 41 | color: u32 = 0, 42 | ripe: bool = false, 43 | harvested: bool = false, 44 | }; 45 | ``` 46 | 47 | Create some entities and components in a world 48 | ```zig 49 | pub fn main() !void { 50 | //Create a world 51 | var world = try World.create(); 52 | defer world.deinit(); 53 | 54 | //Create an entity 55 | var anOrange = try world.entities.create(); 56 | var anApple = try world.entities.create(); 57 | var aPear = try world.entities.create(); 58 | 59 | std.log.info("Created an Orange ID: {}", .{anOrange.id}); 60 | 61 | //Create a unique component 62 | var orangeComponent = try world.components.create(Components.Orange); 63 | var appleComponent = try world.components.create(Components.Apple); 64 | 65 | //Attach and assign a component. Do not use an anonymous struct. 66 | try anOrange.attach(orangeComponent, Components.Orange{.color = 0, .ripe = false, .harvested = false}); 67 | try anApple.attach(appleComponent, Components.Apple{.color = 0, .ripe = false, .harvested = false}); 68 | _ = try aPear.addComponent(Components.Pear{.color = 1, .ripe = false, .harvested = false}); 69 | 70 | //Create 50k entities and attach 50k unique components 71 | var i: usize = 0; 72 | while(i < 50000) : (i += 1) { 73 | var anEntity = try world.entities.create(); 74 | var anOrangeComponent = try world.components.create(Components.Orange); 75 | try anEntity.attach(anOrangeComponent, Components.Orange{.color = 1, .ripe = false, .harvested = false}); 76 | } 77 | 78 | //Filter components by type 79 | var it = world.components.iteratorFilter(Components.Orange); 80 | i = 0; 81 | while(it.next()) |_| : (i += 1) { 82 | //... 83 | } 84 | 85 | std.log.info("Orange components: {}", .{i}); 86 | 87 | //Filter entities by type 88 | var it2 = world.entities.iteratorFilter(Components.Apple); 89 | i = 0; 90 | while(it2.next()) |_| : (i += 1) { 91 | //... 92 | } 93 | 94 | std.log.info("Apple entities: {}", .{i}); 95 | 96 | if(aPear.getOneComponent(Components.Pear) != null) 97 | std.log.info("Pear entities: >= 1", .{}) 98 | else 99 | std.log.info("Pear entities: 0", .{}); 100 | 101 | try Systems.run(Grow, .{world}); 102 | try Systems.run(Harvest, .{world}); 103 | try Systems.run(Raze, .{world}); 104 | 105 | std.log.info("Entities: {}", .{world.entities.count()}); 106 | std.log.info("Components: {}", .{world.components.count()}); 107 | } 108 | ``` 109 | 110 | Create some systems 111 | ```zig 112 | pub fn Grow(world: *World) void { 113 | var it = world.components.iterator(); 114 | var i: u32 = 0; 115 | while(it.next()) |component| : (i += 1) { 116 | if(component.is(Components.Orange)) { 117 | try component.set(Components.Orange, .{.ripe = true}); 118 | } 119 | 120 | if(component.is(Components.Apple)) { 121 | try component.set(Components.Apple, .{.ripe = true}); 122 | } 123 | 124 | if(component.is(Components.Pear)) { 125 | try component.set(Components.Pear, .{.ripe = true}); 126 | } 127 | //Fruits fall from the tree 128 | component.detach(); 129 | } 130 | std.log.info("Fruits grown: {}", .{i}); 131 | } 132 | 133 | pub fn Harvest(world: *World) void { 134 | var it = world.components.iterator(); 135 | var i: u32 = 0; 136 | while(it.next()) |component| { 137 | if(component.is(Components.Orange)) { 138 | if(Cast(Components.Orange, component).ripe == true) { 139 | try component.set(Components.Orange, .{.harvested = true}); 140 | i += 1; 141 | } 142 | } 143 | if(component.is(Components.Apple)) { 144 | if(Cast(Components.Apple, component).ripe == true) { 145 | try component.set(Components.Apple, .{.harvested = true}); 146 | i += 1; 147 | } 148 | } 149 | if(component.is(Components.Pear)) { 150 | if(Cast(Components.Pear, component).ripe == true) { 151 | try component.set(Components.Pear, .{.harvested = true}); 152 | i += 1; 153 | } 154 | } 155 | component.destroy(); 156 | } 157 | 158 | world.components.gc(); 159 | std.log.info("Fruits harvested: {}", .{i}); 160 | } 161 | 162 | pub fn Raze(world: *World) void { 163 | var it = world.entities.iterator(); 164 | var i: u32 = 0; 165 | 166 | while(it.next()) |entity| { 167 | entity.destroy(); 168 | i += 1; 169 | } 170 | 171 | std.log.info("Entities destroyed: {}", .{i}); 172 | } 173 | ``` 174 | 175 | With C bindings 176 | ```c 177 | #include 178 | #include 179 | #include "../include/coyote.h" 180 | 181 | typedef struct apple { 182 | int color; 183 | int ripe; 184 | int harvested; 185 | } apple; 186 | 187 | typedef struct orange { 188 | int color; 189 | int ripe; 190 | int harvested; 191 | } orange; 192 | 193 | typedef struct pear { 194 | int color; 195 | int ripe; 196 | int harvested; 197 | } pear; 198 | 199 | static const coyote_type t_apple = COYOTE_MAKE_TYPE(0, apple); 200 | static const coyote_type t_orange = COYOTE_MAKE_TYPE(1, orange); 201 | static const coyote_type t_pear = COYOTE_MAKE_TYPE(2, pear); 202 | 203 | int main(void) { 204 | world world = coyote_world_create(); 205 | 206 | if(world != 0) 207 | printf("Created world @%d\n", world); 208 | else 209 | printf("World creation failed.\n"); 210 | 211 | entity e_apple = coyote_entity_create(world); 212 | entity e_orange = coyote_entity_create(world); 213 | entity e_pear = coyote_entity_create(world); 214 | 215 | component c_apple = coyote_component_create(world, t_apple); 216 | component c_orange = coyote_component_create(world, t_orange); 217 | component c_pear = coyote_component_create(world, t_pear); 218 | 219 | printf("Created an apple component @%d\n", c_apple); 220 | printf("Created an orange component @%d\n", c_orange); 221 | printf("Created an pear component @%d\n", c_pear); 222 | 223 | iterator it = coyote_components_iterator_filter(world, t_orange); 224 | component next = coyote_components_iterator_filter_next(it); 225 | if(next) 226 | printf("Another orange component @%d\n", c_orange); 227 | else 228 | printf("NOT another orange component @%d\n", c_orange); 229 | 230 | if(coyote_component_is(c_orange, t_orange)) 231 | printf("Component is an orange @%d\n", c_orange); 232 | else 233 | printf("Component is NOT an orange @%d\n", c_orange); 234 | 235 | coyote_entity_attach(e_apple, c_apple, t_apple); 236 | 237 | //Assignment must happen after attach, TODO: Change? 238 | apple* a1 = coyote_component_get(c_apple); a1->color = 255; a1->ripe = 0; a1->harvested = 0; 239 | printf("Got and assigned an apple component @%d\n", a1); 240 | 241 | coyote_entity_detach(e_apple, c_apple); 242 | coyote_component_destroy(c_apple); 243 | coyote_entity_destroy(e_apple); 244 | coyote_entity_destroy(e_pear); 245 | 246 | printf("Number of entities: %d == 1\n", coyote_entities_count(world)); 247 | printf("Number of components: %d == 3\n", coyote_components_count(world)); 248 | 249 | coyote_world_destroy(world); 250 | printf("World destroyed.\n"); 251 | return 0; 252 | } 253 | ``` 254 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(b: *std.Build) void { 4 | const target = b.standardTargetOptions(.{}); 5 | const optimize = b.standardOptimizeOption(.{}); 6 | 7 | // Add a debug option for release builds 8 | const debug_info = b.option(bool, "debug-info", "Include debug information in release builds") orelse false; 9 | 10 | // Create a modified optimize option that includes debug info when requested 11 | const effective_optimize = if (debug_info and optimize == .ReleaseFast) 12 | .ReleaseFast 13 | else 14 | optimize; 15 | 16 | const exe = b.addExecutable(.{ 17 | .root_source_file = b.path("examples/fruits.zig"), 18 | .optimize = effective_optimize, 19 | .target = target, 20 | .name = "ecs", 21 | }); 22 | 23 | exe.root_module.addAnonymousImport("coyote-ecs", .{ 24 | .root_source_file = b.path("src/coyote.zig"), 25 | }); 26 | 27 | exe.linkLibC(); 28 | b.installArtifact(exe); 29 | 30 | const run_cmd = b.addRunArtifact(exe); 31 | run_cmd.step.dependOn(b.getInstallStep()); 32 | 33 | const run_step = b.step("run", "Run the app"); 34 | run_step.dependOn(&run_cmd.step); 35 | 36 | const main_tests = b.addExecutable(.{ 37 | .root_source_file = b.path("examples/tests.zig"), 38 | .optimize = effective_optimize, 39 | .target = target, 40 | .name = "tests", 41 | }); 42 | main_tests.linkLibC(); 43 | 44 | main_tests.root_module.addAnonymousImport("coyote-ecs", .{ 45 | .root_source_file = b.path("src/coyote.zig"), 46 | }); 47 | b.installArtifact(main_tests); 48 | 49 | const test_step = b.step("tests", "Run library tests"); 50 | test_step.dependOn(&main_tests.step); 51 | 52 | const test_install = b.option( 53 | bool, 54 | "install-tests", 55 | "Install the test binaries into zig-out", 56 | ) orelse false; 57 | 58 | // Static C lib 59 | const static_c_lib: ?*std.Build.Step.Compile = if (target.result.os.tag != .wasi) lib: { 60 | const static_lib = b.addStaticLibrary(.{ 61 | .name = "coyote", 62 | .root_source_file = b.path("src/c_api.zig"), 63 | .target = target, 64 | .optimize = effective_optimize, 65 | }); 66 | b.installArtifact(static_lib); 67 | static_lib.linkLibC(); 68 | b.default_step.dependOn(&static_lib.step); 69 | 70 | const static_binding_test = b.addExecutable(.{ 71 | .name = "static-binding-test", 72 | .target = target, 73 | .optimize = effective_optimize, 74 | }); 75 | static_binding_test.linkLibC(); 76 | static_binding_test.addIncludePath(b.path("include")); 77 | static_binding_test.addCSourceFile(.{ .file = b.path("examples/fruits.c"), .flags = &[_][]const u8{ "-Wall", "-Wextra", "-pedantic", "-std=c99", "-D_POSIX_C_SOURCE=199309L" } }); 78 | static_binding_test.linkLibrary(static_lib); 79 | if (test_install) b.installArtifact(static_binding_test); 80 | 81 | const static_binding_test_run = b.addRunArtifact(static_binding_test); 82 | test_step.dependOn(&static_binding_test_run.step); 83 | 84 | break :lib static_lib; 85 | } else null; 86 | 87 | _ = static_c_lib; 88 | 89 | // Dynamic C lib 90 | if (target.query.isNative()) { 91 | const dynamic_lib_name = "coyote"; 92 | 93 | const dynamic_lib = b.addSharedLibrary(.{ 94 | .name = dynamic_lib_name, 95 | .root_source_file = b.path("src/c_api.zig"), 96 | .target = target, 97 | .optimize = effective_optimize, 98 | }); 99 | dynamic_lib.linkLibC(); 100 | b.installArtifact(dynamic_lib); 101 | b.default_step.dependOn(&dynamic_lib.step); 102 | 103 | const dynamic_binding_test = b.addExecutable(.{ 104 | .name = "dynamic-binding-test", 105 | .target = target, 106 | .optimize = effective_optimize, 107 | }); 108 | dynamic_binding_test.linkLibC(); 109 | dynamic_binding_test.addIncludePath(b.path("include")); 110 | dynamic_binding_test.addCSourceFile(.{ .file = b.path("examples/fruits.c"), .flags = &[_][]const u8{ "-Wall", "-Wextra", "-pedantic", "-std=c99", "-D_POSIX_C_SOURCE=199309L" } }); 111 | dynamic_binding_test.linkLibrary(dynamic_lib); 112 | if (test_install) b.installArtifact(dynamic_binding_test); 113 | 114 | const dynamic_binding_test_run = b.addRunArtifact(dynamic_binding_test); 115 | test_step.dependOn(&dynamic_binding_test_run.step); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .name = .coyote_ecs, 3 | .minimum_zig_version = "0.14.0", 4 | .version = "0.1.0", 5 | .fingerprint = 0xfae96f0210655b4, 6 | .paths = .{""}, 7 | } 8 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | title: Coyote ECS Documentation 2 | description: Documentation for the Coyote ECS library 3 | theme: jekyll-theme-minimal 4 | baseurl: "/coyote-ecs" 5 | url: "https://linuxy.github.io" 6 | 7 | # Build settings 8 | markdown: kramdown 9 | permalink: pretty -------------------------------------------------------------------------------- /docs/advanced-optimizations.md: -------------------------------------------------------------------------------- 1 | # Advanced Optimizations 2 | 3 | This guide covers advanced optimization techniques for Coyote ECS, including SIMD operations, vectorization, and other performance enhancements. 4 | 5 | ## SIMD Optimizations 6 | 7 | SIMD (Single Instruction, Multiple Data) allows you to process multiple data points in parallel, which can significantly improve performance for certain operations. 8 | 9 | ### Vectorizing Component Data 10 | 11 | Components with multiple similar fields (like Position with x and y) can be vectorized using Zig's SIMD types: 12 | 13 | ```zig 14 | const std = @import("std"); 15 | const Vec2 = @Vector(2, f32); 16 | 17 | pub const Components = struct { 18 | pub const Position = struct { 19 | data: Vec2 = Vec2{ 0, 0 }, 20 | 21 | pub fn init(x: f32, y: f32) Position { 22 | return Position{ .data = Vec2{ x, y } }; 23 | } 24 | 25 | pub fn add(self: *Position, other: Position) void { 26 | self.data += other.data; 27 | } 28 | 29 | pub fn scale(self: *Position, factor: f32) void { 30 | self.data *= @splat(2, factor); 31 | } 32 | }; 33 | }; 34 | ``` 35 | 36 | ### SIMD Operations on Components 37 | 38 | When iterating over components, you can use SIMD operations to process multiple components at once: 39 | 40 | ```zig 41 | pub fn UpdatePositions(world: *World, delta: f32) void { 42 | var it = world.components.iteratorFilter(Components.Position); 43 | const delta_vec = @splat(2, delta); 44 | 45 | while(it.next()) |component| { 46 | var pos = component.get(Components.Position); 47 | pos.data += delta_vec; 48 | } 49 | } 50 | ``` 51 | 52 | ### Batch Processing with SIMD 53 | 54 | For systems that process multiple components of the same type, you can use SIMD to process them in batches: 55 | 56 | ```zig 57 | pub fn UpdateVelocities(world: *World, gravity: f32) void { 58 | var it = world.components.iteratorFilter(Components.Velocity); 59 | const gravity_vec = Vec2{ 0, gravity }; 60 | 61 | while(it.next()) |component| { 62 | var vel = component.get(Components.Velocity); 63 | vel.data += gravity_vec; 64 | } 65 | } 66 | ``` 67 | 68 | ## Parallel Processing with Chunked Iteration 69 | 70 | Coyote ECS provides a `iteratorFilterRange` function that allows you to process components in chunks, which is perfect for parallel processing: 71 | 72 | ### Using iteratorFilterRange 73 | 74 | The `iteratorFilterRange` function allows you to specify a range of components to iterate over: 75 | 76 | ```zig 77 | pub fn UpdatePositionsParallel(world: *World, delta_time: f32) void { 78 | const thread_count = std.Thread.getCpuCount(); 79 | const component_count = world.components.count(Components.Position); 80 | const chunk_size = (component_count + thread_count - 1) / thread_count; 81 | 82 | var threads: []std.Thread = undefined; 83 | threads = std.heap.page_allocator.alloc(std.Thread, thread_count) catch return; 84 | defer std.heap.page_allocator.free(threads); 85 | 86 | var i: usize = 0; 87 | while (i < thread_count) : (i += 1) { 88 | const start = i * chunk_size; 89 | const end = @min(start + chunk_size, component_count); 90 | 91 | threads[i] = std.Thread.spawn(.{}, struct { 92 | fn updateChunk(w: *World, start_idx: usize, end_idx: usize, dt: f32) void { 93 | var it = w.components.iteratorFilterRange(Components.Position, start_idx, end_idx); 94 | const dt_vec = @splat(2, dt); 95 | 96 | while (it.next()) |component| { 97 | var pos = component.get(Components.Position); 98 | pos.data += dt_vec; 99 | } 100 | } 101 | }.updateChunk, .{ world, start, end, delta_time }) catch continue; 102 | } 103 | 104 | // Wait for all threads to complete 105 | for (threads) |thread| { 106 | thread.join(); 107 | } 108 | } 109 | ``` 110 | 111 | ### Combining SIMD with Parallel Processing 112 | 113 | For maximum performance, you can combine SIMD operations with parallel processing: 114 | 115 | ```zig 116 | pub fn UpdatePhysicsParallel(world: *World, delta_time: f32) void { 117 | const thread_count = std.Thread.getCpuCount(); 118 | const component_count = world.components.count(Components.Position); 119 | const chunk_size = (component_count + thread_count - 1) / thread_count; 120 | 121 | var threads: []std.Thread = undefined; 122 | threads = std.heap.page_allocator.alloc(std.Thread, thread_count) catch return; 123 | defer std.heap.page_allocator.free(threads); 124 | 125 | var i: usize = 0; 126 | while (i < thread_count) : (i += 1) { 127 | const start = i * chunk_size; 128 | const end = @min(start + chunk_size, component_count); 129 | 130 | threads[i] = std.Thread.spawn(.{}, struct { 131 | fn updatePhysicsChunk(w: *World, start_idx: usize, end_idx: usize, dt: f32) void { 132 | // Process positions with SIMD 133 | var pos_it = w.components.iteratorFilterRange(Components.Position, start_idx, end_idx); 134 | const dt_vec = @splat(2, dt); 135 | 136 | while (pos_it.next()) |component| { 137 | var pos = component.get(Components.Position); 138 | pos.data += dt_vec; 139 | } 140 | 141 | // Process velocities with SIMD 142 | var vel_it = w.components.iteratorFilterRange(Components.Velocity, start_idx, end_idx); 143 | const gravity_vec = Vec2{ 0, 9.8 * dt }; 144 | 145 | while (vel_it.next()) |component| { 146 | var vel = component.get(Components.Velocity); 147 | vel.data += gravity_vec; 148 | } 149 | } 150 | }.updatePhysicsChunk, .{ world, start, end, delta_time }) catch continue; 151 | } 152 | 153 | // Wait for all threads to complete 154 | for (threads) |thread| { 155 | thread.join(); 156 | } 157 | } 158 | ``` 159 | 160 | ## Vectorized Entity and Component Storage 161 | 162 | ### SoA (Structure of Arrays) vs AoS (Array of Structures) 163 | 164 | Coyote ECS currently uses an Array of Structures (AoS) approach for component storage. For SIMD operations, a Structure of Arrays (SoA) approach can be more efficient: 165 | 166 | ```zig 167 | // Current AoS approach 168 | pub const Position = struct { 169 | x: f32 = 0, 170 | y: f32 = 0, 171 | }; 172 | 173 | // SoA approach for SIMD 174 | pub const PositionStorage = struct { 175 | xs: []f32, 176 | ys: []f32, 177 | 178 | pub fn init(allocator: std.mem.Allocator, capacity: usize) !PositionStorage { 179 | return PositionStorage{ 180 | .xs = try allocator.alloc(f32, capacity), 181 | .ys = try allocator.alloc(f32, capacity), 182 | }; 183 | } 184 | 185 | pub fn deinit(self: *PositionStorage, allocator: std.mem.Allocator) void { 186 | allocator.free(self.xs); 187 | allocator.free(self.ys); 188 | } 189 | 190 | pub fn updateAll(self: *PositionStorage, count: usize, delta_x: f32, delta_y: f32) void { 191 | const delta_x_vec = @splat(4, delta_x); 192 | const delta_y_vec = @splat(4, delta_y); 193 | 194 | var i: usize = 0; 195 | while (i + 4 <= count) : (i += 4) { 196 | const x_vec = std.mem.bytesAsSlice(f32, self.xs[i..i+4]); 197 | const y_vec = std.mem.bytesAsSlice(f32, self.ys[i..i+4]); 198 | 199 | x_vec.* += delta_x_vec; 200 | y_vec.* += delta_y_vec; 201 | } 202 | 203 | // Handle remaining elements 204 | while (i < count) : (i += 1) { 205 | self.xs[i] += delta_x; 206 | self.ys[i] += delta_y; 207 | } 208 | } 209 | }; 210 | ``` 211 | 212 | ### Implementing SoA in Coyote ECS 213 | 214 | To implement SoA in Coyote ECS, you would need to modify the component storage system: 215 | 216 | ```zig 217 | // Example of how SoA could be integrated into Coyote ECS 218 | pub const ComponentStorage = struct { 219 | // For each component type, store arrays of each field 220 | position_xs: []f32, 221 | position_ys: []f32, 222 | velocity_xs: []f32, 223 | velocity_ys: []f32, 224 | // ... other component fields 225 | 226 | pub fn updatePositions(self: *ComponentStorage, count: usize, delta_time: f32) void { 227 | const dt_vec = @splat(4, delta_time); 228 | 229 | var i: usize = 0; 230 | while (i + 4 <= count) : (i += 4) { 231 | const vel_x_vec = std.mem.bytesAsSlice(f32, self.velocity_xs[i..i+4]); 232 | const vel_y_vec = std.mem.bytesAsSlice(f32, self.velocity_ys[i..i+4]); 233 | const pos_x_vec = std.mem.bytesAsSlice(f32, self.position_xs[i..i+4]); 234 | const pos_y_vec = std.mem.bytesAsSlice(f32, self.position_ys[i..i+4]); 235 | 236 | pos_x_vec.* += vel_x_vec.* * dt_vec; 237 | pos_y_vec.* += vel_y_vec.* * dt_vec; 238 | } 239 | 240 | // Handle remaining elements 241 | while (i < count) : (i += 1) { 242 | self.position_xs[i] += self.velocity_xs[i] * delta_time; 243 | self.position_ys[i] += self.velocity_ys[i] * delta_time; 244 | } 245 | } 246 | }; 247 | ``` 248 | 249 | ## Memory Alignment for SIMD 250 | 251 | For optimal SIMD performance, ensure your component data is properly aligned: 252 | 253 | ```zig 254 | // Ensure 16-byte alignment for SIMD operations 255 | pub const AlignedPosition = struct { 256 | data: Vec2 align(16) = Vec2{ 0, 0 }, 257 | }; 258 | ``` 259 | 260 | ## Benchmarking SIMD vs Non-SIMD 261 | 262 | To measure the performance improvement from SIMD: 263 | 264 | ```zig 265 | pub fn benchmarkSimd() void { 266 | var world = World.create() catch return; 267 | defer world.deinit(); 268 | 269 | // Create test entities 270 | var i: usize = 0; 271 | while (i < 1000000) : (i += 1) { 272 | var entity = world.entities.create() catch continue; 273 | var position = world.components.create(Components.Position) catch continue; 274 | entity.attach(position, Components.Position{ .x = 0, .y = 0 }) catch continue; 275 | } 276 | 277 | // Benchmark non-SIMD 278 | const start1 = std.time.nanoTimestamp(); 279 | UpdatePositionsNonSimd(&world, 0.016); 280 | const end1 = std.time.nanoTimestamp(); 281 | const non_simd_time = @as(f64, @floatFromInt(end1 - start1)) / 1_000_000.0; 282 | 283 | // Benchmark SIMD 284 | const start2 = std.time.nanoTimestamp(); 285 | UpdatePositionsSimd(&world, 0.016); 286 | const end2 = std.time.nanoTimestamp(); 287 | const simd_time = @as(f64, @floatFromInt(end2 - start2)) / 1_000_000.0; 288 | 289 | std.debug.print("Non-SIMD: {d:.2}ms, SIMD: {d:.2}ms, Speedup: {d:.2}x\n", 290 | .{non_simd_time, simd_time, non_simd_time / simd_time}); 291 | } 292 | ``` 293 | 294 | ## Next Steps 295 | 296 | - Check out the [Performance Guide](performance-guide.md) for general optimization tips 297 | - Explore the [Examples](examples.md) for practical usage patterns 298 | - Read the [Core Concepts](core-concepts.md) for a deeper understanding of ECS -------------------------------------------------------------------------------- /docs/api-reference.md: -------------------------------------------------------------------------------- 1 | # Coyote ECS API Reference 2 | 3 | This document provides a comprehensive reference for the Coyote ECS API, including all major functions, structs, and types. 4 | 5 | ## Table of Contents 6 | 7 | - [World](#world) 8 | - [Entity](#entity) 9 | - [Component](#component) 10 | - [SuperComponents](#supercomponents) 11 | - [SuperEntities](#superentities) 12 | - [Systems](#systems) 13 | - [Iterators](#iterators) 14 | - [SIMD Optimizations](#simd-optimizations) 15 | - [C API](#c-api) 16 | - [Utility Functions](#utility-functions) 17 | 18 | ## World 19 | 20 | The `World` struct is the main container for all entities, components, and systems in the ECS. 21 | 22 | ### Functions 23 | 24 | #### `create() !*World` 25 | 26 | Creates a new ECS world. 27 | 28 | ```zig 29 | var world = try World.create(); 30 | defer world.destroy(); 31 | ``` 32 | 33 | #### `destroy(self: *World) void` 34 | 35 | Destroys the world and all its entities and components. 36 | 37 | ```zig 38 | world.destroy(); 39 | ``` 40 | 41 | ## Entity 42 | 43 | The `Entity` struct represents an entity in the ECS. 44 | 45 | ### Functions 46 | 47 | #### `create(ctx: *SuperEntities) !*Entity` 48 | 49 | Creates a new entity. 50 | 51 | ```zig 52 | const entity = try world.entities.create(); 53 | ``` 54 | 55 | #### `destroy(self: *Entity) void` 56 | 57 | Destroys the entity. 58 | 59 | ```zig 60 | entity.destroy(); 61 | ``` 62 | 63 | #### `addComponent(ctx: *Entity, comp_val: anytype) !*Component` 64 | 65 | Adds a component to the entity. 66 | 67 | ```zig 68 | const component = try entity.addComponent(MyComponent{ .value = 42 }); 69 | ``` 70 | 71 | #### `getOneComponent(ctx: *Entity, comptime comp_type: type) ?*const Component` 72 | 73 | Gets a component of the specified type from the entity. 74 | 75 | ```zig 76 | if (entity.getOneComponent(MyComponent)) |component| { 77 | // Use component 78 | } 79 | ``` 80 | 81 | #### `attach(self: *Entity, component: *Component, comp_type: anytype) !void` 82 | 83 | Attaches a component to the entity. 84 | 85 | ```zig 86 | try entity.attach(component, MyComponent{ .value = 42 }); 87 | ``` 88 | 89 | #### `detach(self: *Entity, component: *Component) !void` 90 | 91 | Detaches a component from the entity. 92 | 93 | ```zig 94 | try entity.detach(component); 95 | ``` 96 | 97 | #### `set(self: *Entity, component: *Component, comptime comp_type: type, members: anytype) !void` 98 | 99 | Sets the values of a component. 100 | 101 | ```zig 102 | try entity.set(component, MyComponent, .{ .value = 42 }); 103 | ``` 104 | 105 | ## Component 106 | 107 | The `Component` struct represents a component in the ECS. 108 | 109 | ### Functions 110 | 111 | #### `is(self: *const Component, comp_type: anytype) bool` 112 | 113 | Checks if the component is of the specified type. 114 | 115 | ```zig 116 | if (component.is(MyComponent)) { 117 | // Component is of type MyComponent 118 | } 119 | ``` 120 | 121 | #### `set(component: *Component, comptime comp_type: type, members: anytype) !void` 122 | 123 | Sets the values of the component. 124 | 125 | ```zig 126 | try component.set(MyComponent, .{ .value = 42 }); 127 | ``` 128 | 129 | #### `detach(self: *Component) void` 130 | 131 | Detaches the component from all entities. 132 | 133 | ```zig 134 | component.detach(); 135 | ``` 136 | 137 | #### `dealloc(self: *Component) void` 138 | 139 | Deallocates the component's data. 140 | 141 | ```zig 142 | component.dealloc(); 143 | ``` 144 | 145 | #### `destroy(self: *Component) void` 146 | 147 | Destroys the component. 148 | 149 | ```zig 150 | component.destroy(); 151 | ``` 152 | 153 | ## SuperComponents 154 | 155 | The `SuperComponents` struct manages all components in the ECS. 156 | 157 | ### Functions 158 | 159 | #### `count(ctx: *SuperComponents) u32` 160 | 161 | Returns the total number of components. 162 | 163 | ```zig 164 | const count = world.components.count(); 165 | ``` 166 | 167 | #### `create(ctx: *SuperComponents, comptime comp_type: type) !*Component` 168 | 169 | Creates a new component of the specified type. 170 | 171 | ```zig 172 | const component = try world.components.create(MyComponent); 173 | ``` 174 | 175 | #### `create_c(ctx: *SuperComponents, comp_type: c_type) !*Component` 176 | 177 | Creates a new component from a C type. 178 | 179 | ```zig 180 | const component = try world.components.create_c(my_c_type); 181 | ``` 182 | 183 | #### `expand(ctx: *SuperComponents) !void` 184 | 185 | Expands the component storage. 186 | 187 | ```zig 188 | try world.components.expand(); 189 | ``` 190 | 191 | #### `gc(ctx: *SuperComponents) void` 192 | 193 | Runs garbage collection on components. 194 | 195 | ```zig 196 | world.components.gc(); 197 | ``` 198 | 199 | #### `iterator(ctx: *SuperComponents) SuperComponents.Iterator` 200 | 201 | Returns an iterator over all components. 202 | 203 | ```zig 204 | var it = world.components.iterator(); 205 | while (it.next()) |component| { 206 | // Process component 207 | } 208 | ``` 209 | 210 | #### `iteratorFilter(ctx: *SuperComponents, comptime comp_type: type) SuperComponents.MaskedIterator` 211 | 212 | Returns an iterator over components of the specified type. 213 | 214 | ```zig 215 | var it = world.components.iteratorFilter(MyComponent); 216 | while (it.next()) |component| { 217 | // Process component 218 | } 219 | ``` 220 | 221 | #### `iteratorFilterRange(ctx: *SuperComponents, comptime comp_type: type, start_idx: usize, end_idx: usize) SuperComponents.MaskedRangeIterator` 222 | 223 | Returns an iterator over components of the specified type within a range. 224 | 225 | ```zig 226 | var it = world.components.iteratorFilterRange(MyComponent, 0, 100); 227 | while (it.next()) |component| { 228 | // Process component 229 | } 230 | ``` 231 | 232 | #### `iteratorFilterByEntity(ctx: *SuperComponents, entity: *Entity, comptime comp_type: type) SuperComponents.MaskedEntityIterator` 233 | 234 | Returns an iterator over components of the specified type attached to an entity. 235 | 236 | ```zig 237 | var it = world.components.iteratorFilterByEntity(entity, MyComponent); 238 | while (it.next()) |component| { 239 | // Process component 240 | } 241 | ``` 242 | 243 | ## SuperEntities 244 | 245 | The `SuperEntities` struct manages all entities in the ECS. 246 | 247 | ### Functions 248 | 249 | #### `count(ctx: *SuperEntities) u32` 250 | 251 | Returns the total number of entities. 252 | 253 | ```zig 254 | const count = world.entities.count(); 255 | ``` 256 | 257 | #### `create(ctx: *SuperEntities) !*Entity` 258 | 259 | Creates a new entity. 260 | 261 | ```zig 262 | const entity = try world.entities.create(); 263 | ``` 264 | 265 | #### `expand(ctx: *SuperEntities) !void` 266 | 267 | Expands the entity storage. 268 | 269 | ```zig 270 | try world.entities.expand(); 271 | ``` 272 | 273 | #### `iterator(ctx: *SuperEntities) SuperEntities.Iterator` 274 | 275 | Returns an iterator over all entities. 276 | 277 | ```zig 278 | var it = world.entities.iterator(); 279 | while (it.next()) |entity| { 280 | // Process entity 281 | } 282 | ``` 283 | 284 | #### `iteratorFilter(ctx: *SuperEntities, comptime comp_type: type) SuperEntities.MaskedIterator` 285 | 286 | Returns an iterator over entities with components of the specified type. 287 | 288 | ```zig 289 | var it = world.entities.iteratorFilter(MyComponent); 290 | while (it.next()) |entity| { 291 | // Process entity 292 | } 293 | ``` 294 | 295 | ## Systems 296 | 297 | The `Systems` struct provides functionality for running systems in the ECS. 298 | 299 | ### Functions 300 | 301 | #### `run(comptime f: anytype, args: anytype) !void` 302 | 303 | Runs a system function. 304 | 305 | ```zig 306 | try Systems.run(updateSystem, .{world}); 307 | ``` 308 | 309 | ## Iterators 310 | 311 | Coyote ECS provides several iterator types for iterating over entities and components. 312 | 313 | ### Iterator Types 314 | 315 | #### `SuperComponents.Iterator` 316 | 317 | Iterates over all components. 318 | 319 | #### `SuperComponents.MaskedIterator` 320 | 321 | Iterates over components of a specific type. 322 | 323 | #### `SuperComponents.MaskedRangeIterator` 324 | 325 | Iterates over components of a specific type within a range. 326 | 327 | #### `SuperComponents.MaskedEntityIterator` 328 | 329 | Iterates over components of a specific type attached to an entity. 330 | 331 | #### `SuperEntities.Iterator` 332 | 333 | Iterates over all entities. 334 | 335 | #### `SuperEntities.MaskedIterator` 336 | 337 | Iterates over entities with components of a specific type. 338 | 339 | ### Iterator Methods 340 | 341 | #### `next(it: *Iterator) ?*Component` 342 | 343 | Returns the next component in the iterator. 344 | 345 | ```zig 346 | while (it.next()) |component| { 347 | // Process component 348 | } 349 | ``` 350 | 351 | ## SIMD Optimizations 352 | 353 | Coyote ECS provides SIMD (Single Instruction, Multiple Data) optimizations for efficient processing of components. 354 | 355 | ### Functions 356 | 357 | #### `processComponentsSimd(ctx: *_Components, comptime comp_type: type, processor: fn (*comp_type) void) void` 358 | 359 | Processes components of the specified type using SIMD operations. 360 | 361 | ```zig 362 | world._components[0].processComponentsSimd(MyComponent, |component| { 363 | // Process component 364 | }); 365 | ``` 366 | 367 | #### `processComponentsRangeSimd(ctx: *_Components, comptime comp_type: type, start_idx: usize, end_idx: usize, processor: fn (*comp_type) void) void` 368 | 369 | Processes components of the specified type within a range using SIMD operations. 370 | 371 | ```zig 372 | world._components[0].processComponentsRangeSimd(MyComponent, 0, 100, |component| { 373 | // Process component 374 | }); 375 | ``` 376 | 377 | ### Iterator Types with SIMD Support 378 | 379 | #### `SuperComponents.MaskedIterator` 380 | 381 | Iterates over components of a specific type using SIMD operations for mask checks. 382 | 383 | ```zig 384 | var it = world.components.iteratorFilter(MyComponent); 385 | while (it.next()) |component| { 386 | // Process component 387 | } 388 | ``` 389 | 390 | #### `SuperComponents.MaskedRangeIterator` 391 | 392 | Iterates over components of a specific type within a range using SIMD operations for mask checks. 393 | 394 | ```zig 395 | var it = world.components.iteratorFilterRange(MyComponent, 0, 100); 396 | while (it.next()) |component| { 397 | // Process component 398 | } 399 | ``` 400 | 401 | #### `SuperComponents.MaskedEntityIterator` 402 | 403 | Iterates over components of a specific type attached to an entity using SIMD operations for mask checks. 404 | 405 | ```zig 406 | var it = world.components.iteratorFilterByEntity(entity, MyComponent); 407 | while (it.next()) |component| { 408 | // Process component 409 | } 410 | ``` 411 | 412 | For more details on SIMD optimizations, see the [Advanced Optimizations](advanced-optimizations.md) guide. 413 | 414 | ## C API 415 | 416 | Coyote ECS provides a C API for cross-language compatibility. 417 | 418 | ### Types 419 | 420 | #### `coyote_world` 421 | 422 | A handle to a Coyote ECS world. 423 | 424 | ```c 425 | coyote_world world = coyote_world_create(); 426 | ``` 427 | 428 | #### `coyote_entity` 429 | 430 | A handle to a Coyote ECS entity. 431 | 432 | ```c 433 | coyote_entity entity = coyote_entities_create(world); 434 | ``` 435 | 436 | #### `coyote_component` 437 | 438 | A handle to a Coyote ECS component. 439 | 440 | ```c 441 | coyote_component component = coyote_components_create(world, type); 442 | ``` 443 | 444 | #### `coyote_type` 445 | 446 | A C type definition for components. 447 | 448 | ```c 449 | coyote_type type = { 450 | .id = 1, 451 | .size = sizeof(MyComponent), 452 | .alignof = alignof(MyComponent), 453 | .name = "MyComponent" 454 | }; 455 | ``` 456 | 457 | #### `coyote_iterator` 458 | 459 | A handle to a Coyote ECS iterator. 460 | 461 | ```c 462 | coyote_iterator iterator = coyote_components_iterator(world); 463 | ``` 464 | 465 | ### Functions 466 | 467 | #### World Management 468 | 469 | ##### `coyote_world_create()` 470 | 471 | Creates a new Coyote ECS world. 472 | 473 | ```c 474 | coyote_world world = coyote_world_create(); 475 | ``` 476 | 477 | ##### `coyote_world_destroy(world)` 478 | 479 | Destroys a Coyote ECS world. 480 | 481 | ```c 482 | coyote_world_destroy(world); 483 | ``` 484 | 485 | #### Entity Management 486 | 487 | ##### `coyote_entities_create(world)` 488 | 489 | Creates a new entity. 490 | 491 | ```c 492 | coyote_entity entity = coyote_entities_create(world); 493 | ``` 494 | 495 | ##### `coyote_entities_destroy(entity)` 496 | 497 | Destroys an entity. 498 | 499 | ```c 500 | coyote_entities_destroy(entity); 501 | ``` 502 | 503 | ##### `coyote_entities_count(world)` 504 | 505 | Returns the number of entities. 506 | 507 | ```c 508 | size_t count = coyote_entities_count(world); 509 | ``` 510 | 511 | #### Component Management 512 | 513 | ##### `coyote_components_create(world, type)` 514 | 515 | Creates a new component. 516 | 517 | ```c 518 | coyote_component component = coyote_components_create(world, type); 519 | ``` 520 | 521 | ##### `coyote_components_destroy(component)` 522 | 523 | Destroys a component. 524 | 525 | ```c 526 | coyote_components_destroy(component); 527 | ``` 528 | 529 | ##### `coyote_components_count(world)` 530 | 531 | Returns the number of components. 532 | 533 | ```c 534 | size_t count = coyote_components_count(world); 535 | ``` 536 | 537 | ##### `coyote_components_gc(world)` 538 | 539 | Runs garbage collection on components. 540 | 541 | ```c 542 | coyote_components_gc(world); 543 | ``` 544 | 545 | #### Component Attachment 546 | 547 | ##### `coyote_entity_attach_component(entity, component, data, size)` 548 | 549 | Attaches a component to an entity. 550 | 551 | ```c 552 | coyote_entity_attach_component(entity, component, &my_component_data, sizeof(MyComponent)); 553 | ``` 554 | 555 | ##### `coyote_entity_detach_component(entity, component)` 556 | 557 | Detaches a component from an entity. 558 | 559 | ```c 560 | coyote_entity_detach_component(entity, component); 561 | ``` 562 | 563 | #### Iterators 564 | 565 | ##### `coyote_components_iterator(world)` 566 | 567 | Returns an iterator over all components. 568 | 569 | ```c 570 | coyote_iterator iterator = coyote_components_iterator(world); 571 | ``` 572 | 573 | ##### `coyote_components_iterator_next(iterator)` 574 | 575 | Returns the next component in the iterator. 576 | 577 | ```c 578 | coyote_component component = coyote_components_iterator_next(iterator); 579 | ``` 580 | 581 | ##### `coyote_components_iterator_filter(world, type)` 582 | 583 | Returns an iterator over components of the specified type. 584 | 585 | ```c 586 | coyote_iterator iterator = coyote_components_iterator_filter(world, type); 587 | ``` 588 | 589 | ##### `coyote_components_iterator_filter_next(iterator)` 590 | 591 | Returns the next component in the filtered iterator. 592 | 593 | ```c 594 | coyote_component component = coyote_components_iterator_filter_next(iterator); 595 | ``` 596 | 597 | ##### `coyote_components_iterator_filter_range(world, type, start_idx, end_idx)` 598 | 599 | Returns an iterator over components of the specified type within a range. 600 | 601 | ```c 602 | coyote_iterator iterator = coyote_components_iterator_filter_range(world, type, 0, 100); 603 | ``` 604 | 605 | ##### `coyote_components_iterator_filter_range_next(iterator)` 606 | 607 | Returns the next component in the range iterator. 608 | 609 | ```c 610 | coyote_component component = coyote_components_iterator_filter_range_next(iterator); 611 | ``` 612 | 613 | ##### `coyote_entities_iterator(world)` 614 | 615 | Returns an iterator over all entities. 616 | 617 | ```c 618 | coyote_iterator iterator = coyote_entities_iterator(world); 619 | ``` 620 | 621 | ##### `coyote_entities_iterator_next(iterator)` 622 | 623 | Returns the next entity in the iterator. 624 | 625 | ```c 626 | coyote_entity entity = coyote_entities_iterator_next(iterator); 627 | ``` 628 | 629 | ##### `coyote_entities_iterator_filter(world, type)` 630 | 631 | Returns an iterator over entities with components of the specified type. 632 | 633 | ```c 634 | coyote_iterator iterator = coyote_entities_iterator_filter(world, type); 635 | ``` 636 | 637 | ##### `coyote_entities_iterator_filter_next(iterator)` 638 | 639 | Returns the next entity in the filtered iterator. 640 | 641 | ```c 642 | coyote_entity entity = coyote_entities_iterator_filter_next(iterator); 643 | ``` 644 | 645 | For more details on the C API, see the [C API Guide](c-api-guide.md). 646 | 647 | ## Utility Functions 648 | 649 | ### `Cast(comptime T: type, component: ?*Component) *T` 650 | 651 | Casts a component to a specific type. 652 | 653 | ```zig 654 | const typed_component = Cast(MyComponent, component); 655 | ``` 656 | 657 | ### `CastData(comptime T: type, component: ?*anyopaque) *T` 658 | 659 | Casts component data to a specific type. 660 | 661 | ```zig 662 | const typed_data = CastData(MyComponent, component.data); 663 | ``` 664 | 665 | ### `typeToId(comptime T: type) u32` 666 | 667 | Converts a type to an ID. 668 | 669 | ```zig 670 | const id = typeToId(MyComponent); 671 | ``` 672 | 673 | ### `typeToIdC(comp_type: c_type) u32` 674 | 675 | Converts a C type to an ID. 676 | 677 | ```zig 678 | const id = typeToIdC(my_c_type); 679 | ``` 680 | 681 | ### `opaqueDestroy(self: std.mem.Allocator, ptr: anytype, sz: usize, alignment: u8) void` 682 | 683 | Destroys an opaque pointer. 684 | 685 | ```zig 686 | opaqueDestroy(allocator, ptr, size, alignment); 687 | ``` -------------------------------------------------------------------------------- /docs/c-api-guide.md: -------------------------------------------------------------------------------- 1 | # C API Guide 2 | 3 | This guide explains how to use Coyote ECS from C code. Coyote ECS provides a C API that allows you to use the ECS functionality in C projects. 4 | 5 | ## Getting Started 6 | 7 | ### Including the Header 8 | 9 | ```c 10 | #include 11 | ``` 12 | 13 | ### Basic Types 14 | 15 | The C API provides the following basic types: 16 | 17 | ```c 18 | typedef uintptr_t world; 19 | typedef uintptr_t entity; 20 | typedef uintptr_t component; 21 | typedef uintptr_t iterator; 22 | typedef uintptr_t coyote_type; 23 | ``` 24 | 25 | ## Creating a World 26 | 27 | ```c 28 | world world = coyote_world_create(); 29 | if (world == 0) { 30 | // Handle error 31 | } 32 | ``` 33 | 34 | ## Defining Components 35 | 36 | Components in C are defined as structs: 37 | 38 | ```c 39 | typedef struct position { 40 | float x; 41 | float y; 42 | } position; 43 | 44 | typedef struct velocity { 45 | float x; 46 | float y; 47 | } velocity; 48 | ``` 49 | 50 | ## Creating Component Types 51 | 52 | Use the `COYOTE_MAKE_TYPE` macro to create component types: 53 | 54 | ```c 55 | static const coyote_type t_position = COYOTE_MAKE_TYPE(0, position); 56 | static const coyote_type t_velocity = COYOTE_MAKE_TYPE(1, velocity); 57 | ``` 58 | 59 | ## Creating Entities and Components 60 | 61 | ```c 62 | // Create an entity 63 | entity e = coyote_entity_create(world); 64 | 65 | // Create components 66 | component c_pos = coyote_component_create(world, t_position); 67 | component c_vel = coyote_component_create(world, t_velocity); 68 | 69 | // Attach components to entity 70 | coyote_entity_attach(e, c_pos, t_position); 71 | coyote_entity_attach(e, c_vel, t_velocity); 72 | 73 | // Set component data 74 | position* pos = coyote_component_get(c_pos); 75 | pos->x = 0.0f; 76 | pos->y = 0.0f; 77 | 78 | velocity* vel = coyote_component_get(c_vel); 79 | vel->x = 1.0f; 80 | vel->y = 1.0f; 81 | ``` 82 | 83 | ## Iterating Over Components 84 | 85 | ```c 86 | // Iterate over all components of a specific type 87 | iterator it = coyote_components_iterator_filter(world, t_position); 88 | component next; 89 | while ((next = coyote_components_iterator_filter_next(it)) != 0) { 90 | position* pos = coyote_component_get(next); 91 | // Work with the position component 92 | } 93 | ``` 94 | 95 | ## Iterating Over Components in Chunks 96 | 97 | For parallel processing, you can iterate over components in chunks: 98 | 99 | ```c 100 | // Get the total number of components 101 | int total_components = coyote_components_count(world); 102 | int thread_count = 4; // Or use a thread pool 103 | int chunk_size = (total_components + thread_count - 1) / thread_count; 104 | 105 | // Process components in parallel 106 | for (int i = 0; i < thread_count; i++) { 107 | size_t start_idx = i * chunk_size; 108 | size_t end_idx = (i + 1) * chunk_size; 109 | if (end_idx > total_components) { 110 | end_idx = total_components; 111 | } 112 | 113 | // Create an iterator for this chunk 114 | iterator chunk_it = coyote_components_iterator_filter_range(world, t_position, start_idx, end_idx); 115 | 116 | // Process the chunk 117 | component next; 118 | while ((next = coyote_components_iterator_filter_range_next(chunk_it)) != 0) { 119 | position* pos = coyote_component_get(next); 120 | // Work with the position component in this chunk 121 | } 122 | } 123 | ``` 124 | 125 | ## Iterating Over Entities 126 | 127 | ```c 128 | // Iterate over entities with specific components 129 | iterator it = coyote_entities_iterator_filter(world, t_position); 130 | entity next; 131 | while ((next = coyote_entities_iterator_filter_next(it)) != 0) { 132 | // Work with the entity 133 | } 134 | ``` 135 | 136 | ## Component Management 137 | 138 | ### Checking Component Type 139 | 140 | ```c 141 | if (coyote_component_is(component, t_position)) { 142 | // This is a position component 143 | } 144 | ``` 145 | 146 | ### Destroying Components 147 | 148 | ```c 149 | coyote_component_destroy(component); 150 | ``` 151 | 152 | ## Memory Management 153 | 154 | ### Garbage Collection 155 | 156 | Call the garbage collector when appropriate: 157 | 158 | ```c 159 | coyote_components_gc(world); 160 | ``` 161 | 162 | ### Counting Entities and Components 163 | 164 | ```c 165 | int entity_count = coyote_entities_count(world); 166 | int component_count = coyote_components_count(world); 167 | ``` 168 | 169 | ## Advanced Usage 170 | 171 | ### Parallel Processing 172 | 173 | For high-performance applications, you can use the chunked iterator to process components in parallel: 174 | 175 | ```c 176 | #include 177 | 178 | #define NUM_THREADS 4 179 | 180 | typedef struct { 181 | world world; 182 | coyote_type component_type; 183 | size_t start_idx; 184 | size_t end_idx; 185 | } thread_data; 186 | 187 | void* process_chunk(void* arg) { 188 | thread_data* data = (thread_data*)arg; 189 | 190 | iterator it = coyote_components_iterator_filter_range( 191 | data->world, 192 | data->component_type, 193 | data->start_idx, 194 | data->end_idx 195 | ); 196 | 197 | component next; 198 | while ((next = coyote_components_iterator_filter_range_next(it)) != 0) { 199 | // Process the component 200 | } 201 | 202 | return NULL; 203 | } 204 | 205 | void process_components_parallel(world world, coyote_type component_type) { 206 | int total_components = coyote_components_count(world); 207 | int chunk_size = (total_components + NUM_THREADS - 1) / NUM_THREADS; 208 | 209 | pthread_t threads[NUM_THREADS]; 210 | thread_data thread_args[NUM_THREADS]; 211 | 212 | // Create threads 213 | for (int i = 0; i < NUM_THREADS; i++) { 214 | size_t start_idx = i * chunk_size; 215 | size_t end_idx = (i + 1) * chunk_size; 216 | if (end_idx > total_components) { 217 | end_idx = total_components; 218 | } 219 | 220 | thread_args[i].world = world; 221 | thread_args[i].component_type = component_type; 222 | thread_args[i].start_idx = start_idx; 223 | thread_args[i].end_idx = end_idx; 224 | 225 | pthread_create(&threads[i], NULL, process_chunk, &thread_args[i]); 226 | } 227 | 228 | // Wait for all threads to complete 229 | for (int i = 0; i < NUM_THREADS; i++) { 230 | pthread_join(threads[i], NULL); 231 | } 232 | } 233 | ``` 234 | 235 | ## Next Steps 236 | 237 | - Check out the [Performance Guide](performance-guide.md) for optimization tips 238 | - Explore the [Advanced Optimizations Guide](advanced-optimizations.md) for SIMD and parallel processing 239 | - Read the [Core Concepts](core-concepts.md) for a deeper understanding of ECS -------------------------------------------------------------------------------- /docs/core-concepts.md: -------------------------------------------------------------------------------- 1 | # Core Concepts 2 | 3 | This guide explains the fundamental concepts of Entity Component Systems (ECS) and how they are implemented in Coyote ECS. 4 | 5 | ## What is an ECS? 6 | 7 | An Entity Component System (ECS) is a software architectural pattern that is commonly used in game development and simulation software. It is based on the principle of composition over inheritance and focuses on data-oriented design. 8 | 9 | ## Key Concepts 10 | 11 | ### Entities 12 | 13 | Entities are the basic units in an ECS. They are essentially just IDs that can have components attached to them. In Coyote ECS, entities are created through the world: 14 | 15 | ```zig 16 | var entity = try world.entities.create(); 17 | ``` 18 | 19 | ### Components 20 | 21 | Components are pure data structures that define the properties and state of entities. They contain no logic or behavior. In Coyote ECS, components are defined as Zig structs: 22 | 23 | ```zig 24 | pub const Components = struct { 25 | pub const Position = struct { 26 | x: f32 = 0, 27 | y: f32 = 0, 28 | }; 29 | }; 30 | ``` 31 | 32 | ### Systems 33 | 34 | Systems contain the logic and behavior of your application. They operate on entities that have specific components. In Coyote ECS, systems are just functions that take a world as a parameter: 35 | 36 | ```zig 37 | pub fn UpdatePosition(world: *World) void { 38 | var it = world.entities.iteratorFilter(Components.Position); 39 | while(it.next()) |entity| { 40 | // Update position logic here 41 | } 42 | } 43 | ``` 44 | 45 | ### World 46 | 47 | The world is the container that manages all entities, components, and systems. It provides the interface for creating and managing the ECS structure: 48 | 49 | ```zig 50 | var world = try World.create(); 51 | defer world.deinit(); 52 | ``` 53 | 54 | ## Component Management 55 | 56 | ### Creating Components 57 | 58 | Components are created through the world's component manager: 59 | 60 | ```zig 61 | var component = try world.components.create(Components.Position); 62 | ``` 63 | 64 | ### Attaching Components 65 | 66 | Components are attached to entities using the `attach` method: 67 | 68 | ```zig 69 | try entity.attach(component, Components.Position{ .x = 0, .y = 0 }); 70 | ``` 71 | 72 | ### Detaching Components 73 | 74 | Components can be detached from entities: 75 | 76 | ```zig 77 | entity.detach(component); 78 | ``` 79 | 80 | ### Destroying Components 81 | 82 | When components are no longer needed, they should be destroyed: 83 | 84 | ```zig 85 | component.destroy(); 86 | ``` 87 | 88 | ## Entity Management 89 | 90 | ### Creating Entities 91 | 92 | Entities are created through the world: 93 | 94 | ```zig 95 | var entity = try world.entities.create(); 96 | ``` 97 | 98 | ### Destroying Entities 99 | 100 | Entities can be destroyed when they are no longer needed: 101 | 102 | ```zig 103 | entity.destroy(); 104 | ``` 105 | 106 | ## Querying and Iteration 107 | 108 | ### Component Iteration 109 | 110 | Iterate over all components of a specific type: 111 | 112 | ```zig 113 | var it = world.components.iteratorFilter(Components.Position); 114 | while(it.next()) |component| { 115 | // Work with the component 116 | } 117 | ``` 118 | 119 | ### Entity Iteration 120 | 121 | Iterate over entities with specific components: 122 | 123 | ```zig 124 | var it = world.entities.iteratorFilter(Components.Position); 125 | while(it.next()) |entity| { 126 | // Work with the entity 127 | } 128 | ``` 129 | 130 | ### Component Access 131 | 132 | Access components attached to an entity: 133 | 134 | ```zig 135 | if(entity.getOneComponent(Components.Position)) |position| { 136 | // Work with the position component 137 | } 138 | ``` 139 | 140 | ## System Execution 141 | 142 | Systems are executed using the `Systems.run` function: 143 | 144 | ```zig 145 | try Systems.run(UpdatePosition, .{world}); 146 | ``` 147 | 148 | ## Memory Management 149 | 150 | Coyote ECS handles memory management automatically, but you should: 151 | 152 | 1. Always call `defer world.deinit()` after creating a world 153 | 2. Destroy components when they are no longer needed 154 | 3. Destroy entities when they are no longer needed 155 | 4. Use the garbage collector when appropriate: 156 | 157 | ```zig 158 | world.components.gc(); 159 | ``` 160 | 161 | ## Best Practices 162 | 163 | 1. Keep components small and focused 164 | 2. Use systems to implement behavior 165 | 3. Avoid storing references to components or entities 166 | 4. Use the iterator pattern for querying 167 | 5. Clean up resources properly 168 | 6. Use appropriate component types for your data 169 | 7. Consider performance implications of component access patterns 170 | 171 | ## Performance Considerations 172 | 173 | 1. Components are stored in contiguous memory for better cache utilization 174 | 2. Iteration is optimized for common access patterns 175 | 3. Component creation and destruction is designed to be efficient 176 | 4. The garbage collector helps manage memory without fragmentation 177 | 178 | ## Next Steps 179 | 180 | - Check out the [Examples](examples.md) for practical usage patterns 181 | - Read the [API Reference](api-reference.md) for detailed documentation 182 | - Learn about [Performance Optimization](performance-guide.md) -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | This page contains practical examples of using Coyote ECS in different scenarios. 4 | 5 | ## Basic Example: Fruit Garden 6 | 7 | This example demonstrates basic entity and component management using a fruit garden simulation. 8 | 9 | ### Components Definition 10 | 11 | ```zig 12 | const std = @import("std"); 13 | const ecs = @import("coyote-ecs"); 14 | 15 | pub const Components = struct { 16 | pub const Apple = struct { 17 | color: u32 = 0, 18 | ripe: bool = false, 19 | harvested: bool = false, 20 | }; 21 | 22 | pub const Orange = struct { 23 | color: u32 = 0, 24 | ripe: bool = false, 25 | harvested: bool = false, 26 | }; 27 | 28 | pub const Pear = struct { 29 | color: u32 = 0, 30 | ripe: bool = false, 31 | harvested: bool = false, 32 | }; 33 | }; 34 | ``` 35 | 36 | ### Main Program 37 | 38 | ```zig 39 | pub fn main() !void { 40 | var world = try World.create(); 41 | defer world.deinit(); 42 | 43 | // Create entities 44 | var anOrange = try world.entities.create(); 45 | var anApple = try world.entities.create(); 46 | var aPear = try world.entities.create(); 47 | 48 | // Create and attach components 49 | var orangeComponent = try world.components.create(Components.Orange); 50 | var appleComponent = try world.components.create(Components.Apple); 51 | 52 | try anOrange.attach(orangeComponent, Components.Orange{ 53 | .color = 0, 54 | .ripe = false, 55 | .harvested = false, 56 | }); 57 | try anApple.attach(appleComponent, Components.Apple{ 58 | .color = 0, 59 | .ripe = false, 60 | .harvested = false, 61 | }); 62 | _ = try aPear.addComponent(Components.Pear{ 63 | .color = 1, 64 | .ripe = false, 65 | .harvested = false, 66 | }); 67 | 68 | // Run systems 69 | try Systems.run(Grow, .{world}); 70 | try Systems.run(Harvest, .{world}); 71 | try Systems.run(Raze, .{world}); 72 | } 73 | ``` 74 | 75 | ### Systems Implementation 76 | 77 | ```zig 78 | pub fn Grow(world: *World) void { 79 | var it = world.components.iterator(); 80 | while(it.next()) |component| { 81 | if(component.is(Components.Orange)) { 82 | try component.set(Components.Orange, .{.ripe = true}); 83 | } 84 | if(component.is(Components.Apple)) { 85 | try component.set(Components.Apple, .{.ripe = true}); 86 | } 87 | if(component.is(Components.Pear)) { 88 | try component.set(Components.Pear, .{.ripe = true}); 89 | } 90 | component.detach(); 91 | } 92 | } 93 | 94 | pub fn Harvest(world: *World) void { 95 | var it = world.components.iterator(); 96 | while(it.next()) |component| { 97 | if(component.is(Components.Orange)) { 98 | if(Cast(Components.Orange, component).ripe) { 99 | try component.set(Components.Orange, .{.harvested = true}); 100 | } 101 | } 102 | // Similar for Apple and Pear 103 | component.destroy(); 104 | } 105 | world.components.gc(); 106 | } 107 | 108 | pub fn Raze(world: *World) void { 109 | var it = world.entities.iterator(); 110 | while(it.next()) |entity| { 111 | entity.destroy(); 112 | } 113 | } 114 | ``` 115 | 116 | ## Game Development Example: 2D Physics 117 | 118 | This example shows how to implement a simple 2D physics system. 119 | 120 | ### Components 121 | 122 | ```zig 123 | pub const Components = struct { 124 | pub const Position = struct { 125 | x: f32 = 0, 126 | y: f32 = 0, 127 | }; 128 | 129 | pub const Velocity = struct { 130 | x: f32 = 0, 131 | y: f32 = 0, 132 | }; 133 | 134 | pub const Acceleration = struct { 135 | x: f32 = 0, 136 | y: f32 = 0, 137 | }; 138 | 139 | pub const Mass = struct { 140 | value: f32 = 1.0, 141 | }; 142 | }; 143 | ``` 144 | 145 | ### Physics Systems 146 | 147 | ```zig 148 | pub fn UpdatePhysics(world: *World, delta_time: f32) void { 149 | var it = world.entities.iteratorFilter(Components.Position); 150 | while(it.next()) |entity| { 151 | if(entity.getOneComponent(Components.Velocity)) |velocity| { 152 | var pos = entity.getOneComponent(Components.Position).?; 153 | pos.x += velocity.x * delta_time; 154 | pos.y += velocity.y * delta_time; 155 | } 156 | } 157 | } 158 | 159 | pub fn ApplyGravity(world: *World, gravity: f32) void { 160 | var it = world.entities.iteratorFilter(Components.Mass); 161 | while(it.next()) |entity| { 162 | if(entity.getOneComponent(Components.Velocity)) |velocity| { 163 | velocity.y += gravity; 164 | } 165 | } 166 | } 167 | ``` 168 | 169 | ## Performance Example: Particle System 170 | 171 | This example demonstrates how to handle large numbers of entities efficiently. 172 | 173 | ```zig 174 | pub fn CreateParticleSystem(world: *World, count: usize) !void { 175 | var i: usize = 0; 176 | while(i < count) : (i += 1) { 177 | var entity = try world.entities.create(); 178 | var position = try world.components.create(Components.Position); 179 | var velocity = try world.components.create(Components.Velocity); 180 | 181 | try entity.attach(position, Components.Position{ 182 | .x = @floatFromInt(i % 100), 183 | .y = @floatFromInt(i / 100), 184 | }); 185 | try entity.attach(velocity, Components.Velocity{ 186 | .x = 0, 187 | .y = 0, 188 | }); 189 | } 190 | } 191 | 192 | pub fn UpdateParticles(world: *World) void { 193 | var it = world.entities.iteratorFilter(Components.Position); 194 | while(it.next()) |entity| { 195 | if(entity.getOneComponent(Components.Velocity)) |velocity| { 196 | var pos = entity.getOneComponent(Components.Position).?; 197 | pos.x += velocity.x; 198 | pos.y += velocity.y; 199 | } 200 | } 201 | } 202 | ``` 203 | 204 | ## Next Steps 205 | 206 | - Check out the [C API Guide](c-api-guide.md) for C language integration 207 | - Learn about [Performance Optimization](performance-guide.md) for large-scale applications 208 | - Read the [Core Concepts](core-concepts.md) for a deeper understanding of ECS -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Coyote ECS 2 | 3 | This guide will help you get started with Coyote ECS in your Zig project. 4 | 5 | ## Installation 6 | 7 | 1. Clone the repository: 8 | ```bash 9 | git clone --recursive https://github.com/linuxy/coyote-ecs.git 10 | ``` 11 | 12 | 2. Add Coyote ECS to your project's `build.zig.zon`: 13 | ```zig 14 | .{ 15 | .name = "your-project", 16 | .version = "0.1.0", 17 | .dependencies = .{ 18 | .coyote_ecs = .{ 19 | .url = "https://github.com/linuxy/coyote-ecs/archive/main.tar.gz", 20 | .hash = "your-hash-here", 21 | }, 22 | }, 23 | } 24 | ``` 25 | 26 | 3. Import Coyote ECS in your `build.zig`: 27 | ```zig 28 | const coyote_ecs = b.dependency("coyote_ecs", .{ 29 | .target = target, 30 | .optimize = optimize, 31 | }); 32 | ``` 33 | 34 | ## Basic Usage 35 | 36 | ### 1. Define Your Components 37 | 38 | First, define your components in a container: 39 | 40 | ```zig 41 | const std = @import("std"); 42 | const ecs = @import("coyote-ecs"); 43 | 44 | pub const Components = struct { 45 | pub const Position = struct { 46 | x: f32 = 0, 47 | y: f32 = 0, 48 | }; 49 | 50 | pub const Velocity = struct { 51 | x: f32 = 0, 52 | y: f32 = 0, 53 | }; 54 | }; 55 | ``` 56 | 57 | ### 2. Create a World 58 | 59 | Create a world to manage your entities and components: 60 | 61 | ```zig 62 | var world = try World.create(); 63 | defer world.deinit(); 64 | ``` 65 | 66 | ### 3. Create Entities and Components 67 | 68 | ```zig 69 | // Create an entity 70 | var entity = try world.entities.create(); 71 | 72 | // Create components 73 | var position = try world.components.create(Components.Position); 74 | var velocity = try world.components.create(Components.Velocity); 75 | 76 | // Attach components to the entity 77 | try entity.attach(position, Components.Position{ .x = 0, .y = 0 }); 78 | try entity.attach(velocity, Components.Velocity{ .x = 1, .y = 1 }); 79 | ``` 80 | 81 | ### 4. Create Systems 82 | 83 | Systems are functions that operate on entities with specific components: 84 | 85 | ```zig 86 | pub fn UpdatePosition(world: *World) void { 87 | var it = world.entities.iteratorFilter(Components.Position); 88 | while(it.next()) |entity| { 89 | if(entity.getOneComponent(Components.Velocity)) |velocity| { 90 | var pos = entity.getOneComponent(Components.Position).?; 91 | pos.x += velocity.x; 92 | pos.y += velocity.y; 93 | } 94 | } 95 | } 96 | ``` 97 | 98 | ### 5. Run Your Systems 99 | 100 | ```zig 101 | try Systems.run(UpdatePosition, .{world}); 102 | ``` 103 | 104 | ## Next Steps 105 | 106 | - Check out the [Core Concepts](core-concepts.md) guide to learn more about how Coyote ECS works 107 | - Explore the [Examples](examples.md) for more complex usage patterns 108 | - Read the [API Reference](api-reference.md) for detailed documentation 109 | - Learn about [Performance Optimization](performance-guide.md) 110 | 111 | ## Common Patterns 112 | 113 | ### Iterating Over Components 114 | 115 | ```zig 116 | var it = world.components.iteratorFilter(Components.Position); 117 | while(it.next()) |component| { 118 | // Work with the component 119 | } 120 | ``` 121 | 122 | ### Querying Entities 123 | 124 | ```zig 125 | var it = world.entities.iteratorFilter(Components.Position); 126 | while(it.next()) |entity| { 127 | if(entity.getOneComponent(Components.Velocity)) |velocity| { 128 | // Entity has both Position and Velocity components 129 | } 130 | } 131 | ``` 132 | 133 | ### Component Lifecycle 134 | 135 | ```zig 136 | // Create 137 | var component = try world.components.create(Components.Position); 138 | 139 | // Attach 140 | try entity.attach(component, Components.Position{ .x = 0, .y = 0 }); 141 | 142 | // Detach 143 | entity.detach(component); 144 | 145 | // Destroy 146 | component.destroy(); 147 | ``` -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Coyote ECS Documentation 2 | 3 | Welcome to the Coyote ECS documentation! Coyote ECS is a fast and simple Entity Component System (ECS) written in Zig. 4 | 5 | ## Quick Links 6 | 7 | - [Getting Started](getting-started.md) 8 | - [Core Concepts](core-concepts.md) 9 | - [API Reference](api-reference.md) 10 | - [Examples](examples.md) 11 | - [C API Guide](c-api-guide.md) 12 | - [Performance Guide](performance-guide.md) 13 | 14 | ## What is Coyote ECS? 15 | 16 | Coyote ECS is a lightweight, high-performance Entity Component System designed for Zig applications. It provides a simple yet powerful way to manage game objects, simulations, and other entity-based systems. 17 | 18 | ### Key Features 19 | 20 | - Fast and efficient component storage 21 | - Simple and intuitive API 22 | - C bindings for cross-language compatibility 23 | - Zero dependencies 24 | - Built with Zig 0.14.0 25 | 26 | ## Quick Example 27 | 28 | ```zig 29 | const std = @import("std"); 30 | const ecs = @import("coyote-ecs"); 31 | 32 | // Define your components 33 | pub const Components = struct { 34 | pub const Position = struct { 35 | x: f32 = 0, 36 | y: f32 = 0, 37 | }; 38 | }; 39 | 40 | // Create a world and entities 41 | var world = try World.create(); 42 | defer world.deinit(); 43 | 44 | var entity = try world.entities.create(); 45 | var position = try world.components.create(Components.Position); 46 | try entity.attach(position, Components.Position{ .x = 0, .y = 0 }); 47 | ``` 48 | 49 | ## Getting Started 50 | 51 | Check out our [Getting Started Guide](getting-started.md) to begin using Coyote ECS in your project. 52 | 53 | ## Contributing 54 | 55 | We welcome contributions! Please see our [Contributing Guide](contributing.md) for details on how to get involved. 56 | 57 | ## License 58 | 59 | Coyote ECS is licensed under the same terms as Zig. See the [LICENSE](../LICENSE) file for details. -------------------------------------------------------------------------------- /docs/performance-guide.md: -------------------------------------------------------------------------------- 1 | # Performance Guide 2 | 3 | This guide provides tips and best practices for optimizing your Coyote ECS applications. 4 | 5 | ## Memory Layout 6 | 7 | Coyote ECS is designed with performance in mind. Understanding its memory layout can help you optimize your usage: 8 | 9 | ### Component Storage 10 | 11 | Components are stored in contiguous memory blocks, which provides several benefits: 12 | 13 | 1. Better cache utilization 14 | 2. Reduced memory fragmentation 15 | 3. Efficient iteration over components of the same type 16 | 17 | ```zig 18 | // Good: Components are stored contiguously 19 | var it = world.components.iteratorFilter(Components.Position); 20 | while(it.next()) |component| { 21 | // Fast iteration due to contiguous memory 22 | } 23 | ``` 24 | 25 | ## Component Design 26 | 27 | ### Keep Components Small 28 | 29 | Small components are more efficient to copy and move: 30 | 31 | ```zig 32 | // Good: Small, focused component 33 | pub const Position = struct { 34 | x: f32 = 0, 35 | y: f32 = 0, 36 | }; 37 | 38 | // Bad: Large component with mixed concerns 39 | pub const GameObject = struct { 40 | position: Position, 41 | velocity: Velocity, 42 | health: f32, 43 | inventory: [100]Item, 44 | // ... many more fields 45 | }; 46 | ``` 47 | 48 | ### Use Appropriate Types 49 | 50 | Choose the most appropriate types for your data: 51 | 52 | ```zig 53 | // Good: Using appropriate types 54 | pub const Transform = struct { 55 | x: f32, // For precise positioning 56 | y: f32, 57 | scale: f32, 58 | rotation: f32, 59 | }; 60 | 61 | // Bad: Using larger types than needed 62 | pub const Transform = struct { 63 | x: f64, // Unnecessary precision 64 | y: f64, 65 | scale: f64, 66 | rotation: f64, 67 | }; 68 | ``` 69 | 70 | ## Entity Management 71 | 72 | ### Batch Entity Creation 73 | 74 | Create entities in batches when possible: 75 | 76 | ```zig 77 | // Good: Batch creation 78 | var i: usize = 0; 79 | while(i < 1000) : (i += 1) { 80 | var entity = try world.entities.create(); 81 | // ... setup entity 82 | } 83 | 84 | // Bad: Creating entities one at a time in different places 85 | var entity1 = try world.entities.create(); 86 | // ... some code ... 87 | var entity2 = try world.entities.create(); 88 | // ... more code ... 89 | var entity3 = try world.entities.create(); 90 | ``` 91 | 92 | ### Efficient Entity Destruction 93 | 94 | Destroy entities in batches when possible: 95 | 96 | ```zig 97 | // Good: Batch destruction 98 | var it = world.entities.iterator(); 99 | while(it.next()) |entity| { 100 | entity.destroy(); 101 | } 102 | 103 | // Bad: Destroying entities one at a time 104 | entity1.destroy(); 105 | // ... some code ... 106 | entity2.destroy(); 107 | // ... more code ... 108 | entity3.destroy(); 109 | ``` 110 | 111 | ## Component Access Patterns 112 | 113 | ### Minimize Component Access 114 | 115 | Access components only when needed: 116 | 117 | ```zig 118 | // Good: Minimal component access 119 | pub fn UpdatePosition(world: *World) void { 120 | var it = world.entities.iteratorFilter(Components.Position); 121 | while(it.next()) |entity| { 122 | if(entity.getOneComponent(Components.Velocity)) |velocity| { 123 | var pos = entity.getOneComponent(Components.Position).?; 124 | pos.x += velocity.x; 125 | pos.y += velocity.y; 126 | } 127 | } 128 | } 129 | 130 | // Bad: Frequent component access 131 | pub fn UpdatePosition(world: *World) void { 132 | var it = world.entities.iterator(); 133 | while(it.next()) |entity| { 134 | if(entity.getOneComponent(Components.Position)) |pos| { 135 | if(entity.getOneComponent(Components.Velocity)) |vel| { 136 | pos.x += vel.x; 137 | pos.y += vel.y; 138 | } 139 | } 140 | } 141 | } 142 | ``` 143 | 144 | ### Use Iterator Filters 145 | 146 | Use iterator filters to process only relevant components: 147 | 148 | ```zig 149 | // Good: Using iterator filters 150 | var it = world.components.iteratorFilter(Components.Position); 151 | while(it.next()) |component| { 152 | // Process only position components 153 | } 154 | 155 | // Bad: Checking component type manually 156 | var it = world.components.iterator(); 157 | while(it.next()) |component| { 158 | if(component.is(Components.Position)) { 159 | // Process position component 160 | } 161 | } 162 | ``` 163 | 164 | ## System Design 165 | 166 | ### Keep Systems Focused 167 | 168 | Design systems to do one thing well: 169 | 170 | ```zig 171 | // Good: Focused systems 172 | pub fn UpdatePosition(world: *World) void { 173 | // Only updates position 174 | } 175 | 176 | pub fn UpdateVelocity(world: *World) void { 177 | // Only updates velocity 178 | } 179 | 180 | // Bad: Systems doing too much 181 | pub fn UpdatePhysics(world: *World) void { 182 | // Updates position, velocity, acceleration, forces, etc. 183 | } 184 | ``` 185 | 186 | ### Batch Processing 187 | 188 | Process components in batches when possible: 189 | 190 | ```zig 191 | // Good: Batch processing 192 | pub fn UpdatePositions(world: *World) void { 193 | var it = world.components.iteratorFilter(Components.Position); 194 | while(it.next()) |component| { 195 | // Process multiple components at once 196 | } 197 | } 198 | 199 | // Bad: Processing one at a time 200 | pub fn UpdatePosition(world: *World) void { 201 | var it = world.components.iteratorFilter(Components.Position); 202 | while(it.next()) |component| { 203 | // Process one component at a time 204 | } 205 | } 206 | ``` 207 | 208 | ## Memory Management 209 | 210 | ### Use the Garbage Collector 211 | 212 | Call the garbage collector when appropriate: 213 | 214 | ```zig 215 | // Good: Using GC after batch operations 216 | world.components.gc(); 217 | 218 | // Bad: Calling GC too frequently 219 | while(some_condition) { 220 | // ... process components 221 | world.components.gc(); // Too frequent 222 | } 223 | ``` 224 | 225 | ### Clean Up Resources 226 | 227 | Destroy components and entities when they're no longer needed: 228 | 229 | ```zig 230 | // Good: Proper cleanup 231 | component.destroy(); 232 | entity.destroy(); 233 | 234 | // Bad: Leaving resources allocated 235 | // component and entity remain allocated 236 | ``` 237 | 238 | ## C API Performance 239 | 240 | ### Minimize C API Calls 241 | 242 | Reduce the number of C API calls when possible: 243 | 244 | ```c 245 | // Good: Fewer API calls 246 | component c = coyote_component_create(world, t_position); 247 | coyote_entity_attach(e, c, t_position); 248 | position* pos = coyote_component_get(c); 249 | pos->x = 0.0f; 250 | pos->y = 0.0f; 251 | 252 | // Bad: Many API calls 253 | component c = coyote_component_create(world, t_position); 254 | coyote_entity_attach(e, c, t_position); 255 | position* pos = coyote_component_get(c); 256 | coyote_component_set(c, t_position, &(position){0.0f, 0.0f}); 257 | ``` 258 | 259 | ### Use Appropriate Data Structures 260 | 261 | Choose appropriate data structures for your C components: 262 | 263 | ```c 264 | // Good: Simple struct 265 | typedef struct position { 266 | float x; 267 | float y; 268 | } position; 269 | 270 | // Bad: Complex nested structures 271 | typedef struct game_object { 272 | struct { 273 | float x; 274 | float y; 275 | } position; 276 | struct { 277 | float x; 278 | float y; 279 | } velocity; 280 | // ... many more nested structures 281 | } game_object; 282 | ``` 283 | 284 | ## Benchmarking 285 | 286 | Use the [coyote-bunnies](https://github.com/linuxy/coyote-bunnies) benchmark to measure performance: 287 | 288 | ```zig 289 | // Run the benchmark 290 | zig build -Doptimize=ReleaseFast 291 | ./zig-out/bin/benchmark 292 | ``` 293 | 294 | ## Advanced Optimizations 295 | 296 | For more advanced optimization techniques, including SIMD operations, vectorization, and parallel processing, check out the [Advanced Optimizations Guide](advanced-optimizations.md). 297 | 298 | ## Next Steps 299 | 300 | - Check out the [Examples](examples.md) for performance-oriented examples 301 | - Read the [Core Concepts](core-concepts.md) for a deeper understanding of ECS 302 | - Explore the [C API Guide](c-api-guide.md) for C-specific optimizations 303 | - Learn about [Advanced Optimizations](advanced-optimizations.md) including SIMD and vectorization -------------------------------------------------------------------------------- /examples/fruits.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../include/coyote.h" 4 | 5 | typedef struct apple { 6 | int color; 7 | int ripe; 8 | int harvested; 9 | } apple; 10 | 11 | typedef struct orange { 12 | int color; 13 | int ripe; 14 | int harvested; 15 | } orange; 16 | 17 | typedef struct pear { 18 | int color; 19 | int ripe; 20 | int harvested; 21 | } pear; 22 | 23 | static const coyote_type t_apple = COYOTE_MAKE_TYPE(0, apple); 24 | static const coyote_type t_orange = COYOTE_MAKE_TYPE(1, orange); 25 | static const coyote_type t_pear = COYOTE_MAKE_TYPE(2, pear); 26 | 27 | int main(void) { 28 | world world = coyote_world_create(); 29 | 30 | if(world != 0) 31 | printf("Created world @%d\n", world); 32 | else 33 | printf("World creation failed.\n"); 34 | 35 | entity e_apple = coyote_entity_create(world); 36 | entity e_orange = coyote_entity_create(world); 37 | entity e_pear = coyote_entity_create(world); 38 | 39 | component c_apple = coyote_component_create(world, t_apple); 40 | component c_orange = coyote_component_create(world, t_orange); 41 | component c_pear = coyote_component_create(world, t_pear); 42 | 43 | printf("Created an apple component @%d\n", c_apple); 44 | printf("Created an orange component @%d\n", c_orange); 45 | printf("Created an pear component @%d\n", c_pear); 46 | 47 | iterator it = coyote_components_iterator_filter(world, t_orange); 48 | component next = coyote_components_iterator_filter_next(it); 49 | if(next) 50 | printf("Another orange component @%d\n", c_orange); 51 | else 52 | printf("NOT another orange component @%d\n", c_orange); 53 | 54 | if(coyote_component_is(c_orange, t_orange)) 55 | printf("Component is an orange @%d\n", c_orange); 56 | else 57 | printf("Component is NOT an orange @%d\n", c_orange); 58 | 59 | coyote_entity_attach(e_apple, c_apple, t_apple); 60 | 61 | //Assignment must happen after attach, TODO: Change? 62 | apple* a1 = coyote_component_get(c_apple); a1->color = 255; a1->ripe = 0; a1->harvested = 0; 63 | printf("Got and assigned an apple component @%d\n", a1); 64 | 65 | coyote_entity_detach(e_apple, c_apple); 66 | coyote_component_destroy(c_apple); 67 | coyote_entity_destroy(e_apple); 68 | coyote_entity_destroy(e_pear); 69 | 70 | printf("Number of entities: %d == 1\n", coyote_entities_count(world)); 71 | printf("Number of components: %d == 2\n", coyote_components_count(world)); 72 | 73 | coyote_world_destroy(world); 74 | printf("World destroyed.\n"); 75 | return 0; 76 | } 77 | -------------------------------------------------------------------------------- /examples/fruits.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const ecs = @import("coyote-ecs"); 3 | 4 | const World = ecs.World; 5 | const Cast = ecs.Cast; 6 | const Systems = ecs.Systems; 7 | 8 | //Name configured in ECS constants 9 | pub const Components = struct { 10 | pub const Apple = struct { 11 | color: u32 = 0, 12 | ripe: bool = false, 13 | harvested: bool = false, 14 | }; 15 | 16 | pub const Orange = struct { 17 | color: u32 = 0, 18 | ripe: bool = false, 19 | harvested: bool = false, 20 | }; 21 | 22 | pub const Pear = struct { 23 | color: u32 = 0, 24 | ripe: bool = false, 25 | harvested: bool = false, 26 | }; 27 | }; 28 | 29 | pub fn main() !void { 30 | //Create a world 31 | var world = try World.create(); 32 | defer world.destroy(); 33 | 34 | //Create an entity 35 | var anOrange = try world.entities.create(); 36 | var anApple = try world.entities.create(); 37 | var aPear = try world.entities.create(); 38 | 39 | std.log.info("Created an Orange ID: {}", .{anOrange.id}); 40 | 41 | //Create a unique component 42 | const orangeComponent = try world.components.create(Components.Orange); 43 | const appleComponent = try world.components.create(Components.Apple); 44 | 45 | //Attach and assign a component. Do not use an anonymous struct. 46 | try anOrange.attach(orangeComponent, Components.Orange{ .color = 0, .ripe = false, .harvested = false }); 47 | try anApple.attach(appleComponent, Components.Apple{ .color = 0, .ripe = false, .harvested = false }); 48 | 49 | //Create 50k entities and attach 50k unique components 50 | var i: usize = 0; 51 | while (i < 50000) : (i += 1) { 52 | const anEntity = try world.entities.create(); 53 | const anOrangeComponent = try world.components.create(Components.Orange); 54 | try anEntity.attach(anOrangeComponent, Components.Orange{ .color = 1, .ripe = false, .harvested = false }); 55 | } 56 | 57 | //Filter components by type 58 | var it = world.components.iteratorFilter(Components.Orange); 59 | i = 0; 60 | while (it.next()) |_| : (i += 1) { 61 | //... 62 | } 63 | 64 | std.log.info("Orange components: {}", .{i}); 65 | 66 | //Filter entities by type 67 | var it2 = world.entities.iteratorFilter(Components.Apple); 68 | i = 0; 69 | while (it2.next()) |_| : (i += 1) { 70 | //... 71 | } 72 | 73 | std.log.info("Apple entities: {}", .{i}); 74 | 75 | _ = try aPear.addComponent(Components.Pear{ .color = 1, .ripe = false, .harvested = false }); 76 | 77 | if (aPear.getOneComponent(Components.Pear) != null) 78 | std.log.info("Pear entities: >= 1", .{}) 79 | else 80 | std.log.info("Pear entities: 0", .{}); 81 | 82 | try Systems.run(Grow, .{world}); 83 | try Systems.run(Harvest, .{world}); 84 | try Systems.run(Raze, .{world}); 85 | 86 | std.log.info("Entities: {}", .{world.entities.count()}); 87 | std.log.info("Components: {}", .{world.components.count()}); 88 | } 89 | 90 | pub fn Grow(world: *World) void { 91 | var it = world.components.iterator(); 92 | var i: u32 = 0; 93 | while (it.next()) |component| : (i += 1) { 94 | if (component.is(Components.Orange)) { 95 | try component.set(Components.Orange, .{ .ripe = true }); 96 | } 97 | 98 | if (component.is(Components.Apple)) { 99 | try component.set(Components.Apple, .{ .ripe = true }); 100 | } 101 | 102 | if (component.is(Components.Pear)) { 103 | try component.set(Components.Pear, .{ .ripe = true }); 104 | } 105 | //Fruits fall from the tree 106 | component.detach(); 107 | } 108 | std.log.info("Fruits grown: {}", .{i}); 109 | } 110 | 111 | pub fn Harvest(world: *World) void { 112 | var it = world.components.iterator(); 113 | var i: u32 = 0; 114 | while (it.next()) |component| { 115 | if (component.is(Components.Orange)) { 116 | if (Cast(Components.Orange, component).ripe == true) { 117 | try component.set(Components.Orange, .{ .harvested = true }); 118 | i += 1; 119 | } 120 | } 121 | if (component.is(Components.Apple)) { 122 | if (Cast(Components.Apple, component).ripe == true) { 123 | try component.set(Components.Apple, .{ .harvested = true }); 124 | i += 1; 125 | } 126 | } 127 | if (component.is(Components.Pear)) { 128 | if (Cast(Components.Pear, component).ripe == true) { 129 | try component.set(Components.Pear, .{ .harvested = true }); 130 | i += 1; 131 | } 132 | } 133 | component.destroy(); 134 | } 135 | 136 | world.components.gc(); 137 | std.log.info("Fruits harvested: {}", .{i}); 138 | } 139 | 140 | pub fn Raze(world: *World) void { 141 | var it = world.entities.iterator(); 142 | var i: u32 = 0; 143 | 144 | while (it.next()) |entity| { 145 | entity.destroy(); 146 | i += 1; 147 | } 148 | 149 | std.log.info("Entities destroyed: {}", .{i}); 150 | } 151 | -------------------------------------------------------------------------------- /examples/tests.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const ecs = @import("coyote-ecs"); 3 | 4 | const World = ecs.World; 5 | const Cast = ecs.Cast; 6 | const Systems = ecs.Systems; 7 | 8 | const NUM = 1_000_000; 9 | const CHUNK_SIZE = 10_000; // Process in smaller chunks for debugging 10 | 11 | pub const Components = struct { 12 | pub const Apple = struct { 13 | color: u32 = 0, 14 | ripe: bool = false, 15 | harvested: bool = false, 16 | }; 17 | 18 | pub const Orange = struct { 19 | color: u32 = 0, 20 | ripe: bool = false, 21 | harvested: bool = false, 22 | }; 23 | 24 | pub const Pear = struct { 25 | color: u32 = 0, 26 | ripe: bool = false, 27 | harvested: bool = false, 28 | }; 29 | }; 30 | 31 | pub fn main() !void { 32 | var world = try World.create(); 33 | defer world.destroy(); 34 | 35 | std.debug.print("Creating {} entities ... ", .{NUM}); 36 | try elapsed(tests_entity_create, .{world}); 37 | std.debug.print("Iterating {} entities ... ", .{NUM}); 38 | try elapsed(tests_entity_iterate, .{world}); 39 | 40 | // Break down component creation into chunks 41 | std.debug.print("Creating {} emplaced components and entities in chunks of {} ", .{ NUM, CHUNK_SIZE }); 42 | var chunk_start: usize = 0; 43 | const then = std.time.milliTimestamp(); 44 | while (chunk_start < NUM) : (chunk_start += CHUNK_SIZE) { 45 | const chunk_end = @min(chunk_start + CHUNK_SIZE, NUM); 46 | //std.debug.print("Processing chunk {}/{} ({}-{})...\n", .{ chunk_start / CHUNK_SIZE + 1, (NUM + CHUNK_SIZE - 1) / CHUNK_SIZE, chunk_start, chunk_end }); 47 | try tests_component_create_range(world, chunk_start, chunk_end); 48 | 49 | // Add a GC call after each chunk to prevent memory buildup 50 | world.components.gc(); 51 | } 52 | std.debug.print("completed in {}ms.\n", .{std.time.milliTimestamp() - then}); 53 | std.debug.print("Iterating {} components ... ", .{NUM}); 54 | try elapsed(tests_component_iterate, .{world}); 55 | std.debug.print("Destroying and deallocating {} components ... ", .{NUM}); 56 | try elapsed(tests_component_destroy, .{world}); 57 | std.debug.print("Destroying {} entities ... ", .{NUM}); 58 | try elapsed(tests_entity_destroy, .{world}); 59 | } 60 | 61 | pub fn tests_entity_create(world: *World) !void { 62 | var i: usize = 0; 63 | while (i < NUM) : (i += 1) { 64 | const anEntity = try world.entities.create(); 65 | _ = anEntity; 66 | } 67 | } 68 | 69 | pub fn tests_entity_iterate(world: *World) !void { 70 | var it = world.entities.iterator(); 71 | var count: usize = 0; 72 | while (it.next()) |_| { 73 | count += 1; 74 | } 75 | std.debug.print("(Found {} entities) ", .{count}); 76 | } 77 | 78 | pub fn tests_entity_destroy(world: *World) !void { 79 | var it = world.entities.iterator(); 80 | var count: usize = 0; 81 | while (it.next()) |entity| { 82 | entity.destroy(); 83 | count += 1; 84 | } 85 | std.debug.print("(Destroyed {} entities) ", .{count}); 86 | } 87 | 88 | pub fn tests_component_create_range(world: *World, start: usize, end: usize) !void { 89 | var i: usize = start; 90 | while (i < end) : (i += 1) { 91 | var anEntity = try world.entities.create(); 92 | const component = try anEntity.addComponent(Components.Pear{ .color = 1, .ripe = false, .harvested = false }); 93 | 94 | // Verify the component was properly attached 95 | if (!component.attached) { 96 | //std.debug.print("Component at index {} was not properly attached\n", .{i}); 97 | return error.ComponentNotAttached; 98 | } 99 | 100 | // Add some debug info every 1000 components 101 | if (i % 1000 == 0) { 102 | //std.debug.print("Created component {} (chunk offset {})\n", .{ i, i - start }); 103 | } 104 | } 105 | } 106 | 107 | pub fn tests_component_create(world: *World) !void { 108 | try tests_component_create_range(world, 0, NUM); 109 | } 110 | 111 | pub fn tests_component_iterate(world: *World) !void { 112 | var it = world.components.iterator(); 113 | var count: usize = 0; 114 | while (it.next()) |_| { 115 | count += 1; 116 | } 117 | std.debug.print("(Found {} components) ", .{count}); 118 | } 119 | 120 | pub fn tests_component_destroy(world: *World) !void { 121 | var it = world.components.iterator(); 122 | var count: usize = 0; 123 | while (it.next()) |component| { 124 | component.destroy(); 125 | count += 1; 126 | } 127 | world.components.gc(); 128 | std.debug.print("(Destroyed {} components) ", .{count}); 129 | } 130 | 131 | pub fn elapsed(comptime f: anytype, args: anytype) !void { 132 | const then = std.time.milliTimestamp(); 133 | const ret = @call(.auto, f, args); 134 | if (@typeInfo(@TypeOf(ret)) == .error_union) try ret; 135 | std.debug.print("completed in {}ms.\n", .{std.time.milliTimestamp() - then}); 136 | } 137 | -------------------------------------------------------------------------------- /include/coyote.h: -------------------------------------------------------------------------------- 1 | #ifndef COYOTE_H 2 | #define COYOTE_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | 10 | typedef struct coyote_type { 11 | uintptr_t id; 12 | uintptr_t size; // component sizeof 13 | const char* name; // component name 14 | } coyote_type; 15 | 16 | #define COYOTE_MAKE_TYPE(TypeId, TypeName) { .id = TypeId, .size = sizeof(TypeName) , .name = #TypeName } 17 | 18 | typedef uintptr_t entity; 19 | typedef uintptr_t component; 20 | typedef uintptr_t world; 21 | typedef uintptr_t iterator; 22 | 23 | uintptr_t coyote_world_create(); 24 | void coyote_world_destroy(world world); 25 | entity coyote_entity_create(world world); 26 | void coyote_entity_destroy(entity entity); 27 | component coyote_component_create(world world, coyote_type type); 28 | int coyote_entity_attach(entity entity, component component, coyote_type type); 29 | int coyote_entity_detach(entity entity, component component); 30 | int coyote_entities_iterator(world world, iterator iterator); 31 | entity coyote_entities_iterator_next(iterator iterator); 32 | int coyote_components_iterator(world world, iterator iterator); 33 | component coyote_components_iterator_next(iterator iterator); 34 | int coyote_component_is(component component, coyote_type type); 35 | iterator coyote_components_iterator_filter(world world, coyote_type type); 36 | iterator coyote_components_entities_filter(world world, coyote_type type); 37 | component coyote_components_iterator_filter_next(iterator iterator); 38 | entity coyote_entities_iterator_filter_next(iterator iterator); 39 | iterator coyote_components_iterator_filter_range(world world, coyote_type type, size_t start_idx, size_t end_idx); 40 | component coyote_components_iterator_filter_range_next(iterator iterator); 41 | void coyote_components_gc(world world); 42 | int coyote_components_count(world world); 43 | int coyote_entities_count(world world); 44 | void* coyote_component_get(component component); 45 | void coyote_component_destroy(component component); 46 | 47 | #ifdef __cplusplus 48 | } 49 | #endif 50 | 51 | #endif /* COYOTE_H */ 52 | -------------------------------------------------------------------------------- /src/c_api.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const coyote = @import("coyote.zig"); 4 | 5 | inline fn coyote_error(err: anyerror) c_int { 6 | return @intFromError(err); 7 | } 8 | 9 | export fn coyote_world_create() usize { 10 | const new_world = @as(?*anyopaque, @ptrCast(coyote.World.create() catch return 0)); 11 | return @intFromPtr(new_world); 12 | } 13 | 14 | export fn coyote_world_destroy(world_ptr: usize) void { 15 | const world = @as(*coyote.World, @ptrFromInt(world_ptr)); 16 | world.destroy(); 17 | } 18 | 19 | export fn coyote_entity_create(world_ptr: usize) usize { 20 | const world = @as(*coyote.World, @ptrFromInt(world_ptr)); 21 | const entity = (world.entities.create() catch return 0); 22 | return @intFromPtr(entity); 23 | } 24 | 25 | export fn coyote_entity_destroy(entity_ptr: usize) void { 26 | if (entity_ptr == 0) { 27 | std.log.err("Invalid entity.", .{}); 28 | return; 29 | } 30 | 31 | const entity = @as(*coyote.Entity, @ptrFromInt(entity_ptr)); 32 | entity.destroy(); 33 | } 34 | //static const coy_type transform_type = { .coy_id = 0, .coy_sizeof = sizeof(transform) , .name = "transform"}; 35 | //static const coy_type velocity_type = COYOTE_MAKE_TYPE(1, velocity); 36 | export fn coyote_component_create(world_ptr: usize, c_type: coyote.c_type) usize { 37 | const world = @as(*coyote.World, @ptrFromInt(world_ptr)); 38 | const component = world.components.create_c(c_type) catch |err| return @as(usize, @intCast(coyote_error(err))); 39 | 40 | return @intFromPtr(component); 41 | } 42 | 43 | export fn coyote_component_destroy(component_ptr: usize) void { 44 | if (component_ptr == 0) { 45 | std.log.err("Invalid component.", .{}); 46 | return; 47 | } 48 | 49 | const component = @as(*coyote.Component, @ptrFromInt(component_ptr)); 50 | component.destroy(); 51 | } 52 | 53 | export fn coyote_components_gc(world_ptr: usize) void { 54 | const world = @as(*coyote.World, @ptrFromInt(world_ptr)); 55 | world.components.gc(); 56 | } 57 | 58 | export fn coyote_component_get(component_ptr: usize) ?*anyopaque { 59 | if (component_ptr == 0) { 60 | std.log.err("Invalid component.", .{}); 61 | return null; 62 | } 63 | 64 | const component = @as(*coyote.Component, @ptrFromInt(component_ptr)); 65 | return component.data; 66 | } 67 | 68 | export fn coyote_entity_attach(entity_ptr: usize, component_ptr: usize, c_type: coyote.c_type) c_int { 69 | if (component_ptr == 0) { 70 | std.log.err("Invalid component.", .{}); 71 | return 1; 72 | } 73 | 74 | const component = @as(*coyote.Component, @ptrFromInt(component_ptr)); 75 | 76 | if (entity_ptr == 0) { 77 | std.log.err("Invalid entity.", .{}); 78 | return 1; 79 | } 80 | 81 | const entity = @as(*coyote.Entity, @ptrFromInt(entity_ptr)); 82 | 83 | entity.attach(component, c_type) catch return 1; 84 | 85 | return 0; 86 | } 87 | 88 | export fn coyote_entity_detach(entity_ptr: usize, component_ptr: usize) c_int { 89 | if (component_ptr == 0) { 90 | std.log.err("Invalid component.", .{}); 91 | return 1; 92 | } 93 | 94 | const component = @as(*coyote.Component, @ptrFromInt(component_ptr)); 95 | 96 | if (entity_ptr == 0) { 97 | std.log.err("Invalid entity.", .{}); 98 | return 1; 99 | } 100 | 101 | const entity = @as(*coyote.Entity, @ptrFromInt(entity_ptr)); 102 | 103 | _ = entity; 104 | component.detach(); 105 | return 0; 106 | } 107 | 108 | export fn coyote_component_detach(component_ptr: usize) c_int { 109 | if (component_ptr == 0) { 110 | std.log.err("Invalid component.", .{}); 111 | return 1; 112 | } 113 | 114 | const component = @as(*coyote.Entity, @ptrFromInt(component_ptr)); 115 | _ = component; 116 | 117 | return 0; 118 | } 119 | 120 | export fn coyote_components_iterator(world_ptr: usize, out_iterator: *coyote.SuperComponents.Iterator) c_int { 121 | const world = @as(*coyote.World, @ptrFromInt(world_ptr)); 122 | const it = world.components.iterator(); 123 | out_iterator.* = it; 124 | return 0; 125 | } 126 | 127 | export fn coyote_components_iterator_next(iterator_ptr: usize) usize { 128 | const iterator = @as(*coyote.SuperComponents.Iterator, @ptrFromInt(iterator_ptr)); 129 | if (iterator.next()) |bind| { 130 | return @intFromPtr(bind); 131 | } else { 132 | coyote.allocator.destroy(iterator); 133 | return 0; 134 | } 135 | } 136 | 137 | export fn coyote_components_iterator_filter(world_ptr: usize, c_type: coyote.c_type) usize { 138 | const world = @as(*coyote.World, @ptrFromInt(world_ptr)); 139 | const components = &world._components; 140 | const iterator = coyote.allocator.create(coyote.SuperComponents.MaskedIterator) catch unreachable; 141 | iterator.* = coyote.SuperComponents.MaskedIterator{ .ctx = components, .filter_type = coyote.typeToIdC(c_type), .alive = coyote.CHUNK_SIZE * world.components_len, .world = world }; 142 | return @intFromPtr(iterator); 143 | } 144 | 145 | export fn coyote_components_iterator_filter_next(iterator_ptr: usize) usize { 146 | const iterator = @as(*coyote.SuperComponents.MaskedIterator, @ptrFromInt(iterator_ptr)); 147 | if (iterator.next()) |bind| { 148 | return @intFromPtr(bind); 149 | } else { 150 | coyote.allocator.destroy(iterator); 151 | return 0; 152 | } 153 | } 154 | 155 | export fn coyote_entities_iterator(world_ptr: usize, out_iterator: *coyote.SuperEntities.Iterator) usize { 156 | const world = @as(*coyote.World, @ptrFromInt(world_ptr)); 157 | const it = world.entities.iterator(); 158 | out_iterator.* = it; 159 | return 0; 160 | } 161 | 162 | export fn coyote_entities_iterator_next(iterator_ptr: usize) usize { 163 | const iterator = @as(*coyote.SuperEntities.Iterator, @ptrFromInt(iterator_ptr)); 164 | if (iterator.next()) |bind| { 165 | return @intFromPtr(bind); 166 | } else { 167 | coyote.allocator.destroy(iterator); 168 | return 0; 169 | } 170 | } 171 | 172 | export fn coyote_entities_iterator_filter(world_ptr: usize, c_type: coyote.c_type) usize { 173 | const world = @as(*coyote.World, @ptrFromInt(world_ptr)); 174 | const entities = &world._entities; 175 | const iterator = coyote.allocator.create(coyote.SuperEntities.MaskedIterator) catch unreachable; 176 | iterator.* = coyote.SuperEntities.MaskedIterator{ .ctx = entities, .filter_type = coyote.typeToIdC(c_type), .alive = world.entities.alive, .world = world }; 177 | return @intFromPtr(iterator); 178 | } 179 | 180 | export fn coyote_entities_iterator_filter_next(iterator_ptr: usize) usize { 181 | const iterator = @as(*coyote.SuperEntities.MaskedIterator, @ptrFromInt(iterator_ptr)); 182 | if (iterator.next()) |bind| { 183 | return @intFromPtr(bind); 184 | } else { 185 | coyote.allocator.destroy(iterator); 186 | return 0; 187 | } 188 | } 189 | 190 | export fn coyote_component_is(component: *coyote.Component, c_type: coyote.c_type) usize { 191 | if (component.typeId.? == c_type.id) { 192 | return 1; 193 | } else { 194 | return 0; 195 | } 196 | } 197 | 198 | export fn coyote_entities_count(world_ptr: usize) c_int { 199 | const world = @as(*coyote.World, @ptrFromInt(world_ptr)); 200 | return @as(c_int, @intCast(world.entities.count())); 201 | } 202 | 203 | export fn coyote_components_count(world_ptr: usize) c_int { 204 | const world = @as(*coyote.World, @ptrFromInt(world_ptr)); 205 | return @as(c_int, @intCast(world.components.count())); 206 | } 207 | 208 | export fn coyote_components_iterator_filter_range(world_ptr: usize, c_type: coyote.c_type, start_idx: usize, end_idx: usize) usize { 209 | const world = @as(*coyote.World, @ptrFromInt(world_ptr)); 210 | const components = &world._components; 211 | const iterator = coyote.allocator.create(coyote.SuperComponents.MaskedRangeIterator) catch unreachable; 212 | iterator.* = coyote.SuperComponents.MaskedRangeIterator{ .ctx = components, .filter_type = coyote.typeToIdC(c_type), .index = start_idx, .start_index = start_idx, .end_index = end_idx, .world = world }; 213 | return @intFromPtr(iterator); 214 | } 215 | 216 | export fn coyote_components_iterator_filter_range_next(iterator_ptr: usize) usize { 217 | const iterator = @as(*coyote.SuperComponents.MaskedRangeIterator, @ptrFromInt(iterator_ptr)); 218 | if (iterator.next()) |bind| { 219 | return @intFromPtr(bind); 220 | } else { 221 | coyote.allocator.destroy(iterator); 222 | return 0; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/coyote.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | 4 | pub const MAX_COMPONENTS = 12; //Maximum number of component types, 10x runs 10x slower create O(n) TODO: Fix 5 | pub const CHUNK_SIZE = 128; //Only operate on one chunk at a time 6 | pub const MAGIC = 0x0DEADB33F; //Helps check for optimizer related issues 7 | 8 | pub const allocator = std.heap.c_allocator; 9 | 10 | //No chunk should know of another chunk 11 | //Modulo ID/CHUNK 12 | 13 | //SuperComponents map component chunks to current layout 14 | 15 | pub const c_type = extern struct { 16 | id: usize = 0, 17 | size: usize = 0, 18 | alignof: u8 = 8, 19 | name: [*c]u8 = null, 20 | }; 21 | 22 | pub const SuperComponents = struct { 23 | world: ?*anyopaque = undefined, //Defeats cyclical reference checking 24 | alive: usize, 25 | 26 | pub inline fn count(ctx: *SuperComponents) u32 { 27 | const world = @as(*World, @ptrCast(@alignCast(ctx.world))); 28 | 29 | var i: usize = 0; 30 | var total: u32 = 0; 31 | while (i < world.components_len) : (i += 1) { 32 | total += world._components[i].alive; 33 | } 34 | return total; 35 | } 36 | 37 | pub fn create(ctx: *SuperComponents, comptime comp_type: type) !*Component { 38 | var world = @as(*World, @ptrCast(@alignCast(ctx.world))); 39 | 40 | // Find a chunk with available space 41 | var i: usize = 0; 42 | var found_chunk = false; 43 | while (i < world.components_len) : (i += 1) { 44 | if (world._components[i].alive < CHUNK_SIZE) { 45 | world.components_free_idx = i; 46 | found_chunk = true; 47 | break; 48 | } 49 | } 50 | 51 | // If no chunk has space, create a new one 52 | if (!found_chunk) { 53 | try ctx.expand(); 54 | } 55 | 56 | // Create the component in the selected chunk 57 | const component = try world._components[world.components_free_idx].create(comp_type); 58 | 59 | // Only increment the alive count after successful creation 60 | ctx.alive += 1; 61 | 62 | return component; 63 | } 64 | 65 | pub fn create_c(ctx: *SuperComponents, comp_type: c_type) !*Component { 66 | var world = @as(*World, @ptrCast(@alignCast(ctx.world))); 67 | 68 | // Find a chunk with available space 69 | var i: usize = 0; 70 | var found_chunk = false; 71 | while (i < world.components_len) : (i += 1) { 72 | if (world._components[i].alive < CHUNK_SIZE) { 73 | world.components_free_idx = i; 74 | found_chunk = true; 75 | break; 76 | } 77 | } 78 | 79 | // If no chunk has space, create a new one 80 | if (!found_chunk) { 81 | try ctx.expand(); 82 | } 83 | 84 | // Create the component in the selected chunk 85 | const component = try world._components[world.components_free_idx].create_c(comp_type); 86 | 87 | // Only increment the alive count after successful creation 88 | ctx.alive += 1; 89 | 90 | return component; 91 | } 92 | 93 | pub fn expand(ctx: *SuperComponents) !void { 94 | var world = @as(*World, @ptrCast(@alignCast(ctx.world))); 95 | 96 | world._components = try world.allocator.realloc(world._components, world.components_len + 1); 97 | world._components[world.components_len].world = world; 98 | world._components[world.components_len].len = 0; 99 | world._components[world.components_len].alive = 0; 100 | world._components[world.components_len].created = 0; 101 | world._components[world.components_len].free_idx = 0; 102 | world._components[world.components_len].chunk = world.components_len; 103 | world._components[world.components_len].sparse = try world.allocator.alloc(Component, CHUNK_SIZE); 104 | 105 | var i: usize = 0; 106 | while (i < MAX_COMPONENTS) : (i += 1) { 107 | world._components[world.components_len].entity_mask[i] = std.StaticBitSet(CHUNK_SIZE).initEmpty(); 108 | } 109 | 110 | world.components_len += 1; 111 | world.components_free_idx = world.components_len - 1; 112 | components_idx = world.components_free_idx; 113 | } 114 | 115 | pub fn gc(ctx: *SuperComponents) void { 116 | const world = @as(*World, @ptrCast(@alignCast(ctx.world))); 117 | var i: usize = 0; 118 | var j: usize = 0; 119 | while (i < world.components_len) : (i += 1) { 120 | while (j < CHUNK_SIZE) : (j += 1) { 121 | if (world._components[i].sparse[j].allocated and !world._components[i].sparse[j].alive) { 122 | world._components[i].sparse[j].dealloc(); 123 | } 124 | } 125 | j = 0; 126 | } 127 | } 128 | 129 | pub const Iterator = struct { 130 | ctx: *[]_Components, 131 | index: usize = 0, 132 | alive: usize = 0, 133 | world: *World, 134 | 135 | pub inline fn next(it: *Iterator) ?*Component { 136 | while (it.index < it.alive) : (it.index += 1) { 137 | const mod = it.index / CHUNK_SIZE; 138 | const rem = @rem(it.index, CHUNK_SIZE); 139 | if (it.ctx.*[mod].sparse[rem].alive) { 140 | const sparse_index = rem; 141 | it.index += 1; 142 | return &it.ctx.*[mod].sparse[sparse_index]; 143 | } 144 | } 145 | 146 | return null; 147 | } 148 | }; 149 | 150 | pub const MaskedIterator = struct { 151 | ctx: *[]_Components, 152 | index: usize = 0, 153 | filter_type: u32, 154 | alive: usize = 0, 155 | world: *World, 156 | 157 | pub fn next(it: *MaskedIterator) ?*Component { 158 | const vector_width = std.simd.suggestVectorLength(u32) orelse 4; // Or appropriate size 159 | var i: usize = it.index; 160 | while (i + vector_width <= it.alive) : (i += vector_width) { 161 | const mod = i / CHUNK_SIZE; 162 | const rems = blk: { 163 | var result: @Vector(vector_width, u32) = undefined; 164 | var j: u32 = 0; 165 | while (j < vector_width) : (j += 1) { 166 | result[j] = @intCast(@rem(i + j, CHUNK_SIZE)); 167 | } 168 | break :blk result; 169 | }; 170 | 171 | // Process multiple mask checks in parallel 172 | const masks = blk: { 173 | var result: @Vector(vector_width, bool) = undefined; 174 | var j: u32 = 0; 175 | while (j < vector_width) : (j += 1) { 176 | result[j] = it.world._components[mod].entity_mask[it.filter_type].isSet(rems[j]); 177 | } 178 | break :blk result; 179 | }; 180 | 181 | // Find first match 182 | inline for (0..vector_width) |j| { 183 | if (masks[j]) { 184 | it.index = i + j + 1; 185 | return &it.ctx.*[mod].sparse[@intCast(rems[j])]; 186 | } 187 | } 188 | } 189 | 190 | // Handle remaining elements 191 | while (i < it.alive) : (i += 1) { 192 | const mod = i / CHUNK_SIZE; 193 | const rem = @rem(i, CHUNK_SIZE); 194 | if (it.world._components[mod].entity_mask[it.filter_type].isSet(rem)) { 195 | const sparse_index = rem; 196 | it.index = i + 1; 197 | return &it.ctx.*[mod].sparse[sparse_index]; 198 | } 199 | } 200 | 201 | return null; 202 | } 203 | }; 204 | 205 | pub const MaskedRangeIterator = struct { 206 | ctx: *[]_Components, 207 | index: usize = 0, 208 | filter_type: u32, 209 | start_index: usize = 0, 210 | end_index: usize = 0, 211 | world: *World, 212 | 213 | pub fn next(it: *MaskedRangeIterator) ?*Component { 214 | const vector_width = std.simd.suggestVectorLength(u32) orelse 4; 215 | var i: usize = it.index; 216 | 217 | // Ensure we don't go beyond the end_index 218 | const effective_end = @min(it.end_index, it.index + vector_width); 219 | 220 | if (i < effective_end) { 221 | const mod = i / CHUNK_SIZE; 222 | const rems = blk: { 223 | var result: @Vector(vector_width, u32) = undefined; 224 | var j: u32 = 0; 225 | while (j < vector_width) : (j += 1) { 226 | result[j] = @intCast(@rem(i + j, CHUNK_SIZE)); 227 | } 228 | break :blk result; 229 | }; 230 | 231 | // Process multiple mask checks in parallel 232 | const masks = blk: { 233 | var result: @Vector(vector_width, bool) = undefined; 234 | var j: u32 = 0; 235 | while (j < vector_width) : (j += 1) { 236 | result[j] = it.world._components[mod].entity_mask[it.filter_type].isSet(rems[j]); 237 | } 238 | break :blk result; 239 | }; 240 | 241 | // Find first match 242 | inline for (0..vector_width) |j| { 243 | if (masks[j]) { 244 | it.index = i + j + 1; 245 | return &it.ctx.*[mod].sparse[@intCast(rems[j])]; 246 | } 247 | } 248 | } 249 | 250 | // Handle remaining elements 251 | while (i < it.end_index) : (i += 1) { 252 | const mod = i / CHUNK_SIZE; 253 | const rem = @rem(i, CHUNK_SIZE); 254 | if (it.world._components[mod].entity_mask[it.filter_type].isSet(rem)) { 255 | const sparse_index = rem; 256 | it.index = i + 1; 257 | return &it.ctx.*[mod].sparse[sparse_index]; 258 | } 259 | } 260 | 261 | return null; 262 | } 263 | }; 264 | 265 | pub const MaskedEntityIterator = struct { 266 | ctx: *[]_Components, 267 | inner_index: usize = 0, 268 | outer_index: usize = 0, 269 | filter_type: u32, 270 | entities_alive: usize = 0, 271 | components_alive: usize = 0, 272 | world: *World, 273 | entity: *Entity, 274 | 275 | pub inline fn next(it: *MaskedEntityIterator) ?*Component { 276 | const vector_width = std.simd.suggestVectorLength(u32) orelse 4; 277 | 278 | while (it.outer_index < it.components_alive) { 279 | // Process vector_width components at a time 280 | const remaining = it.components_alive - it.outer_index; 281 | const batch_size = @min(vector_width, remaining); 282 | 283 | // Prepare vectors for parallel processing 284 | var owner_checks: @Vector(vector_width, bool) = @splat(false); 285 | var component_indices: @Vector(vector_width, u32) = undefined; 286 | 287 | // Fill vectors with component data 288 | for (0..batch_size) |i| { 289 | const idx = it.outer_index + i; 290 | const rem = @rem(idx, CHUNK_SIZE); 291 | const mod = idx / CHUNK_SIZE; 292 | component_indices[i] = @intCast(rem); 293 | //owner_checks[i] = it.world._entities[idx].component_mask[it.filter_type].isSet(it.entity.id); 294 | owner_checks[i] = it.world._components[mod].sparse[rem].owners.isSet(it.entity.id); 295 | } 296 | 297 | // Process components that are owned by the entity 298 | for (0..batch_size) |i| { 299 | if (owner_checks[i]) { 300 | const mod = (it.outer_index + i) / CHUNK_SIZE; 301 | const rem = component_indices[i]; 302 | 303 | // Use SIMD for entity chunk processing 304 | const entities_per_vector = std.simd.suggestVectorLength(u32) orelse 4; 305 | var entity_idx: usize = 0; 306 | 307 | while (entity_idx + entities_per_vector <= it.world.entities_len) : (entity_idx += entities_per_vector) { 308 | var entity_checks: @Vector(entities_per_vector, bool) = undefined; 309 | 310 | // Check multiple entities in parallel 311 | inline for (0..entities_per_vector) |j| { 312 | entity_checks[j] = it.world._entities[entity_idx + j].component_mask[it.filter_type].isSet(it.world._components[mod].sparse[@intCast(rem)].id); 313 | } 314 | 315 | // If any entity has this component 316 | inline for (0..entities_per_vector) |j| { 317 | if (entity_checks[j]) { 318 | it.outer_index += i + 1; 319 | return &it.ctx.*[mod].sparse[@intCast(rem)]; 320 | } 321 | } 322 | } 323 | 324 | // Handle remaining entities 325 | while (entity_idx < it.world.entities_len) : (entity_idx += 1) { 326 | if (it.world._entities[entity_idx].component_mask[it.filter_type].isSet(it.world._components[mod].sparse[@intCast(rem)].id)) { 327 | it.outer_index += i + 1; 328 | return &it.ctx.*[mod].sparse[@intCast(rem)]; 329 | } 330 | } 331 | } 332 | } 333 | 334 | it.outer_index += batch_size; 335 | } 336 | 337 | return null; 338 | } 339 | }; 340 | 341 | //TODO: By attached vs unattached 342 | pub inline fn iterator(ctx: *SuperComponents) SuperComponents.Iterator { 343 | const world = @as(*World, @ptrCast(@alignCast(ctx.world))); 344 | const components = &world._components; 345 | return .{ .ctx = components, .index = 0, .alive = CHUNK_SIZE * world.components_len, .world = world }; 346 | } 347 | 348 | pub fn iteratorFilter(ctx: *SuperComponents, comptime comp_type: type) SuperComponents.MaskedIterator { 349 | //get an iterator for components attached to this entity 350 | const world = @as(*World, @ptrCast(@alignCast(ctx.world))); 351 | const components = &world._components; 352 | return .{ .ctx = components, .filter_type = typeToId(comp_type), .alive = CHUNK_SIZE * world.components_len, .world = world }; 353 | } 354 | 355 | pub fn iteratorFilterRange(ctx: *SuperComponents, comptime comp_type: type, start_idx: usize, end_idx: usize) SuperComponents.MaskedRangeIterator { 356 | //get an iterator for components attached to this entity within a specific range 357 | const world = @as(*World, @ptrCast(@alignCast(ctx.world))); 358 | const components = &world._components; 359 | return .{ .ctx = components, .filter_type = typeToId(comp_type), .index = start_idx, .start_index = start_idx, .end_index = end_idx, .world = world }; 360 | } 361 | 362 | pub fn iteratorFilterByEntity(ctx: *SuperComponents, entity: *Entity, comptime comp_type: type) SuperComponents.MaskedEntityIterator { 363 | //get an iterator for components attached to this entity 364 | const world = @as(*World, @ptrCast(@alignCast(ctx.world))); 365 | const components = &world._components; 366 | return .{ .ctx = components, .filter_type = typeToId(comp_type), .components_alive = ctx.alive, .entities_alive = world.entities.alive, .world = world, .entity = entity }; 367 | } 368 | }; 369 | 370 | pub const _Components = struct { 371 | world: ?*anyopaque = undefined, //Defeats cyclical reference checking 372 | len: u32, 373 | alive: u32, 374 | sparse: []Component, 375 | free_idx: u32 = 0, 376 | created: u32 = 0, 377 | entity_mask: [MAX_COMPONENTS]std.StaticBitSet(CHUNK_SIZE), //Owns at least one component of type 378 | chunk: usize, 379 | 380 | pub inline fn count(ctx: *_Components) u32 { 381 | return ctx.alive; 382 | } 383 | 384 | pub fn processComponentsSimd(ctx: *_Components, comptime comp_type: type, processor: fn (*comp_type) void) void { 385 | const vector_width = std.simd.suggestVectorLength(u32) orelse 4; 386 | var i: usize = 0; 387 | 388 | // Process components in SIMD batches 389 | while (i + vector_width <= ctx.alive) : (i += vector_width) { 390 | const rems = blk: { 391 | var result: @Vector(vector_width, u32) = undefined; 392 | var j: u32 = 0; 393 | while (j < vector_width) : (j += 1) { 394 | result[j] = @intCast(@rem(i + j, CHUNK_SIZE)); 395 | } 396 | break :blk result; 397 | }; 398 | 399 | // Process multiple components in parallel 400 | inline for (0..vector_width) |j| { 401 | const component = &ctx.sparse[@intCast(rems[j])]; 402 | if (component.alive and component.typeId == typeToId(comp_type)) { 403 | if (component.data) |data| { 404 | const typed_data = CastData(comp_type, data); 405 | processor(typed_data); 406 | } 407 | } 408 | } 409 | } 410 | 411 | // Handle remaining components 412 | while (i < ctx.alive) : (i += 1) { 413 | const rem = @rem(i, CHUNK_SIZE); 414 | const component = &ctx.sparse[rem]; 415 | if (component.alive and component.typeId == typeToId(comp_type)) { 416 | if (component.data) |data| { 417 | const typed_data = CastData(comp_type, data); 418 | processor(typed_data); 419 | } 420 | } 421 | } 422 | } 423 | 424 | pub fn processComponentsRangeSimd(ctx: *_Components, comptime comp_type: type, start_idx: usize, end_idx: usize, processor: fn (*comp_type) void) void { 425 | const vector_width = std.simd.suggestVectorLength(u32) orelse 4; 426 | var i: usize = start_idx; 427 | 428 | // Process components in SIMD batches within the range 429 | while (i + vector_width <= end_idx) : (i += vector_width) { 430 | const rems = blk: { 431 | var result: @Vector(vector_width, u32) = undefined; 432 | var j: u32 = 0; 433 | while (j < vector_width) : (j += 1) { 434 | result[j] = @intCast(@rem(i + j, CHUNK_SIZE)); 435 | } 436 | break :blk result; 437 | }; 438 | 439 | // Process multiple components in parallel 440 | inline for (0..vector_width) |j| { 441 | const component = &ctx.sparse[@intCast(rems[j])]; 442 | if (component.alive and component.typeId == typeToId(comp_type)) { 443 | if (component.data) |data| { 444 | const typed_data = CastData(comp_type, data); 445 | processor(typed_data); 446 | } 447 | } 448 | } 449 | } 450 | 451 | // Handle remaining components 452 | while (i < end_idx) : (i += 1) { 453 | const rem = @rem(i, CHUNK_SIZE); 454 | const component = &ctx.sparse[rem]; 455 | if (component.alive and component.typeId == typeToId(comp_type)) { 456 | if (component.data) |data| { 457 | const typed_data = CastData(comp_type, data); 458 | processor(typed_data); 459 | } 460 | } 461 | } 462 | } 463 | 464 | pub fn create(ctx: *_Components, comptime comp_type: type) !*Component { 465 | const world = @as(*World, @ptrCast(@alignCast(ctx.world))); 466 | 467 | if (ctx.alive >= CHUNK_SIZE) 468 | return error.NoFreeComponentSlots; 469 | 470 | // Reset free_idx if it's out of bounds 471 | if (ctx.free_idx >= CHUNK_SIZE) 472 | ctx.free_idx = 0; 473 | 474 | // Find a free slot 475 | const start_idx = ctx.free_idx; 476 | var wrapped = false; 477 | while (ctx.sparse[ctx.free_idx].alive) { 478 | ctx.free_idx += 1; 479 | if (ctx.free_idx >= CHUNK_SIZE) { 480 | if (wrapped) { 481 | return error.NoFreeComponentSlots; 482 | } 483 | ctx.free_idx = 0; 484 | wrapped = true; 485 | } 486 | if (ctx.free_idx == start_idx) { 487 | return error.NoFreeComponentSlots; 488 | } 489 | } 490 | 491 | // Initialize the component 492 | var component = &ctx.sparse[ctx.free_idx]; 493 | component.world = world; 494 | component.attached = false; 495 | component.magic = MAGIC; 496 | component.typeId = typeToId(comp_type); 497 | component.id = ctx.free_idx; 498 | component.alive = true; 499 | component.owners = std.StaticBitSet(CHUNK_SIZE).initEmpty(); 500 | component.type_node = .{ .data = component }; 501 | component.chunk = ctx.chunk; 502 | component.data = null; 503 | component.allocated = false; 504 | 505 | // Update chunk state 506 | ctx.free_idx += 1; 507 | ctx.created += 1; 508 | ctx.alive += 1; 509 | if (!wrapped) { 510 | ctx.len += 1; 511 | } 512 | 513 | if (typeToId(comp_type) >= MAX_COMPONENTS) 514 | return error.ComponentNotInContainer; 515 | 516 | return component; 517 | } 518 | 519 | pub fn create_c(ctx: *_Components, comp_type: c_type) !*Component { 520 | const world = @as(*World, @ptrCast(@alignCast(ctx.world))); 521 | 522 | if (ctx.alive >= CHUNK_SIZE) 523 | return error.NoFreeComponentSlots; 524 | 525 | // Reset free_idx if it's out of bounds 526 | if (ctx.free_idx >= CHUNK_SIZE) 527 | ctx.free_idx = 0; 528 | 529 | // Find a free slot 530 | const start_idx = ctx.free_idx; 531 | var wrapped = false; 532 | while (ctx.sparse[ctx.free_idx].alive) { 533 | ctx.free_idx += 1; 534 | if (ctx.free_idx >= CHUNK_SIZE) { 535 | if (wrapped) { 536 | return error.NoFreeComponentSlots; 537 | } 538 | ctx.free_idx = 0; 539 | wrapped = true; 540 | } 541 | if (ctx.free_idx == start_idx) { 542 | return error.NoFreeComponentSlots; 543 | } 544 | } 545 | 546 | // Initialize the component 547 | var component = &ctx.sparse[ctx.free_idx]; 548 | component.world = world; 549 | component.attached = false; 550 | component.magic = MAGIC; 551 | component.typeId = typeToIdC(comp_type); 552 | component.id = ctx.free_idx; 553 | component.alive = true; 554 | component.owners = std.StaticBitSet(CHUNK_SIZE).initEmpty(); 555 | component.type_node = .{ .data = component }; 556 | component.chunk = ctx.chunk; 557 | component.data = null; 558 | component.allocated = false; 559 | 560 | // Update chunk state 561 | ctx.free_idx += 1; 562 | ctx.created += 1; 563 | ctx.alive += 1; 564 | if (!wrapped) { 565 | ctx.len += 1; 566 | } 567 | 568 | if (typeToIdC(comp_type) >= MAX_COMPONENTS) 569 | return error.ComponentNotInContainer; 570 | 571 | return component; 572 | } 573 | }; 574 | 575 | //Global 576 | var types: [MAX_COMPONENTS]usize = undefined; 577 | var types_size: [MAX_COMPONENTS]usize = undefined; 578 | var types_align: [MAX_COMPONENTS]u8 = undefined; 579 | var type_idx: usize = 0; 580 | 581 | //TLS 582 | var entities_idx: usize = 0; 583 | var components_idx: usize = 0; 584 | 585 | pub const World = struct { 586 | //Superset of Entities and Systems 587 | entities: SuperEntities, 588 | components: SuperComponents, 589 | _entities: []Entities, 590 | _components: []_Components, 591 | entities_len: usize = 0, 592 | components_len: usize = 0, 593 | components_free_idx: usize = 0, 594 | entities_free_idx: usize = 0, 595 | 596 | systems: Systems, 597 | allocator: std.mem.Allocator, 598 | 599 | pub fn create() !*World { 600 | var world = allocator.create(World) catch unreachable; 601 | 602 | world.allocator = allocator; 603 | world.entities.world = world; 604 | world.components.world = world; 605 | 606 | world.entities_len = 1; 607 | world.components_len = 1; 608 | world.entities_free_idx = 0; 609 | world.components_free_idx = 0; 610 | world.components.alive = 0; 611 | world.entities.alive = 0; 612 | 613 | world._entities = try allocator.alloc(Entities, 1); 614 | world._entities[entities_idx].world = world; 615 | world._entities[entities_idx].len = 0; 616 | world._entities[entities_idx].alive = 0; 617 | world._entities[entities_idx].free_idx = 0; 618 | world._entities[entities_idx].sparse = try allocator.alloc(Entity, CHUNK_SIZE); 619 | 620 | world.systems = Systems{}; 621 | 622 | world._components = try allocator.alloc(_Components, 1); 623 | world._components[components_idx].world = world; 624 | world._components[components_idx].len = 0; 625 | world._components[components_idx].alive = 0; 626 | world._components[components_idx].free_idx = 0; 627 | world._components[components_idx].chunk = 0; 628 | world._components[components_idx].sparse = try allocator.alloc(Component, CHUNK_SIZE); 629 | 630 | var i: usize = 0; 631 | while (i < MAX_COMPONENTS) { 632 | world._entities[entities_idx].component_mask[i] = std.StaticBitSet(CHUNK_SIZE).initEmpty(); 633 | world._components[components_idx].entity_mask[i] = std.StaticBitSet(CHUNK_SIZE).initEmpty(); 634 | i += 1; 635 | } 636 | 637 | return world; 638 | } 639 | 640 | pub fn destroy(self: *World) void { 641 | var it = self.components.iterator(); 642 | while (it.next()) |component| 643 | component.destroy(); 644 | 645 | self.components.gc(); 646 | var i: usize = 0; 647 | while (i < self.components_len) : (i += 1) 648 | self.allocator.free(self._components[i].sparse); 649 | 650 | self.allocator.free(self._components); 651 | 652 | i = 0; 653 | while (i < self.entities_len) : (i += 1) 654 | self.allocator.free(self._entities[i].sparse); 655 | 656 | self.allocator.free(self._entities); 657 | self.allocator.destroy(self); 658 | } 659 | }; 660 | 661 | pub const Component = struct { 662 | chunk: usize, 663 | id: u32, 664 | data: ?*anyopaque, 665 | world: ?*anyopaque, 666 | owners: std.StaticBitSet(CHUNK_SIZE), 667 | attached: bool, 668 | typeId: ?u32 = undefined, 669 | allocated: bool = false, 670 | alive: bool = false, 671 | type_node: std.DoublyLinkedList(*Component).Node, 672 | magic: usize = MAGIC, 673 | 674 | pub inline fn is(self: *const Component, comp_type: anytype) bool { 675 | if (self.typeId == typeToId(comp_type)) { 676 | return true; 677 | } else { 678 | return false; 679 | } 680 | } 681 | 682 | pub inline fn set(component: *Component, comptime comp_type: type, members: anytype) !void { 683 | const field_ptr = @as(*comp_type, @ptrCast(@alignCast(component.data))); 684 | inline for (std.meta.fields(@TypeOf(members))) |sets| { 685 | @field(field_ptr, sets.name) = @field(members, sets.name); 686 | } 687 | } 688 | 689 | //Detaches from all entities 690 | pub inline fn detach(self: *Component) void { 691 | 692 | //TODO: Entities mask TBD 693 | self.attached = false; 694 | self.owners = std.StaticBitSet(CHUNK_SIZE).initEmpty(); 695 | } 696 | 697 | pub inline fn dealloc(self: *Component) void { 698 | const world = @as(*World, @ptrCast(@alignCast(self.world))); 699 | 700 | if (!self.alive and self.magic == MAGIC and self.allocated) { 701 | opaqueDestroy(world.allocator, self.data.?, types_size[@as(usize, @intCast(self.typeId.?))], types_align[@as(usize, @intCast(self.typeId.?))]); 702 | self.allocated = false; 703 | } 704 | } 705 | 706 | pub inline fn destroy(self: *Component) void { 707 | const world = @as(*World, @ptrCast(@alignCast(self.world))); 708 | 709 | //TODO: Destroy data? If allocated just hold to reuse. 710 | if (self.alive and self.magic == MAGIC) { 711 | self.attached = false; 712 | self.owners = std.StaticBitSet(CHUNK_SIZE).initEmpty(); 713 | self.alive = false; 714 | 715 | if (world._components[self.chunk].alive > 0) 716 | world._components[self.chunk].alive -= 1; 717 | 718 | world._components[self.chunk].free_idx = self.id; 719 | world.components_free_idx = self.chunk; 720 | 721 | if (world.components.alive > 0) 722 | world.components.alive -= 1; 723 | } 724 | } 725 | }; 726 | 727 | pub const Entity = struct { 728 | chunk: usize, 729 | id: u32, 730 | alive: bool, 731 | world: ?*anyopaque, 732 | allocated: bool = false, 733 | 734 | pub inline fn addComponent(ctx: *Entity, comp_val: anytype) !*Component { 735 | const world = @as(*World, @ptrCast(@alignCast(ctx.world))); 736 | const component = try world.components.create(@TypeOf(comp_val)); 737 | try ctx.attach(component, comp_val); 738 | return component; 739 | } 740 | 741 | pub inline fn getOneComponent(ctx: *Entity, comptime comp_type: type) ?*const Component { 742 | const world = @as(*World, @ptrCast(@alignCast(ctx.world))); 743 | var it = world.components.iteratorFilterByEntity(ctx, comp_type); 744 | const next = it.next(); 745 | return next; 746 | } 747 | 748 | pub fn attach(self: *Entity, component: *Component, comp_type: anytype) !void { 749 | const world = @as(*World, @ptrCast(@alignCast(component.world))); 750 | 751 | if (@sizeOf(@TypeOf(comp_type)) > 0) { 752 | var ref = @TypeOf(comp_type){}; 753 | ref = comp_type; 754 | if (!component.allocated) { 755 | const data = try world.allocator.create(@TypeOf(comp_type)); 756 | data.* = comp_type; 757 | const oref = @as(?*anyopaque, @ptrCast(data)); 758 | component.data = oref; 759 | } else { 760 | if (component.allocated and component.typeId == typeToId(@TypeOf(comp_type))) { 761 | const data = CastData(@TypeOf(comp_type), component.data); 762 | data.* = comp_type; 763 | } else { 764 | if (component.allocated and component.typeId != typeToId(@TypeOf(comp_type))) { 765 | opaqueDestroy(world.allocator, component.data, types_size[@as(usize, @intCast(typeToId(@TypeOf(comp_type))))], types_align[@as(usize, @intCast(typeToId(@TypeOf(comp_type))))]); 766 | const data = try world.allocator.create(@TypeOf(comp_type)); 767 | data.* = comp_type; 768 | const oref = @as(?*anyopaque, @ptrCast(data)); 769 | component.data = oref; 770 | } 771 | } 772 | } 773 | } 774 | component.attached = true; 775 | component.allocated = true; 776 | 777 | world._entities[self.chunk].component_mask[@as(usize, @intCast(component.typeId.?))].setValue(component.id, true); 778 | world._components[component.chunk].entity_mask[@as(usize, @intCast(component.typeId.?))].setValue(self.id, true); 779 | component.owners.setValue(self.id, true); 780 | } 781 | 782 | pub fn attach_c(self: *Entity, component: *Component, comp_type: *c_type) !void { 783 | const world = @as(*World, @ptrCast(@alignCast(component.world))); 784 | 785 | if (@sizeOf(@TypeOf(comp_type)) > 0) { 786 | var ref = @TypeOf(comp_type){}; 787 | ref = comp_type; 788 | if (!component.allocated) { 789 | const data = try world.allocator.create(@TypeOf(comp_type)); 790 | data.* = comp_type; 791 | const oref = @as(?*anyopaque, @ptrCast(data)); 792 | component.data = oref; 793 | } else { 794 | if (component.allocated and component.typeId == typeToId(@TypeOf(comp_type))) { 795 | const data = CastData(@TypeOf(comp_type), component.data); 796 | data.* = comp_type; 797 | } else { 798 | if (component.allocated and component.typeId != typeToId(@TypeOf(comp_type))) { 799 | opaqueDestroy(world.allocator, component.data, types_size[@as(usize, @intCast(typeToIdC(comp_type)))], types_align[@as(usize, @intCast(typeToIdC(comp_type)))]); 800 | const data = try world.allocator.create(@TypeOf(comp_type)); 801 | data.* = comp_type; 802 | const oref = @as(?*anyopaque, @ptrCast(data)); 803 | component.data = oref; 804 | } 805 | } 806 | } 807 | } 808 | component.attached = true; 809 | component.allocated = true; 810 | 811 | world._entities[self.chunk].component_mask[@as(usize, @intCast(component.typeId.?))].setValue(component.id, true); 812 | world._components[component.chunk].entity_mask[@as(usize, @intCast(component.typeId.?))].setValue(self.id, true); 813 | component.owners.setValue(self.id, true); 814 | } 815 | 816 | pub inline fn detach(self: *Entity, component: *Component) !void { 817 | var world = @as(*World, @ptrCast(@alignCast(self.world))); 818 | 819 | component.attached = false; 820 | component.owners.setValue(self.id, false); 821 | world._entities[self.chunk].component_mask[@as(usize, @intCast(component.typeId.?))].setValue(component.id, false); 822 | } 823 | 824 | pub inline fn destroy(self: *Entity) void { 825 | var world = @as(*World, @ptrCast(@alignCast(self.world))); 826 | 827 | self.alive = false; 828 | world._entities[self.chunk].alive -= 1; 829 | world._entities[self.chunk].free_idx = self.id; 830 | world.entities_free_idx = self.chunk; 831 | world.entities.alive -= 1; 832 | } 833 | 834 | pub inline fn set(self: *Entity, component: *Component, comptime comp_type: type, members: anytype) !void { 835 | var field_ptr = @as(*comp_type, @ptrCast(component.data)); 836 | inline for (std.meta.fields(@TypeOf(members))) |sets| { 837 | @field(field_ptr, sets.name) = @field(members, sets.name); 838 | } 839 | _ = self; 840 | } 841 | }; 842 | 843 | //Do not inline 844 | pub fn typeToId(comptime T: type) u32 { 845 | const longId = @as(usize, @intCast(@intFromPtr(&struct { 846 | var x: u8 = 0; 847 | }.x))); 848 | 849 | var found = false; 850 | var i: usize = 0; 851 | while (i < type_idx) : (i += 1) { 852 | if (types[i] == longId) { 853 | found = true; 854 | break; 855 | } 856 | } 857 | if (!found) { 858 | types[type_idx] = longId; 859 | types_size[type_idx] = @sizeOf(T); 860 | types_align[type_idx] = @alignOf(T); 861 | type_idx += 1; 862 | } 863 | return @as(u32, @intCast(i)); 864 | } 865 | 866 | pub fn typeToIdC(comp_type: c_type) u32 { 867 | const longId = comp_type.id; 868 | 869 | var found = false; 870 | var i: usize = 0; 871 | while (i < type_idx) : (i += 1) { 872 | if (types[i] == longId) { 873 | found = true; 874 | break; 875 | } 876 | } 877 | if (!found) { 878 | types[type_idx] = longId; 879 | types_size[type_idx] = comp_type.size; 880 | types_align[type_idx] = comp_type.alignof; 881 | type_idx += 1; 882 | } 883 | return @as(u32, @intCast(i)); 884 | } 885 | 886 | pub inline fn Cast(comptime T: type, component: ?*Component) *T { 887 | const field_ptr = @as(*T, @ptrCast(@alignCast(component.?.data))); 888 | return field_ptr; 889 | } 890 | 891 | pub inline fn CastData(comptime T: type, component: ?*anyopaque) *T { 892 | const field_ptr = @as(*T, @ptrCast(@alignCast(component))); 893 | return field_ptr; 894 | } 895 | 896 | pub const SuperEntities = struct { 897 | world: ?*anyopaque = undefined, //Defeats cyclical reference checking 898 | alive: usize, 899 | 900 | pub inline fn count(ctx: *SuperEntities) u32 { 901 | const world = @as(*World, @ptrCast(@alignCast(ctx.world))); 902 | 903 | var i: usize = 0; 904 | var total: u32 = 0; 905 | while (i < world.entities_len) : (i += 1) { 906 | total += world._entities[i].alive; 907 | } 908 | return total; 909 | } 910 | 911 | pub fn create(ctx: *SuperEntities) !*Entity { 912 | var world = @as(*World, @ptrCast(@alignCast(ctx.world))); 913 | 914 | defer ctx.alive += 1; 915 | 916 | if (world._entities[world.entities_free_idx].len < CHUNK_SIZE) { 917 | return try world._entities[world.entities_free_idx].create(); 918 | } else { //Create new chunk 919 | try ctx.expand(); 920 | return try world._entities[world.entities_free_idx].create(); 921 | } 922 | } 923 | 924 | pub fn expand(ctx: *SuperEntities) !void { 925 | var world = @as(*World, @ptrCast(@alignCast(ctx.world))); 926 | 927 | world._entities = try world.allocator.realloc(world._entities, world.entities_len + 1); 928 | world._entities[world.entities_len].world = world; 929 | world._entities[world.entities_len].len = 0; 930 | world._entities[world.entities_len].alive = 0; 931 | world._entities[world.entities_len].created = 0; 932 | world._entities[world.entities_len].free_idx = 0; 933 | world._entities[world.entities_len].sparse = try world.allocator.alloc(Entity, CHUNK_SIZE); 934 | 935 | var i: usize = 0; 936 | while (i < MAX_COMPONENTS) : (i += 1) { 937 | world._entities[world.entities_len].component_mask[i] = std.StaticBitSet(CHUNK_SIZE).initEmpty(); 938 | } 939 | 940 | world.entities_len += 1; 941 | world.entities_free_idx = world.entities_len - 1; 942 | entities_idx = world.entities_free_idx; 943 | } 944 | 945 | pub const Iterator = struct { 946 | ctx: *[]Entities, 947 | index: usize = 0, 948 | alive: usize = 0, 949 | 950 | pub inline fn next(it: *Iterator) ?*Entity { 951 | while (it.index < it.alive) : (it.index += 1) { 952 | const mod = it.index / CHUNK_SIZE; 953 | const rem = @rem(it.index, CHUNK_SIZE); 954 | if (it.ctx.*[mod].sparse[rem].alive) { 955 | const sparse_index = rem; 956 | it.index += 1; 957 | return &it.ctx.*[mod].sparse[sparse_index]; 958 | } 959 | } 960 | 961 | return null; 962 | } 963 | }; 964 | 965 | //TODO: Rewrite to use bitset iterator? 966 | pub const MaskedIterator = struct { 967 | ctx: *[]Entities, 968 | index: usize = 0, 969 | filter_type: u32, 970 | alive: usize = 0, 971 | world: *World, 972 | 973 | pub fn next(it: *MaskedIterator) ?*Entity { 974 | while (it.index < it.alive) : (it.index += 1) { 975 | const mod = it.index / CHUNK_SIZE; 976 | const rem = @rem(it.index, CHUNK_SIZE); 977 | if (it.world._components[mod].entity_mask[it.filter_type].isSet(rem)) { 978 | const sparse_index = rem; 979 | it.index += 1; 980 | return &it.ctx.*[mod].sparse[sparse_index]; 981 | } 982 | } 983 | 984 | return null; 985 | } 986 | }; 987 | 988 | pub inline fn iterator(ctx: *SuperEntities) SuperEntities.Iterator { 989 | const world = @as(*World, @ptrCast(@alignCast(ctx.world))); 990 | const entities = &world._entities; 991 | return .{ .ctx = entities, .alive = ctx.alive }; 992 | } 993 | 994 | pub fn iteratorFilter(ctx: *SuperEntities, comptime comp_type: type) SuperEntities.MaskedIterator { 995 | const world = @as(*World, @ptrCast(@alignCast(ctx.world))); 996 | const entities = &world._entities; 997 | 998 | //TODO: Go through each chunk 999 | //get an iterator for entities attached to this entity 1000 | return .{ .ctx = entities, .filter_type = typeToId(comp_type), .alive = world.components.alive, .world = world }; 1001 | } 1002 | }; 1003 | 1004 | const Entities = struct { 1005 | len: u32 = 0, 1006 | sparse: []Entity, 1007 | alive: u32 = 0, 1008 | free_idx: u32 = 0, 1009 | world: ?*anyopaque = undefined, //Defeats cyclical reference checking 1010 | created: u32 = 0, 1011 | component_mask: [MAX_COMPONENTS]std.StaticBitSet(CHUNK_SIZE), 1012 | 1013 | pub inline fn create(ctx: *Entities) !*Entity { 1014 | //most ECS cheat here and don't allocate memory until a component is assigned 1015 | 1016 | //find end of sparse array 1017 | var wrapped = false; 1018 | while (ctx.sparse[ctx.free_idx].alive == true) { 1019 | if (wrapped and ctx.free_idx > CHUNK_SIZE) 1020 | return error.NoFreeEntitySlots; 1021 | 1022 | ctx.free_idx = ctx.alive + 1; 1023 | if (ctx.free_idx > CHUNK_SIZE - 1) { 1024 | ctx.free_idx = 0; 1025 | wrapped = true; 1026 | } 1027 | } 1028 | 1029 | if (!wrapped) 1030 | ctx.len += 1; 1031 | 1032 | var entity = &ctx.sparse[ctx.free_idx]; 1033 | entity.id = ctx.free_idx; 1034 | entity.alive = true; 1035 | entity.world = ctx.world; 1036 | entity.chunk = entities_idx; 1037 | 1038 | ctx.alive += 1; 1039 | ctx.free_idx += 1; 1040 | 1041 | return entity; 1042 | } 1043 | 1044 | pub inline fn count(ctx: *Entities) u32 { 1045 | //count of all living entities 1046 | return ctx.alive; 1047 | } 1048 | }; 1049 | 1050 | pub const Systems = struct { 1051 | pub fn run(comptime f: anytype, args: anytype) !void { 1052 | const ret = @call(.auto, f, args); 1053 | if (@typeInfo(@TypeOf(ret)) == .error_union) try ret; 1054 | } 1055 | }; 1056 | 1057 | pub fn opaqueDestroy(self: std.mem.Allocator, ptr: anytype, sz: usize, alignment: u8) void { 1058 | const non_const_ptr = @as([*]u8, @ptrFromInt(@intFromPtr(ptr))); 1059 | self.rawFree(non_const_ptr[0..sz], .fromByteUnits(alignment), @returnAddress()); 1060 | } 1061 | --------------------------------------------------------------------------------