├── .devcontainer ├── Dockerfile ├── devcontainer.json └── postCreateCommand.sh ├── .gitignore ├── .gitmodules ├── .vscode ├── launch.json ├── tasks.json └── update_flecs.sh ├── LICENSE ├── README.md ├── build.zig ├── examples ├── benchmark.zig ├── entities │ ├── entities_basics.zig │ ├── entities_hierarchy.zig │ ├── entities_iterate_components.zig │ ├── entities_prefab.zig │ └── entities_relations.zig ├── filters.zig ├── generator.zig ├── queries.zig ├── queries │ ├── queries_basics.zig │ ├── queries_change_tracking.zig │ └── queries_hierarchies.zig ├── query_maker.zig ├── raw.zig ├── systems.zig ├── systems │ ├── systems_basics.zig │ ├── systems_delta_time.zig │ ├── systems_mutate_entity.zig │ └── systems_sync_point.zig ├── terms.zig └── tester.zig └── src ├── c.zig ├── c ├── flecs.c └── flecs.h ├── entity.zig ├── filter.zig ├── flecs.zig ├── iterator.zig ├── meta.zig ├── queries.zig ├── query.zig ├── query_builder.zig ├── table_iterator.zig ├── term.zig ├── term_info.zig ├── tests.zig ├── type.zig ├── type_store.zig ├── utils.zig └── world.zig /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/ubuntu/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Ubuntu version (use hirsuite or bionic on local arm64/Apple Silicon): hirsute, focal, bionic 4 | ARG VARIANT="hirsute" 5 | FROM mcr.microsoft.com/vscode/devcontainers/base:0-${VARIANT} 6 | 7 | # [Optional] Uncomment this section to install additional OS packages. 8 | # RUN apk add curl xz 9 | 10 | # RUN git submodule update --init 11 | 12 | # ARG ZIGVER="0.10.0-dev.717+90f2a8d9c" 13 | # RUN curl -o zig.tar.xz https://ziglang.org/builds/zig-linux-x86_64-0.10.0-dev.717+90f2a8d9c.tar.xz -O && \ 14 | # mkdir zig && tar xf zig.tar.xz -C zig --strip-components 1 && \ 15 | # rm zig.tar.xz && \ 16 | # mv zig .. 17 | 18 | # RUN git submodule update --init 19 | # RUN ../zig/zig build 20 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/ubuntu 3 | { 4 | "name": "Ubuntu", 5 | "build": { 6 | "dockerfile": "Dockerfile", 7 | // Update 'VARIANT' to pick an Ubuntu version: hirsute, focal, bionic 8 | // Use hirsute or bionic on local arm64/Apple Silicon. 9 | "args": { "VARIANT": "focal" } 10 | }, 11 | 12 | // Set *default* container specific settings.json values on container create. 13 | "settings": {}, 14 | 15 | 16 | // Add the IDs of extensions you want installed when the container is created. 17 | "extensions": [ 18 | "prime31.zig", 19 | "AugusteRame.zls-vscode" 20 | ], 21 | 22 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 23 | // "forwardPorts": [], 24 | 25 | // Use 'postCreateCommand' to run commands after the container is created. 26 | "postCreateCommand": "./.devcontainer/postCreateCommand.sh", 27 | 28 | // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 29 | "remoteUser": "vscode", 30 | "features": { 31 | "git": "latest" 32 | } 33 | } -------------------------------------------------------------------------------- /.devcontainer/postCreateCommand.sh: -------------------------------------------------------------------------------- 1 | cd /workspaces 2 | rm -rf zig 3 | rm -rf zls 4 | 5 | sudo apt-get install xz-utils 6 | 7 | curl -o zig.tar.xz https://ziglang.org/builds/zig-linux-x86_64-0.10.0-dev.717+90f2a8d9c.tar.xz -O 8 | mkdir zig && tar xf zig.tar.xz -C zig --strip-components 1 9 | rm zig.tar.xz 10 | 11 | git clone --recursive https://github.com/zigtools/zls.git 12 | cd zls 13 | git checkout f3aabd6b7ca424b6aa1be9ef8a215a842301b994 14 | /workspaces/zig/zig build -Drelease-safe 15 | # ./zig-out/bin/zls config 16 | 17 | 18 | echo 'export PATH="/workspaces/zig:$PATH"' >> ~/.bashrc 19 | echo 'export PATH="/workspaces/zls/zig-out/bin:$PATH"' >> ~/.bashrc 20 | source ~/.bashrc 21 | 22 | cd /workspaces/zig-flecs 23 | git submodule update --init 24 | ../zig/zig build -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | src/raw.zig 3 | src/raw_old.zig 4 | src/raw_new.zig 5 | /zig-cache 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Srekel/zig-flecs/8b404bd2e43e8ace800893f0a83079c15fd3f5aa/.gitmodules -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Last Target", 6 | "type": "lldb", 7 | "request": "launch", 8 | "program": "${workspaceFolder}/zig-cache/bin/${input:zigLastTarget}", 9 | "args": [], 10 | } 11 | ], 12 | "inputs": [ 13 | { 14 | "id": "zigLastTarget", 15 | "type": "command", 16 | "command": "zig.build.getLastTargetOrPrompt" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "options": { 6 | "env": { 7 | "ZIG_SYSTEM_LINKER_HACK": "1" 8 | } 9 | }, 10 | "tasks": [ 11 | { 12 | "label": "Build Project", 13 | "type": "shell", 14 | "command": "zig build", 15 | "problemMatcher": [ 16 | "$gcc" 17 | ], 18 | }, 19 | { 20 | "label": "Build and Run Current Example", 21 | "type": "shell", 22 | "command": "zig build ${fileBasenameNoExtension}", 23 | "problemMatcher": [ 24 | "$gcc" 25 | ], 26 | "group": { 27 | "kind": "build", 28 | "isDefault": true 29 | }, 30 | "presentation": { 31 | "clear": true 32 | } 33 | }, 34 | { 35 | "label": "Build All Examples", 36 | "type": "shell", 37 | "command": "zig build all_examples", 38 | "problemMatcher": [ 39 | "$gcc" 40 | ], 41 | "group": { 42 | "kind": "build", 43 | "isDefault": true 44 | }, 45 | "presentation": { 46 | "clear": true 47 | } 48 | }, 49 | { 50 | "label": "Update Flecs", 51 | "type": "shell", 52 | "command": "zig build update_flecs", 53 | "problemMatcher": [ 54 | "$gcc" 55 | ], 56 | "group": { 57 | "kind": "build", 58 | "isDefault": true 59 | }, 60 | "presentation": { 61 | "clear": true 62 | } 63 | }, 64 | { 65 | "label": "Test Project", 66 | "type": "shell", 67 | "command": "zig build test", 68 | "problemMatcher": [ 69 | "$gcc" 70 | ], 71 | "group": { 72 | "kind": "build", 73 | "isDefault": true 74 | }, 75 | "presentation": { 76 | "clear": true 77 | } 78 | }, 79 | { 80 | "label": "Build and Run Project", 81 | "type": "shell", 82 | "command": "zig build run", 83 | "problemMatcher": [ 84 | "$gcc" 85 | ], 86 | "group": { 87 | "kind": "build", 88 | "isDefault": true 89 | }, 90 | "presentation": { 91 | "clear": true 92 | } 93 | }, 94 | { 95 | "label": "Build and Run Current File", 96 | "type": "shell", 97 | "command": "zig run ${file}", 98 | "problemMatcher": [ 99 | "$gcc" 100 | ], 101 | "presentation": { 102 | "clear": true 103 | }, 104 | "group": { 105 | "kind": "build", 106 | "isDefault": true 107 | } 108 | }, 109 | { 110 | "label": "Build and Run Tests in Current File", 111 | "type": "shell", 112 | "command": "zig test ${file}", 113 | "problemMatcher": [ 114 | "$gcc" 115 | ], 116 | "presentation": { 117 | "clear": true 118 | }, 119 | "group": { 120 | "kind": "build", 121 | "isDefault": true 122 | } 123 | }, 124 | ] 125 | } -------------------------------------------------------------------------------- /.vscode/update_flecs.sh: -------------------------------------------------------------------------------- 1 | echo "----- cd src/c" 2 | cd src/c 3 | 4 | echo "----- translate-c the old flecs.h file" 5 | zig translate-c -lc flecs.h > ../raw_old.zig 6 | 7 | echo "----- download flecs.h and flecs.c" 8 | curl -O https://raw.githubusercontent.com/SanderMertens/flecs/master/flecs.h 9 | curl -O https://raw.githubusercontent.com/SanderMertens/flecs/master/flecs.c 10 | 11 | echo "----- translate-c the new flecs.h file" 12 | zig translate-c -lc flecs.h > ../raw_new.zig 13 | 14 | echo "----- done" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Colton Franklin 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 | # zig-flecs 2 | 3 | A semi-thin wrapper around the wonderful ecs [flecs](https://github.com/SanderMertens/flecs) for the [zig](https://ziglang.org) language. 4 | 5 | Flecs version: 3.1.1 6 | 7 | ## Current Goals 8 | - Provide the same functionality as using the flecs C macros using comptime. 9 | - Provide matching zig examples for all C examples to ensure capability. 10 | - Provide a higher level workflow that eases use for creating and using systems, queries, filters and observers. 11 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | // copied and adjusted from zig-gamedev's build 4 | 5 | pub const Package = struct { 6 | flecs: *std.Build.Module, 7 | flecs_c_cpp: *std.Build.CompileStep, 8 | 9 | pub fn build( 10 | b: *std.Build, 11 | target: std.zig.CrossTarget, 12 | optimize: std.builtin.Mode, 13 | _: struct {}, 14 | ) Package { 15 | const flecs = b.createModule(.{ 16 | .source_file = .{ .path = thisDir() ++ "/src/flecs.zig" }, 17 | .dependencies = &.{}, 18 | }); 19 | 20 | const flecs_c_cpp = b.addStaticLibrary(.{ 21 | .name = "flecs", 22 | .target = target, 23 | .optimize = optimize, 24 | }); 25 | flecs_c_cpp.linkLibC(); 26 | flecs_c_cpp.addIncludePath(thisDir() ++ "/src/c/flecs"); 27 | flecs_c_cpp.addCSourceFile(thisDir() ++ "/src/c/flecs.c", &.{ 28 | "-fno-sanitize=undefined", 29 | "-DFLECS_NO_CPP", 30 | if (@import("builtin").mode == .Debug) "-DFLECS_SANITIZE" else "", 31 | }); 32 | 33 | if (flecs_c_cpp.target.isWindows()) { 34 | flecs_c_cpp.linkSystemLibraryName("ws2_32"); 35 | } 36 | 37 | return .{ 38 | .flecs = flecs, 39 | .flecs_c_cpp = flecs_c_cpp, 40 | }; 41 | } 42 | 43 | pub fn link(flecs_pkg: Package, exe: *std.Build.CompileStep) void { 44 | exe.addIncludePath(thisDir() ++ "/src/c"); 45 | exe.linkLibrary(flecs_pkg.flecs_c_cpp); 46 | } 47 | }; 48 | 49 | pub fn build(_: *std.Build) void {} 50 | 51 | pub fn buildTests( 52 | b: *std.Build, 53 | optimize: std.builtin.Mode, 54 | target: std.zig.CrossTarget, 55 | ) *std.Build.CompileStep { 56 | const tests = b.addTest(.{ 57 | .root_source_file = .{ .path = thisDir() ++ "/src/flecs.zig" }, 58 | .target = target, 59 | .optimize = optimize, 60 | }); 61 | return tests; 62 | } 63 | 64 | inline fn thisDir() []const u8 { 65 | return comptime std.fs.path.dirname(@src().file) orelse "."; 66 | } 67 | -------------------------------------------------------------------------------- /examples/benchmark.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs"); 3 | 4 | // toggle to false to compare the zigified version. Use release builds because the wrapper does a bunch of debug build validation! 5 | const raw_flecs = false; 6 | const total_entities: i32 = 1_000_000; 7 | 8 | pub const Velocity = struct { x: f32, y: f32, z: f64 = 0 }; 9 | pub const Position = struct { x: f32, y: f32 }; 10 | pub const Acceleration = struct { x: f32 = 1, y: f32 = 1 }; 11 | pub const Player = struct { id: u8 = 5 }; 12 | pub const Enemy = struct { id: u8 = 3 }; 13 | const MoveSystemData = struct { pos: *Position, vel: *Velocity }; 14 | 15 | pub fn main() !void { 16 | var world = flecs.World.init(); 17 | defer world.deinit(); 18 | 19 | world.registerComponents(.{ Position, Velocity }); 20 | createEntities(&world); 21 | 22 | if (raw_flecs) { 23 | std.debug.print("\niterate with raw flecs query\n", .{}); 24 | world.newSystem("Move", .on_update, "Position, Velocity", move); 25 | iterateEntities(world, 10); 26 | } else { 27 | std.debug.print("\niterate with zigified flecs\n", .{}); 28 | world.newRunSystem("MoveRun", .on_update, "Position, Velocity", moveRun); 29 | createEntities(&world); 30 | iterateEntities(world, 10); 31 | } 32 | } 33 | 34 | fn move(it: [*c]flecs.c.ecs_iter_t) callconv(.C) void { 35 | const positions = flecs.column(it, Position, 1); 36 | const velocities = flecs.column(it, Velocity, 2); 37 | 38 | var i: usize = 0; 39 | while (i < it.*.count) : (i += 1) { 40 | positions[i].x += velocities[i].x; 41 | positions[i].y += velocities[i].y; 42 | } 43 | } 44 | 45 | fn moveRun(it: [*c]flecs.c.ecs_iter_t) callconv(.C) void { 46 | var iter = flecs.Iterator(MoveSystemData).init(it, flecs.c.ecs_iter_next); 47 | while (iter.next()) |e| { 48 | e.pos.x += e.vel.x; 49 | e.pos.y += e.vel.y; 50 | } 51 | } 52 | 53 | fn createEntities(world: *flecs.World) void { 54 | var timer = std.time.Timer.start() catch unreachable; 55 | 56 | var i: usize = 0; 57 | while (i < total_entities) : (i += 1) { 58 | const e = world.newEntity(); 59 | e.set(&Position{ .x = 100, .y = 100 }); 60 | e.set(&Velocity{ .x = 5, .y = 5 }); 61 | if (i % 3 == 0) e.set(Acceleration{}); 62 | if (i % 7 == 0) e.set(Player{}); 63 | if (i % 9 == 0) e.set(Enemy{}); 64 | } 65 | 66 | var end = timer.lap(); 67 | std.debug.print("create {d} entities: \t{d}\n", .{ total_entities, @floatFromInt(f64, end) / 1000000000 }); 68 | } 69 | 70 | fn iterateEntities(world: flecs.World, times: usize) void { 71 | var i = times; 72 | while (i > 0) : (i -= 1) { 73 | var timer = std.time.Timer.start() catch unreachable; 74 | world.progress(0); 75 | 76 | var end = timer.lap(); 77 | std.debug.print("iterate entities: \t\t{d}\n", .{@floatFromInt(f64, end) / 1000000000}); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /examples/entities/entities_basics.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs"); 3 | const game = @import("game"); 4 | 5 | pub fn system() flecs.EcsSystemDesc { 6 | var desc = std.mem.zeroes(flecs.EcsSystemDesc); 7 | desc.query.filter.terms[0] = std.mem.zeroInit(flecs.EcsTerm, .{ .id = flecs.ecs_id(Position) }); 8 | desc.query.filter.terms[1] = std.mem.zeroInit(flecs.EcsTerm, .{ .id = flecs.ecs_id(Velocity), .oper = flecs.EcsOperKind.ecs_optional }); 9 | desc.query.filter.terms[2] = std.mem.zeroInit(flecs.EcsTerm, .{ .id = flecs.ecs_pair(Likes, Apples)}); 10 | desc.run = run; 11 | return desc; 12 | } 13 | 14 | pub fn run(it: *flecs.EcsIter) callconv(.C) void { 15 | while (flecs.ecs_iter_next(it)) { 16 | var i: usize = 0; 17 | while (i < it.count) : (i += 1) { 18 | const name = flecs.ecs_get_name(it.world.?, it.entities[i]); 19 | if (flecs.ecs_field(it, Position, 1)) |positions| { 20 | if (name != null) { 21 | std.log.debug("{s}'s position: {any}", .{ name, positions[i] }); 22 | } 23 | 24 | positions[i].x += 5.0; 25 | positions[i].y += 5.0; 26 | } 27 | 28 | if (flecs.ecs_field(it, Velocity, 2)) |velocities| { 29 | if (name != null) { 30 | std.log.debug("{s}'s velocity: {any}", .{ name, velocities[i] }); 31 | } 32 | 33 | } 34 | 35 | if (flecs.ecs_field(it, Likes, 3)) |likes| { 36 | if (name != null) { 37 | std.log.debug("{s}'s likes apples how much? {d}!", .{ name, likes[i].amount }); 38 | } 39 | 40 | } 41 | } 42 | } 43 | } 44 | 45 | const Position = struct { x: f32, y: f32 }; 46 | const Walking = struct {}; 47 | const Velocity = struct { x: f32, y: f32 }; 48 | 49 | const Direction = enum { n, s, e, w }; 50 | 51 | const Has = struct {}; 52 | const Apples = struct { count: i32 }; 53 | const Eats = struct { count: i32 }; 54 | const Likes = struct { amount: i32, t: f32 = 0.0 }; 55 | 56 | pub fn main() !void { 57 | var world = flecs.ecs_init().?; 58 | 59 | flecs.ecs_component(world, Position); 60 | flecs.ecs_component(world, Walking); 61 | flecs.ecs_component(world, Velocity); 62 | flecs.ecs_component(world, Has); 63 | flecs.ecs_component(world, Apples); 64 | flecs.ecs_component(world, Eats); 65 | flecs.ecs_component(world, Likes); 66 | flecs.ecs_component(world, Direction); 67 | 68 | // Create an entity with name Bob 69 | const bob = flecs.ecs_new_entity(world, "Bob"); 70 | const jim = flecs.ecs_new_entity(world, "Jim"); 71 | 72 | // The set operation finds or creates a component, and sets it. 73 | flecs.ecs_set(world, bob, &Position{ .x = 10, .y = 20 }); 74 | flecs.ecs_set(world, bob, &Velocity{ .x = 1, .y = 2 }); 75 | 76 | flecs.ecs_set(world, jim, &Position{ .x = 10, .y = 20 }); 77 | flecs.ecs_set(world, jim, &Velocity{ .x = 1, .y = 2 }); 78 | 79 | // The add operation adds a component without setting a value. This is 80 | // useful for tags, or when adding a component with its default value. 81 | //flecs.ecs_add(world, bob, Walking); 82 | 83 | flecs.ecs_add(world, bob, Direction.e); 84 | 85 | if (flecs.ecs_get(world, bob, Direction)) |direction| { 86 | std.log.debug("bob's direction: {any}", .{ direction }); 87 | } 88 | 89 | flecs.ecs_add(world, bob, Direction.s); 90 | 91 | if (flecs.ecs_get(world, bob, Direction)) |direction| { 92 | std.log.debug("bob's direction: {any}", .{ direction }); 93 | } 94 | // Get the value for the Position component 95 | if (flecs.ecs_get(world, bob, Position)) |position| { 96 | std.log.debug("position: {any}", .{position}); 97 | } 98 | 99 | // Overwrite the value of the Position component 100 | flecs.ecs_set(world, bob, &Position{ .x = 20.0, .y = 30.0 }); 101 | 102 | if (flecs.ecs_get(world, bob, Position)) |position| { 103 | std.log.debug("position: {any}", .{position}); 104 | } 105 | 106 | const person = flecs.ecs_new_prefab(world, "Person"); 107 | flecs.ecs_add(world, person, Position); 108 | flecs.ecs_override(world, person, Position); 109 | flecs.ecs_set(world, person, &Velocity{ .x = 5, .y = 5}); 110 | flecs.ecs_add(world, person, Walking); 111 | 112 | // Create another named entity 113 | const alice = flecs.ecs_new_entity(world, "Alice"); 114 | flecs.ecs_add_pair(world, alice, flecs.Constants.EcsIsA, person); 115 | 116 | flecs.ecs_set_pair_second(world, alice, Has, &Apples{ .count = 5 }); 117 | 118 | if (flecs.ecs_get_pair_second(world, alice, Has, Apples)) |apples| { 119 | std.log.debug("Alice has {d} apples!", .{apples.count}); 120 | } 121 | 122 | flecs.ecs_set_pair(world, alice, &Eats{ 123 | .count = 2, 124 | }, Apples); 125 | 126 | if (flecs.ecs_get_pair(world, alice, Eats, Apples)) |eats| { 127 | std.log.debug("Alice eats {d} apples!", .{eats.count}); 128 | } 129 | 130 | flecs.ecs_set_pair(world, alice, Likes{ .amount = 10 }, bob); 131 | flecs.ecs_set_pair_second(world, alice, Likes, &Apples{ .count = 4 }); 132 | 133 | if (flecs.ecs_get_pair(world, alice, Likes, bob)) |b| { 134 | std.log.debug("How much does Alice like bob? {d}", .{b.amount}); 135 | } 136 | 137 | if (flecs.ecs_get_pair(world, alice, Likes, flecs.Constants.EcsWildcard)) |likes| { 138 | std.log.debug("Alice likes someone how much? {d}", .{likes.amount}); 139 | } 140 | 141 | const entities = flecs.ecs_bulk_new(world, Apples, 10); 142 | 143 | for (entities) |entity, i| { 144 | if (flecs.ecs_get(world, entity, Apples)) |apples| { 145 | std.log.debug("Bulk Entity {d}: {d} apples!", .{ i, apples.count }); 146 | } 147 | } 148 | 149 | var system_desc = system(); 150 | flecs.ecs_system(world, "Testing!", flecs.Constants.EcsOnUpdate, &system_desc); 151 | _ = flecs.ecs_progress(world, 0); 152 | 153 | //_ = flecs.ecs_fini(world); 154 | 155 | // TODO: add a getType method and wrapper for flecs types 156 | // Print all the components the entity has. This will output: 157 | // Position, Walking, (Identifier,Name) 158 | // const alice_type = alice.getType(); 159 | 160 | // // Remove tag 161 | // alice.remove(Walking); 162 | 163 | // // Iterate all entities with Position 164 | // var term = flecs.Term(Position).init(world); 165 | // defer term.deinit(); 166 | // var it = term.iterator(); 167 | 168 | // while (it.next()) |position| { 169 | // std.log.debug("{s}: {d}", .{ it.entity().getName(), position }); 170 | // } 171 | 172 | // world.deinit(); 173 | } 174 | -------------------------------------------------------------------------------- /examples/entities/entities_hierarchy.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs"); 3 | 4 | const Position = struct { x: f32, y: f32 }; 5 | const Star = struct {}; 6 | const Planet = struct {}; 7 | const Moon = struct {}; 8 | 9 | 10 | fn iterateTree(world: flecs.World, e: flecs.Entity, p_parent: Position) void { 11 | // Print hierarchical name of entity & the entity type 12 | // TODO: wrap flecs.c.ecs_get_fullpath? 13 | std.debug.print("-- path: {s}, type: {s}\n", .{ e.getFullpath(), world.getTypeStr(e.getType().type) }); 14 | 15 | // Get entity position 16 | if (e.get(Position)) |position| { 17 | // Calculate actual position 18 | const p_actual = .{ .x = position.x + p_parent.x, .y = position.y + p_parent.y }; 19 | std.log.debug("{s}: {d}", .{ e.getName(), p_actual }); 20 | 21 | // Iterate children recursively 22 | var term = flecs.Term({}).initWithPair(world, world.pair(flecs.c.EcsChildOf, e.id)); 23 | var iter = term.entityIterator(); 24 | while (iter.next()) |entity| { 25 | iterateTree(world, entity, p_actual); 26 | } 27 | } 28 | } 29 | 30 | pub fn main() !void { 31 | var world = flecs.World.init(); 32 | 33 | // Create a simple hierarchy. 34 | // Hierarchies use ECS relations and the builtin flecs::ChildOf relation to create entities as children of other entities. 35 | const sun = world.newEntityWithName("Sun"); 36 | sun.add(Star); 37 | sun.set(&Position{ .x = 1, .y = 1 }); 38 | 39 | const mercury = world.newEntityWithName("Mercury"); 40 | mercury.addPair(flecs.c.EcsChildOf, sun); 41 | mercury.add(Planet); 42 | mercury.set(&Position{ .x = 1, .y = 1 }); 43 | 44 | const venus = world.newEntityWithName("Venus"); 45 | venus.addPair(flecs.c.EcsChildOf, sun); 46 | venus.add(Planet); 47 | venus.set(&Position{ .x = 2, .y = 2 }); 48 | 49 | const earth = world.newEntityWithName("Earth"); 50 | earth.addPair(flecs.c.EcsChildOf, sun); 51 | earth.add(Planet); 52 | earth.set(&Position{ .x = 3, .y = 3 }); 53 | 54 | const moon = world.newEntityWithName("Moon"); 55 | moon.addPair(flecs.c.EcsChildOf, earth); 56 | moon.add(Moon); 57 | moon.set(&Position{ .x = 0.1, .y = 0.1 }); 58 | 59 | // Is the Moon a child of Earth? 60 | if (moon.hasPair(flecs.c.EcsChildOf, earth)) 61 | std.log.debug("Moon is a child of Earth!", .{}); 62 | 63 | // Do a depth-first walk of the tree 64 | iterateTree(world, sun, .{ .x = 0, .y = 0 }); 65 | 66 | const FilterCallback = struct { 67 | position: *const Position, 68 | }; 69 | 70 | std.log.debug("Iterate children of earth:", .{}); 71 | 72 | var filter = world.filterParent(FilterCallback, earth); 73 | var iter = filter.iterator(FilterCallback); 74 | 75 | while (iter.next()) |comps| { 76 | std.log.debug("{s} : {any}", .{ iter.entity().getName(), comps.position}); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /examples/entities/entities_iterate_components.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs"); 3 | 4 | const Position = struct { x: f32, y: f32 }; 5 | const Velocity = struct { x: f32, y: f32 }; 6 | const Human = struct {}; 7 | const Eats = struct {}; 8 | const Apples = struct {}; 9 | 10 | fn iterateComponents(world: flecs.World, entity: flecs.Entity) void { 11 | 12 | // First get the entity's type, which is a vector of (component) ids. 13 | const entity_type = entity.getType(); 14 | 15 | // 1. The easiest way to print the components is to use ecs_type_str 16 | std.log.debug("{s}", .{entity_type.asString()}); 17 | 18 | // 2. To print individual ids, iterate the type array with ecs_id_str 19 | for (entity_type.toArray()) |id| { 20 | std.log.debug("{d} : {s}", .{ id, flecs.c.ecs_id_str(world.world, id) }); 21 | } 22 | 23 | // 3. We can also inspect and print the ids in our own way. This is a 24 | // bit more complicated as we need to handle the edge cases of what can be 25 | // encoded in an id, but provides the most flexibility. 26 | 27 | // TODO 28 | } 29 | 30 | pub fn main() !void { 31 | var world = flecs.World.init(); 32 | 33 | // Create an entity which has all of the above 34 | const bob = world.newEntityWithName("Bob"); 35 | bob.set(&Position{ .x = 10, .y = 20 }); 36 | bob.set(&Velocity{ .x = 1, .y = 1 }); 37 | bob.add(Human); 38 | bob.addPair(Eats, Apples); 39 | 40 | // Iterate & components of Bob 41 | std.log.debug("Bob's components:", .{}); 42 | iterateComponents(world, bob); 43 | 44 | // We can use the same function to iterate the components of a component 45 | std.log.debug("Positions's components:", .{}); 46 | iterateComponents(world, .{ .world = world.world, .id = flecs.meta.componentId(world.world, Position) }); 47 | } 48 | -------------------------------------------------------------------------------- /examples/entities/entities_prefab.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs"); 3 | 4 | const Position = struct { x: f32, y: f32 }; 5 | const Attack = struct { value: f32 }; 6 | const Defense = struct { value: f32 }; 7 | const FreightCapacity = struct { value: f32 }; 8 | const ImpulseSpeed = struct { value: f32 }; 9 | 10 | pub fn main() !void { 11 | var world = flecs.World.init(); 12 | 13 | const is_a = flecs.Entity.init(world.world, flecs.c.EcsIsA); 14 | 15 | // Create a prefab hierarchy. Prefabs are entities that by default are 16 | // ignored by queries. 17 | 18 | const spaceship = world.newPrefab("Spaceship"); 19 | spaceship.set(&ImpulseSpeed{ .value = 50 }); 20 | spaceship.set(&Defense{ .value = 50 }); 21 | 22 | // By default components in an inheritance hierarchy are shared between 23 | // entities. The override function ensures that instances have a private 24 | // copy of the component. 25 | spaceship.setOverride(&Position{ .x = 0, .y = 0 }); 26 | 27 | const freighter = world.newPrefab("Freighter"); 28 | // This ensures the entity inherits all components from spaceship. 29 | freighter.addPair(is_a, spaceship); 30 | freighter.set(&FreightCapacity{ .value = 100 }); 31 | freighter.set(&Defense{ .value = 50 }); 32 | 33 | const mammoth_freighter = world.newPrefab("MammothFreighter"); 34 | mammoth_freighter.addPair(is_a, freighter); 35 | mammoth_freighter.set(&FreightCapacity{ .value = 500 }); 36 | mammoth_freighter.set(&Defense{ .value = 300 }); 37 | 38 | const frigate = world.newPrefab("Frigate"); 39 | frigate.addPair(is_a, spaceship); 40 | frigate.set(&Attack{ .value = 100 }); 41 | frigate.set(&Defense{ .value = 75 }); 42 | frigate.set(&ImpulseSpeed{ .value = 125 }); 43 | 44 | // Create an entity from a prefab. 45 | // The instance will have a private copy of the Position component, because 46 | // of the override in the spaceship entity. All other components are shared. 47 | const inst = world.newEntityWithName("my_mammoth_freighter"); 48 | inst.addPair(is_a, mammoth_freighter); 49 | 50 | // Inspect the type of the entity. This outputs: 51 | // Position,(Identifier,Name),(IsA,MammothFreighter) 52 | std.log.debug("{s}", .{inst.getType().asString()}); 53 | 54 | // Even though the instance doesn't have a private copy of ImpulseSpeed, we 55 | // can still get it using the regular API (outputs 50) 56 | if (inst.get(ImpulseSpeed)) |imp| { 57 | std.log.debug("Impulse speed: {d}", .{imp.value}); 58 | } 59 | 60 | // Prefab components can be iterated like regular components: 61 | var builder = flecs.QueryBuilder.init(world) 62 | .with(Position) 63 | .withReadonly(ImpulseSpeed); 64 | 65 | var filter = builder.buildFilter(); 66 | 67 | // To select components from a prefab, use SuperSet 68 | filter.filter.terms[1].subj.set.mask = flecs.c.EcsSuperSet; 69 | 70 | var it = filter.iterator(struct { position: *Position, ispeed: *const ImpulseSpeed }); 71 | while (it.next()) |components| { 72 | components.position.x += components.ispeed.value; 73 | std.log.debug("{s}: {d}", .{ it.entity().getName(), components.position }); 74 | } 75 | 76 | world.deinit(); 77 | } 78 | -------------------------------------------------------------------------------- /examples/entities/entities_relations.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs"); 3 | 4 | const Eats = struct {}; 5 | 6 | pub fn main() !void { 7 | var world = flecs.World.init(); 8 | 9 | // Entity used for Grows relation 10 | const grows = world.newEntityWithName("Grows"); 11 | 12 | // Relation objects 13 | const apples = world.newEntityWithName("Apples"); 14 | const pears = world.newEntityWithName("Pears"); 15 | 16 | // Create an entity with 3 relations. Relations are like regular components, 17 | // but combine two types/identifiers into an (relation, object) pair. 18 | const bob = world.newEntityWithName("Bob"); 19 | bob.addPair(Eats, apples); 20 | bob.addPair(Eats, pears); 21 | // Pairs can also be constructed from two entity ids 22 | bob.addPair(grows, pears); 23 | 24 | // Has can be used with relations as well 25 | if (bob.hasPair(Eats, apples)) 26 | std.log.debug("Bob eats apples.", .{}); 27 | 28 | if (bob.hasPair(grows, flecs.c.EcsWildcard)) 29 | std.log.debug("Bob grows food.", .{}); 30 | 31 | // Print the type of the entity. Should output: 32 | // (Identifier,Name),(Eats,Apples),(Eats,Pears),(Grows,Pears) 33 | std.log.debug("Bob's type: {s}", .{bob.getType().asString()}); 34 | } 35 | -------------------------------------------------------------------------------- /examples/filters.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs"); 3 | 4 | pub const Velocity = struct { x: f32, y: f32 }; 5 | pub const Position = struct { x: f32, y: f32 }; 6 | pub const Acceleration = struct { x: f32, y: f32 }; 7 | pub const Player = struct { id: u8 = 0 }; 8 | pub const Enemy = struct { id: u64 = 0 }; 9 | 10 | pub fn main() !void { 11 | var world = flecs.World.init(); 12 | defer world.deinit(); 13 | 14 | // bulk register required components since we use expressions for the systems 15 | world.registerComponents(.{ Position, Velocity, Acceleration, Player, Enemy }); 16 | 17 | const entity1 = world.newEntity(); 18 | entity1.setName("MyEntityYo"); 19 | 20 | const entity2 = world.newEntityWithName("MyEntity2"); 21 | std.debug.print("entity name: {s}\n", .{entity1.getName()}); 22 | 23 | const entity3 = world.newEntityWithName("HasAccel"); 24 | const entity4 = world.newEntityWithName("HasNoVel"); 25 | 26 | entity1.set(Position{ .x = 1, .y = 1 }); 27 | entity1.set(Velocity{ .x = 1.1, .y = 1.1 }); 28 | entity1.set(Enemy{ .id = 66 }); 29 | entity1.set(Acceleration{ .x = 1.2, .y = 1.2 }); 30 | 31 | entity2.set(Position{ .x = 2, .y = 2 }); 32 | entity2.set(Velocity{ .x = 1.2, .y = 1.2 }); 33 | entity2.set(Player{ .id = 3 }); 34 | 35 | entity3.set(Position{ .x = 3, .y = 3 }); 36 | entity3.set(Velocity{ .x = 1.2, .y = 1.2 }); 37 | entity3.set(Acceleration{ .x = 1.2, .y = 1.2 }); 38 | 39 | entity4.set(Position{ .x = 4, .y = 4 }); 40 | entity4.set(Acceleration{ .x = 1.2, .y = 1.2 }); 41 | 42 | var builder = flecs.QueryBuilder.init(world) 43 | .withReadonly(Position) 44 | .with(Velocity) 45 | .optional(Acceleration) 46 | .either(Player, Enemy) 47 | .orderBy(Position, orderBy); 48 | 49 | var filter = builder.buildFilter(); 50 | defer filter.deinit(); 51 | 52 | std.debug.print("\n\niterate with a FilterIterator\n", .{}); 53 | var filter_iter = filter.filterIterator(); 54 | while (filter_iter.next()) |_| { 55 | std.debug.print("pos: {d}, vel: {d}, accel: {d}, player: {d}\n", .{ filter_iter.getConst(Position), filter_iter.get(Velocity), filter_iter.getOpt(Acceleration), filter_iter.getOpt(Player) }); 56 | } 57 | 58 | std.debug.print("\n\niterate with a TableIterator\n", .{}); 59 | var table_iter = filter.tableIterator(struct { pos: *const Position, vel: *Velocity, acc: ?*Acceleration, player: ?*Player, enemy: ?*Enemy }); 60 | while (table_iter.next()) |it| { 61 | var i: usize = 0; 62 | while (i < it.count) : (i += 1) { 63 | const accel = if (it.data.acc) |acc| acc[i] else null; 64 | const player = if (it.data.player) |play| play[i] else null; 65 | const enemy = if (it.data.enemy) |en| en[i] else null; 66 | std.debug.print("i: {d}, pos: {d}, vel: {d}, acc: {d}, player: {d}, enemy: {d}\n", .{ i, it.data.pos[i], it.data.vel[i], accel, player, enemy }); 67 | } 68 | } 69 | 70 | std.debug.print("\n\niterate with an Iterator\n", .{}); 71 | var super_iter = filter.iterator(struct { pos: *const Position, vel: *Velocity, acc: ?*Acceleration, player: ?*Player, enemy: ?*Enemy }); 72 | while (super_iter.next()) |comps| { 73 | std.debug.print("comps: {any}\n", .{comps}); 74 | } 75 | 76 | std.debug.print("\n\niterate with an each function\n", .{}); 77 | filter.each(eachFilter); 78 | } 79 | 80 | fn eachFilter(e: struct { pos: *const Position, vel: *Velocity, acc: ?*Acceleration, player: ?*Player, enemy: ?*Enemy }) void { 81 | std.debug.print("comps: {any}\n", .{e}); 82 | } 83 | 84 | // only for queries/systems 85 | fn orderBy(e1: flecs.EntityId, c1: ?*const anyopaque, e2: flecs.EntityId, c2: ?*const anyopaque) callconv(.C) c_int { 86 | const p1 = @ptrCast(*const Position, @alignCast(@alignOf(Position), c1)); 87 | const p2 = @ptrCast(*const Position, @alignCast(@alignOf(Position), c2)); 88 | _ = e1; 89 | _ = e2; 90 | return if (p1.x < p2.x) 1 else -1; 91 | } 92 | -------------------------------------------------------------------------------- /examples/generator.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const c = @cImport(@cInclude("flecs.h")); 3 | 4 | pub fn main() !void { 5 | std.log.warn("flecs {any}", .{ c }); 6 | } 7 | -------------------------------------------------------------------------------- /examples/queries.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs"); 3 | 4 | pub const Velocity = struct { x: f32, y: f32 }; 5 | pub const Position = struct { x: f32, y: f32 }; 6 | pub const Acceleration = struct { x: f32, y: f32 }; 7 | pub const Player = struct { id: u8 = 0 }; 8 | pub const Enemy = struct { id: u64 = 0 }; 9 | 10 | pub fn main() !void { 11 | var world = flecs.World.init(); 12 | defer world.deinit(); 13 | 14 | // bulk register required components since we use expressions for the systems 15 | world.registerComponents(.{ Position, Velocity, Acceleration, Player, Enemy }); 16 | 17 | const entity1 = world.newEntityWithName("MyEntityYo"); 18 | const entity2 = world.newEntityWithName("MyEntity2"); 19 | const entity3 = world.newEntityWithName("HasAccel"); 20 | const entity4 = world.newEntityWithName("HasNoVel"); 21 | 22 | entity1.set(Position{ .x = 1, .y = 1 }); 23 | entity1.set(Velocity{ .x = 1.1, .y = 1.1 }); 24 | entity1.set(Enemy{ .id = 66 }); 25 | entity1.set(Acceleration{ .x = 1.2, .y = 1.2 }); 26 | 27 | entity2.set(Position{ .x = 2, .y = 2 }); 28 | entity2.set(Velocity{ .x = 1.2, .y = 1.2 }); 29 | entity2.set(Player{ .id = 3 }); 30 | 31 | entity3.set(Position{ .x = 3, .y = 3 }); 32 | entity3.set(Velocity{ .x = 1.2, .y = 1.2 }); 33 | entity3.set(Acceleration{ .x = 1.2, .y = 1.2 }); 34 | 35 | entity4.set(Position{ .x = 4, .y = 4 }); 36 | entity4.set(Acceleration{ .x = 1.2, .y = 1.2 }); 37 | 38 | var builder = flecs.QueryBuilder.init(world) 39 | .withReadonly(Position) 40 | .with(Velocity) 41 | .optional(Acceleration) 42 | .either(Player, Enemy) 43 | .orderBy(Position, orderBy); 44 | 45 | var query = builder.buildQuery(); 46 | defer query.deinit(); 47 | 48 | std.debug.print("\n\niterate with an Iterator\n", .{}); 49 | var entity_iter = query.iterator(struct { pos: *const Position, vel: *Velocity, acc: ?*Acceleration, player: ?*Player, enemy: ?*Enemy }); 50 | while (entity_iter.next()) |comps| { 51 | std.debug.print("comps: {any}\n", .{comps}); 52 | } 53 | 54 | std.debug.print("\n\niterate with a Query tableIterator\n", .{}); 55 | var table_iter = query.tableIterator(struct { pos: *const Position, vel: *Velocity, acc: ?*Acceleration, player: ?*Player, enemy: ?*Enemy }); 56 | while (table_iter.next()) |it| { 57 | var i: usize = 0; 58 | while (i < it.count) : (i += 1) { 59 | const accel = if (it.data.acc) |acc| acc[i] else null; 60 | const player = if (it.data.player) |play| play[i] else null; 61 | const enemy = if (it.data.enemy) |en| en[i] else null; 62 | std.debug.print("i: {d}, pos: {d}, vel: {d}, acc: {d}, player: {d}, enemy: {d}\n", .{ i, it.data.pos[i], it.data.vel[i], accel, player, enemy }); 63 | } 64 | } 65 | 66 | // add a component causing a new match. This will also trigger a sort. 67 | entity3.set(Player{ .id = 4 }); 68 | 69 | std.debug.print("\n\niterate with a Query each\n", .{}); 70 | query.each(eachQuery); 71 | 72 | std.debug.print("\n\niterate with a Query each\n", .{}); 73 | query.each(eachQuerySeperateParams); 74 | } 75 | 76 | fn eachQuery(e: struct { pos: *const Position, vel: *Velocity, acc: ?*Acceleration, player: ?*Player, enemy: ?*Enemy }) void { 77 | std.debug.print("comps: {any}\n", .{e}); 78 | } 79 | 80 | fn eachQuerySeperateParams(pos: *const Position, vel: *Velocity, acc: ?*Acceleration, player: ?*Player, enemy: ?*Enemy) void { 81 | std.debug.print("pos: {d}, vel: {d}, acc: {d}, player: {d}, enemy: {d}\n", .{ pos, vel, acc, player, enemy }); 82 | } 83 | 84 | fn orderBy(_: flecs.EntityId, c1: ?*const anyopaque, _: flecs.EntityId, c2: ?*const anyopaque) callconv(.C) c_int { 85 | const p1 = flecs.componentCast(Position, c1); 86 | const p2 = flecs.componentCast(Position, c2); 87 | 88 | return if (p1.x < p2.x) 1 else -1; 89 | } 90 | -------------------------------------------------------------------------------- /examples/queries/queries_basics.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs"); 3 | 4 | const Position = struct { x: f32, y: f32 }; 5 | const Velocity = struct { x: f32, y: f32 }; 6 | 7 | pub fn main() !void { 8 | var world = flecs.World.init(); 9 | 10 | // Create a query for Position, Velocity. Queries are the fastest way to 11 | // iterate entities as they cache results. 12 | const QueryCallback = struct { position: *Position, velocity: *const Velocity }; 13 | var query = world.query(QueryCallback); 14 | 15 | // Create a few test entities for a Position, Velocity query 16 | const e1 = world.newEntityWithName("e1"); 17 | e1.set(&Position{ .x = 10, .y = 20 }); 18 | e1.set(&Velocity{ .x = 1, .y = 2 }); 19 | 20 | const e2 = world.newEntityWithName("e2"); 21 | e2.set(&Position{ .x = 10, .y = 20 }); 22 | e2.set(&Velocity{ .x = 3, .y = 4 }); 23 | 24 | // This entity will not match as it does not have Position, Velocity 25 | const e3 = world.newEntityWithName("e3"); 26 | e3.set(&Position{ .x = 10, .y = 20 }); 27 | 28 | // Iterate entities matching the query 29 | var query_it = query.iterator(QueryCallback); 30 | 31 | while (query_it.next()) |components| { 32 | components.position.x += components.velocity.x; 33 | components.position.y += components.velocity.y; 34 | std.log.debug("{s} : {d}", .{ query_it.entity().getName(), components.position }); 35 | } 36 | 37 | // Filters are uncached queries. They are a bit slower to iterate but faster 38 | // to create & have lower overhead as they don't have to maintain a cache. 39 | const FilterCallback = struct { position: *Position, velocity: *const Velocity }; 40 | var filter = world.filter(FilterCallback); 41 | 42 | var filter_it = filter.iterator(FilterCallback); 43 | while (filter_it.next()) |components| { 44 | components.position.x += components.velocity.x; 45 | components.position.y += components.velocity.y; 46 | std.log.debug("{s} : {d}", .{ filter_it.entity().getName(), components.position }); 47 | } 48 | 49 | // Cleanup filter. Filters can allocate memory if the number of terms 50 | // exceeds their internal buffer, or when terms have names. In this case the 51 | // filter didn't allocate, so while fini isn't strictly necessary here, it's 52 | // still good practice to add it. 53 | filter.deinit(); 54 | query.deinit(); 55 | 56 | world.deinit(); 57 | } 58 | -------------------------------------------------------------------------------- /examples/queries/queries_change_tracking.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs"); 3 | const q = flecs.queries; 4 | 5 | // Queries have a builtin mechanism for tracking changes per matched table. This 6 | // is a cheap way of eliminating redundant work, as many entities can be skipped 7 | // with a single check. 8 | // 9 | // This example shows how to use change tracking in combination with a few other 10 | // techniques, like using prefabs to store a single dirty state for multiple 11 | // entities and instanced queries. 12 | 13 | const Position = struct { x: f32, y: f32 }; 14 | const Dirty = struct { value: bool }; 15 | 16 | pub fn main() !void { 17 | var world = flecs.World.init(); 18 | 19 | // Create a query that just reads a component. We'll use this query for 20 | // change tracking. Change tracking for a query is automatically enabled 21 | // when query::changed() is called. 22 | // Each query has its own private dirty state which is reset only when the 23 | // query is iterated. 24 | const QReadCallback = struct { position: *const Position, pub var instanced = true; }; 25 | var q_read = world.query(QReadCallback); 26 | 27 | // Create a query that writes the component based on a Dirty state. 28 | const QWriteCallback = struct { 29 | dirty: *const Dirty, 30 | position: *Position, 31 | 32 | pub var instanced = true; // required due to having shared components 33 | }; 34 | var q_write = world.query(QWriteCallback); 35 | 36 | // Create two prefabs with a Dirty component. We can use this to share a 37 | // single Dirty value for all entities in a table. 38 | const p1 = world.newPrefab("p1"); 39 | p1.set(&Dirty{ .value = false }); 40 | 41 | const p2 = world.newPrefab("p2"); 42 | p2.set(&Dirty{ .value = true }); 43 | 44 | // Create instances of p1 and p2. Because the entities have different 45 | // prefabs, they end up in different tables. 46 | const e1 = world.newEntityWithName("e1"); 47 | e1.isA(p1); 48 | e1.set(&Position{ .x = 10, .y = 20 }); 49 | 50 | const e2 = world.newEntityWithName("e2"); 51 | e2.isA(p1); 52 | e2.set(&Position{ .x = 30, .y = 40 }); 53 | 54 | const e3 = world.newEntityWithName("e3"); 55 | e3.isA(p2); 56 | e3.set(&Position{ .x = 50, .y = 60 }); 57 | 58 | const e4 = world.newEntityWithName("e4"); 59 | e4.isA(p2); 60 | e4.set(&Position{ .x = 70, .y = 80 }); 61 | 62 | // We can use the changed() function on the query to check if any of the 63 | // tables it is matched with has changed. Since this is the first time that 64 | // we check this and the query is matched with the tables we just created, 65 | // the function will return true. 66 | std.log.debug("q_read changed: {d}", .{q_read.changed(null)}); 67 | 68 | // The changed state will remain true until we have iterated each table. 69 | var q_read_it = q_read.tableIterator(QReadCallback); 70 | while (q_read_it.next()) |_| { 71 | // With the it.changed() function we can check if the table we're 72 | // currently iterating has changed since last iteration. 73 | // Because this is the first time the query is iterated, all tables 74 | // will show up as changed. 75 | std.log.debug("it.changed for table [{s}]: {d}", .{ q_read_it.tableType().asString(), flecs.c.ecs_query_changed(q_read.query, q_read_it.iter) }); 76 | } 77 | 78 | // Now that we have iterated all tables, the dirty state is reset. 79 | std.log.debug("q_read changed: {d}\n", .{q_read.changed(null)}); 80 | 81 | // Iterate the write query. Because the Position term is InOut (default) 82 | // iterating the query will write to the dirty state of iterated tables. 83 | var q_write_it = q_write.tableIterator(QWriteCallback); 84 | while (q_write_it.next()) |components| { 85 | std.log.debug("iterate table [{s}]", .{q_write_it.tableType().asString()}); 86 | 87 | // Because we enforced that Dirty is a shared component, we can check a single value for the entire table. 88 | if (!components.data.dirty.*.value) { 89 | q_write_it.skip(); 90 | std.log.debug("it.skip for table [{s}]", .{q_write_it.tableType().asString()}); 91 | continue; 92 | } 93 | 94 | // For all other tables the dirty state will be set. 95 | var i: usize = 0; 96 | while (i < components.count) : (i += 1) { 97 | components.data.position[i].x += 1; 98 | components.data.position[i].y += 1; 99 | } 100 | } 101 | 102 | // One of the tables has changed, so q_read.changed() will return true 103 | std.log.debug("\nq_read changed: {d}", .{q_read.changed(null)}); 104 | 105 | // When we iterate the read query, we'll see that one table has changed. 106 | q_read_it = q_read.tableIterator(QReadCallback); 107 | while (q_read_it.next()) |_| { 108 | std.log.debug("it.changed for table [{s}]: {d}", .{ q_read_it.tableType().asString(), q_read.changed(q_read_it.iter) }); 109 | } 110 | 111 | // Output: 112 | // q_read.changed(): 1 113 | // it.changed() for table [Position, (Identifier,Name), (IsA,p1)]: 1 114 | // it.changed() for table [Position, (Identifier,Name), (IsA,p2)]: 1 115 | // q_read.changed(): 0 116 | // 117 | // iterate table [Position, (Identifier,Name), (IsA,p1)] 118 | // it.skip() for table [Position, (Identifier,Name), (IsA,p1)] 119 | // iterate table [Position, (Identifier,Name), (IsA,p2)] 120 | // 121 | // q_read.changed(): 1 122 | // it.changed() for table [Position, (Identifier,Name), (IsA,p1)]: 0 123 | // it.changed() for table [Position, (Identifier,Name), (IsA,p2)]: 1 124 | 125 | q_read.deinit(); 126 | q_write.deinit(); 127 | world.deinit(); 128 | } 129 | -------------------------------------------------------------------------------- /examples/queries/queries_hierarchies.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs"); 3 | const q = flecs.queries; 4 | 5 | const Position = struct { x: f32, y: f32 }; 6 | const Local = struct {}; 7 | const World = struct {}; 8 | 9 | pub fn main() !void { 10 | var world = flecs.World.init(); 11 | 12 | world.registerComponents(.{ Local, World }); 13 | 14 | // Create a simple hierarchy. 15 | // Hierarchies use ECS relations and the builtin flecs::ChildOf relation to 16 | // create entities as children of other entities. 17 | const sun = world.newEntityWithName("Sun"); 18 | sun.addPair(Position, World); 19 | sun.setPair(Position, Local, .{ .x = 1, .y = 1 }); 20 | 21 | const mercury = world.newEntityWithName("Mercury"); 22 | mercury.addPair(flecs.c.EcsChildOf, sun); 23 | mercury.addPair(Position, World); 24 | mercury.setPair(Position, Local, .{ .x = 1, .y = 1 }); 25 | 26 | const venus = world.newEntityWithName("Venus"); 27 | venus.addPair(flecs.c.EcsChildOf, sun); 28 | venus.addPair(Position, World); 29 | venus.setPair(Position, Local, .{ .x = 2, .y = 2 }); 30 | 31 | const earth = world.newEntityWithName("Earth"); 32 | earth.addPair(flecs.c.EcsChildOf, sun); 33 | earth.addPair(Position, World); 34 | earth.setPair(Position, Local, .{ .x = 3, .y = 3 }); 35 | 36 | const moon = world.newEntityWithName("Moon"); 37 | moon.addPair(flecs.c.EcsChildOf, earth); 38 | moon.addPair(Position, World); 39 | moon.setPair(Position, Local, .{ .x = 0.1, .y = 0.1 }); 40 | 41 | // Create a hierarchical query to compute the global position from the local position and the parent position 42 | var query_t = std.mem.zeroes(flecs.c.ecs_query_desc_t); 43 | // Read from entity's Local position 44 | query_t.filter.terms[0] = std.mem.zeroInit(flecs.c.ecs_term_t, .{ .id = world.pair(Position, Local), .inout = .ecs_in }); 45 | // Write to entity's World position 46 | query_t.filter.terms[1] = std.mem.zeroInit(flecs.c.ecs_term_t, .{ .id = world.pair(Position, World), .inout = .ecs_out }); 47 | // Read from parent's World position 48 | query_t.filter.terms[2].id = world.pair(Position, World); 49 | query_t.filter.terms[2].inout = .ecs_in; 50 | query_t.filter.terms[2].oper = .ecs_optional; 51 | query_t.filter.terms[2].subj.set.mask = flecs.c.EcsParent | flecs.c.EcsCascade; 52 | 53 | const QCallback = struct { local: *const Position, world: *Position, parent: ?*const Position }; 54 | var query = flecs.Query.init(world, &query_t); 55 | defer query.deinit(); 56 | 57 | { 58 | // tester to show the same as above with struct query format 59 | const Funky = struct { 60 | pos_local: *const Position, 61 | pos_world: *Position, 62 | pos_parent: ?*const Position, 63 | 64 | pub var modifiers = .{ q.PairI(Position, Local, "pos_local"), q.WriteonlyI(q.Pair(Position, World), "pos_world"), q.MaskI(q.Pair(Position, World), flecs.c.EcsParent | flecs.c.EcsCascade, "pos_parent") }; 65 | }; 66 | var q2 = world.query(Funky); 67 | defer q2.deinit(); 68 | std.debug.print("\n------ {s}\n", .{query.asString()}); 69 | std.debug.print("------ {s}\n\n", .{q2.asString()}); 70 | } 71 | 72 | // Do the transform 73 | var it = query.iterator(QCallback); 74 | while (it.next()) |comps| { 75 | std.debug.print("-- path: {s}, type: {s}\n", .{ it.entity().getFullpath(), world.getTypeStr(it.entity().getType().type) }); 76 | comps.world.x = comps.local.x; 77 | comps.world.y = comps.local.y; 78 | 79 | if (comps.parent) |parent| { 80 | comps.world.x += parent.x; 81 | comps.world.y += parent.y; 82 | } 83 | } 84 | 85 | // Print ecs positions 86 | var term = flecs.Term(Position).initWithPair(world, world.pair(Position, World)); 87 | var iter = term.iterator(); 88 | while (iter.next()) |pos| { 89 | std.debug.print("{s}: {any}\n", .{ iter.entity().getName(), pos }); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /examples/query_maker.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs"); 3 | const q = flecs.queries; 4 | 5 | pub const ZeroSizedTag = struct {}; 6 | pub const Velocity = struct { x: f32, y: f32 }; 7 | pub const Position = struct { x: f32, y: f32 }; 8 | pub const Acceleration = struct { x: f32, y: f32 }; 9 | pub const Player = struct { id: u8 = 0 }; 10 | pub const Enemy = struct { id: u64 = 0 }; 11 | pub const PopTart = struct { id: u64 = 0 }; 12 | pub const Shit = struct { 13 | pub const Fuck = struct { id: u8 = 0 }; 14 | }; 15 | pub const FunkLocity = Wrapper(Velocity); 16 | 17 | pub fn Wrapper(comptime t: type) type { 18 | return struct { 19 | inner: t, 20 | }; 21 | } 22 | 23 | const TableEachCallbackType = struct { 24 | vel: *const Velocity, // In + And 25 | acc: ?*Acceleration, // needs metadata. could be Or or Optional. If no metadata can assume Optional. 26 | player: ?*Player, 27 | enemy: ?*Enemy, 28 | 29 | // allowed modifiers: Filter, Not, WriteOnly, Or (soon AndFrom, OrFrom, NotFrom) 30 | pub const modifiers = .{ q.Filter(PopTart), q.Filter(q.Or(Player, Enemy)), q.Writeonly(Acceleration), q.Not(FunkLocity) }; 31 | pub const run = system; 32 | pub const order_by = orderBy; 33 | pub const name = "SuperSystem"; 34 | }; 35 | 36 | const SystemCallbackType = struct { 37 | vel: *const Velocity, 38 | acc: ?*Acceleration, 39 | player: ?*Player, 40 | enemy: ?*Enemy, 41 | 42 | pub const run = system; 43 | pub const order_by = orderBy; 44 | pub const name = "SuperSystem"; 45 | pub const expr = "?PopTart"; 46 | }; 47 | 48 | pub fn main() !void { 49 | var world = flecs.World.init(); 50 | defer world.deinit(); 51 | 52 | world.registerComponents(.{ ZeroSizedTag }); 53 | 54 | var filter = world.filter(TableEachCallbackType); 55 | defer filter.deinit(); 56 | std.debug.print("----- {s}\n", .{filter.asString()}); 57 | 58 | var query = world.query(TableEachCallbackType); 59 | defer query.deinit(); 60 | std.debug.print("----- {s}\n", .{query.asString()}); 61 | 62 | world.system(SystemCallbackType, .on_update); 63 | 64 | const entity1 = world.newEntityWithName("MyEntityYo"); 65 | const entity2 = world.newEntityWithName("MyEntity2"); 66 | const entity3 = world.newEntityWithName("HasAccel"); 67 | const entity4 = world.newEntityWithName("HasNoVel"); 68 | 69 | entity1.set(Position{ .x = 0, .y = 0 }); 70 | entity1.set(Velocity{ .x = 1.1, .y = 1.1 }); 71 | entity1.set(Enemy{ .id = 66 }); 72 | entity1.set(FunkLocity{ .inner = .{ .x = 555, .y = 666 } }); 73 | 74 | entity2.set(Position{ .x = 2, .y = 2 }); 75 | entity2.set(Velocity{ .x = 1.2, .y = 1.2 }); 76 | entity2.set(Player{ .id = 3 }); 77 | 78 | entity3.set(Position{ .x = 3, .y = 3 }); 79 | entity3.set(Velocity{ .x = 1.2, .y = 1.2 }); 80 | entity3.set(Player{ .id = 4 }); 81 | 82 | entity4.set(Position{ .x = 4, .y = 4 }); 83 | entity4.set(Acceleration{ .x = 1.2, .y = 1.2 }); 84 | 85 | 86 | var it = filter.iterator(TableEachCallbackType); 87 | while (it.next()) |comps| { 88 | std.debug.print("comps: {any}\n", .{comps}); 89 | } 90 | 91 | it = query.iterator(TableEachCallbackType); 92 | while (it.next()) |comps| { 93 | std.debug.print("comps: {any}\n", .{comps}); 94 | } 95 | 96 | world.progress(0); 97 | } 98 | 99 | fn system(iter: *flecs.Iterator(SystemCallbackType)) void { 100 | while (iter.next()) |e| { 101 | std.debug.print("system: a: {d}, v: {d} - {s}\n", .{ e.acc, e.vel, iter.entity().getName() }); 102 | } 103 | } 104 | 105 | fn orderBy(_: flecs.EntityId, c1: *const Velocity, _: flecs.EntityId, c2: *const Velocity) c_int { 106 | if (c1.x == c2.x) return 0; 107 | return if (c1.x < c2.x) 1 else -1; 108 | } 109 | -------------------------------------------------------------------------------- /examples/raw.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs"); 3 | 4 | pub const Velocity = struct { x: f32, y: f32 }; 5 | pub const Position = struct { x: f32, y: f32 }; 6 | 7 | pub fn main() !void { 8 | var world = flecs.c.ecs_init().?; 9 | defer _ = flecs.c.ecs_fini(world); 10 | 11 | // the system below needs Position and Velocity to be defined before it can be created 12 | var comp_desc = std.mem.zeroInit(flecs.c.ecs_component_desc_t, .{ 13 | .entity = std.mem.zeroInit(flecs.c.ecs_entity_desc_t, .{ .name = @typeName(Position) }), 14 | .size = @sizeOf(Position), 15 | .alignment = @alignOf(Position), 16 | }); 17 | const pos_entity = flecs.c.ecs_component_init(world, &comp_desc); 18 | 19 | comp_desc = std.mem.zeroInit(flecs.c.ecs_component_desc_t, .{ 20 | .entity = std.mem.zeroInit(flecs.c.ecs_entity_desc_t, .{ .name = @typeName(Velocity) }), 21 | .size = @sizeOf(Velocity), 22 | .alignment = @alignOf(Velocity), 23 | }); 24 | const vel_entity = flecs.c.ecs_component_init(world, &comp_desc); 25 | 26 | // create a system 27 | var sys_desc = std.mem.zeroes(flecs.c.ecs_system_desc_t); 28 | sys_desc.entity.name = "MoveSystem"; 29 | sys_desc.entity.add[0] = flecs.c.EcsOnUpdate; 30 | sys_desc.query.filter.expr = "Position, Velocity"; 31 | sys_desc.callback = move; 32 | _ = flecs.c.ecs_system_init(world, &sys_desc); 33 | 34 | // create some entities 35 | const MyEntity = flecs.c.ecs_new_id(world); 36 | _ = flecs.c.ecs_set_name(world, MyEntity, "MyEntityYo"); 37 | 38 | const MyEntity2 = flecs.c.ecs_set_name(world, 0, "MyEntity2"); 39 | std.debug.print("{s}\n\n", .{flecs.c.ecs_get_name(world, MyEntity)}); 40 | 41 | _ = flecs.c.ecs_set_id(world, MyEntity, pos_entity, @sizeOf(Position), &Position{ .x = 0, .y = 0 }); 42 | _ = flecs.c.ecs_set_id(world, MyEntity, vel_entity, @sizeOf(Velocity), &Velocity{ .x = 1.1, .y = 1.1 }); 43 | 44 | _ = flecs.c.ecs_set_id(world, MyEntity2, pos_entity, @sizeOf(Position), &Position{ .x = 2, .y = 2 }); 45 | _ = flecs.c.ecs_set_id(world, MyEntity2, vel_entity, @sizeOf(Velocity), &Velocity{ .x = 1.2, .y = 1.2 }); 46 | 47 | std.debug.print("tick system twice\n", .{}); 48 | _ = flecs.c.ecs_progress(world, 0); 49 | _ = flecs.c.ecs_progress(world, 0); 50 | 51 | 52 | rawFilter(world); 53 | rawQuery(world); 54 | } 55 | 56 | fn rawFilter(world: *flecs.c.ecs_world_t) void { 57 | std.debug.print("\n\nmanually iterate with a filter\n", .{}); 58 | 59 | var filter_desc = std.mem.zeroInit(flecs.c.ecs_filter_desc_t, .{ .expr = "Position, Velocity" }); 60 | var filter: flecs.c.ecs_filter_t = undefined; 61 | _ = flecs.c.ecs_filter_init(world, &filter, &filter_desc); 62 | 63 | var it_filter = flecs.c.ecs_filter_iter(world, &filter); 64 | while (flecs.c.ecs_filter_next(&it_filter)) { 65 | const positions = flecs.column(&it_filter, Position, 1); 66 | const velocities = flecs.column(&it_filter, Velocity, 2); 67 | 68 | var i: usize = 0; 69 | while (i < it_filter.count) : (i += 1) { 70 | std.debug.print("iter: {d}, pos: {d}, vel: {d}\n", .{ i, positions[i], velocities[i] }); 71 | } 72 | } 73 | flecs.c.ecs_filter_fini(&filter); 74 | } 75 | 76 | fn rawQuery(world: *flecs.c.ecs_world_t) void { 77 | std.debug.print("\n\nmanually iterate with a query\n", .{}); 78 | 79 | var desc = std.mem.zeroes(flecs.c.ecs_query_desc_t); 80 | desc.filter.expr = "Position, Velocity"; 81 | var query = flecs.c.ecs_query_init(world, &desc).?; 82 | 83 | var it = flecs.c.ecs_query_iter(world, query); 84 | while (flecs.c.ecs_query_next(&it)) { 85 | const positions = flecs.column(&it, Position, 1); 86 | const velocities = flecs.column(&it, Velocity, 2); 87 | 88 | var i: usize = 0; 89 | while (i < it.count) : (i += 1) { 90 | std.debug.print("i: {d}, pos: {d}, vel: {d}\n", .{ i, positions[i], velocities[i] }); 91 | } 92 | } 93 | flecs.c.ecs_query_fini(query); 94 | } 95 | 96 | fn move(it: [*c]flecs.c.ecs_iter_t) callconv(.C) void { 97 | const positions = flecs.column(it, Position, 1); 98 | const velocities = flecs.column(it, Velocity, 2); 99 | const world = flecs.World{ .world = it.*.world.? }; 100 | 101 | var i: usize = 0; 102 | while (i < it.*.count) : (i += 1) { 103 | positions[i].x += velocities[i].x; 104 | positions[i].y += velocities[i].y; 105 | std.debug.print("p: {d}, v: {d} - {s}\n", .{ positions[i], velocities[i], world.getName(it.*.entities[i]) }); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /examples/systems.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs"); 3 | const q = flecs.queries; 4 | 5 | pub const Velocity = struct { x: f32, y: f32 }; 6 | pub const Position = struct { x: f32, y: f32 }; 7 | pub const Acceleration = struct { x: f32, y: f32 }; 8 | 9 | pub fn main() !void { 10 | var world = flecs.World.init(); 11 | defer world.deinit(); 12 | 13 | world.newWrappedRunSystem("MoveWrap", .on_update, ComponentData, moveWrapped); 14 | world.newWrappedRunSystem("Move2Wrap", .on_update, ComponentData, move2Wrapped); 15 | world.newWrappedRunSystem("AccelWrap", .on_update, AccelComponentData, accelWrapped); 16 | 17 | const entity1 = world.newEntity(); 18 | entity1.setName("MyEntityYo"); 19 | 20 | const entity2 = world.newEntityWithName("MyEntity2"); 21 | const entity3 = world.newEntityWithName("HasAccel"); 22 | const entity4 = world.newEntityWithName("HasNoVel"); 23 | 24 | entity1.set(Position{ .x = 0, .y = 0 }); 25 | entity1.set(Velocity{ .x = 0.1, .y = 0.1 }); 26 | 27 | entity2.set(Position{ .x = 2, .y = 2 }); 28 | entity2.set(Velocity{ .x = 0.2, .y = 0.2 }); 29 | 30 | entity3.set(Position{ .x = 3, .y = 3 }); 31 | entity3.set(Velocity{ .x = 0.3, .y = 0.3 }); 32 | entity3.set(Acceleration{ .x = 1.2, .y = 1.2 }); 33 | 34 | entity4.set(Position{ .x = 4, .y = 4 }); 35 | entity4.set(Acceleration{ .x = 1.2, .y = 1.2 }); 36 | 37 | std.debug.print("tick\n", .{}); 38 | world.progress(0); 39 | std.debug.print("tick\n", .{}); 40 | world.progress(0); 41 | 42 | // open the web explorer at https://www.flecs.dev/explorer/?remote=true 43 | _ = flecs.c.ecs_app_run(world.world, &std.mem.zeroInit(flecs.c.ecs_app_desc_t, .{ 44 | .target_fps = 1, 45 | .delta_time = 1, 46 | .threads = 8, 47 | .enable_rest = true, 48 | })); 49 | } 50 | 51 | const ComponentData = struct { pos: *Position, vel: *Velocity }; 52 | const AccelComponentData = struct { pos: *Position, vel: *Velocity, accel: *Acceleration }; 53 | 54 | fn moveWrapped(iter: *flecs.Iterator(ComponentData)) void { 55 | while (iter.next()) |e| { 56 | std.debug.print("Move wrapped: p: {d}, v: {d} - {s}\n", .{ e.pos, e.vel, iter.entity().getName() }); 57 | } 58 | } 59 | 60 | fn move2Wrapped(iter: *flecs.Iterator(ComponentData)) void { 61 | while (iter.next()) |e| { 62 | std.debug.print("Move2 wrapped: p: {d}, v: {d} - {s}\n", .{ e.pos, e.vel, iter.entity().getName() }); 63 | } 64 | } 65 | 66 | fn accelWrapped(iter: *flecs.Iterator(AccelComponentData)) void { 67 | while (iter.next()) |e| { 68 | std.debug.print("Accel wrapped: p: {d}, v: {d} - {s}\n", .{ e.pos, e.vel, iter.entity().getName() }); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/systems/systems_basics.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs"); 3 | 4 | const Position = struct { x: f32, y: f32 }; 5 | const Velocity = struct { x: f32, y: f32 }; 6 | 7 | const MoveCallback = struct { 8 | position: *Position, 9 | velocity: *const Velocity, 10 | 11 | pub const name = "Move"; 12 | pub const run = move; 13 | }; 14 | 15 | fn move(iter: *flecs.Iterator(MoveCallback)) void { 16 | while (iter.next()) |components| { 17 | components.position.x += components.velocity.x; 18 | components.position.y += components.velocity.y; 19 | std.log.debug("{s}: {d}", .{ iter.entity().getName(), components.position }); 20 | } 21 | } 22 | 23 | pub fn main() !void { 24 | var world = flecs.World.init(); 25 | 26 | // Create a system for Position, Velocity. Systems are like queries (see 27 | // queries) with a function that can be ran or scheduled (see pipeline). 28 | world.system(MoveCallback, .on_update); 29 | 30 | // Create a few test entities for a Position, Velocity query 31 | const e1 = world.newEntityWithName("e1"); 32 | e1.set(&Position{ .x = 10, .y = 20}); 33 | e1.set(&Velocity{ .x = 1, .y = 2}); 34 | 35 | const e2 = world.newEntityWithName("e2"); 36 | e2.set(&Position{ .x = 10, .y = 20}); 37 | e2.set(&Velocity{ .x = 3, .y = 4}); 38 | 39 | // This entity will not match as it does not have Position, Velocity 40 | const e3 = world.newEntityWithName("e3"); 41 | e3.set(&Position{ .x = 10, .y = 20}); 42 | 43 | world.progress(1); 44 | 45 | world.deinit(); 46 | } 47 | -------------------------------------------------------------------------------- /examples/systems/systems_delta_time.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs"); 3 | 4 | const EmptyCallback = struct { 5 | pub const name = "PrintDeltaTime"; 6 | pub const run = printDeltaTime; 7 | }; 8 | 9 | fn printDeltaTime(iter: *flecs.Iterator(EmptyCallback)) void { 10 | std.log.debug("delta_time: {d}", .{ iter.iter.delta_time }); 11 | } 12 | 13 | pub fn main() !void { 14 | var world = flecs.World.init(); 15 | 16 | // Create system that prints delta_time. This system doesn't query for any 17 | // components which means it won't match any entities, but will still be ran 18 | // once for each call to ecs_progress. 19 | world.system(EmptyCallback, .on_update); 20 | 21 | // Call progress with 0.0f for the delta_time parameter. This will cause 22 | // ecs_progress to measure the time passed since the last frame. The 23 | // delta_time of the first frame is a best guess (16ms). 24 | world.progress(0); 25 | 26 | // The following calls should print a delta_time of approximately 100ms 27 | std.os.nanosleep(0, 100 * 100 * 1000); 28 | world.progress(0); 29 | 30 | std.os.nanosleep(0, 100 * 100 * 1000); 31 | world.progress(0); 32 | 33 | world.deinit(); 34 | } 35 | -------------------------------------------------------------------------------- /examples/systems/systems_mutate_entity.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs"); 3 | const q = flecs.queries; 4 | 5 | const Timeout = struct { 6 | value: f32, 7 | }; 8 | 9 | const ExpireCallback = struct { 10 | timeout: *Timeout, 11 | pub const name = "Expire"; 12 | pub const run = expire; 13 | }; 14 | 15 | // System that deletes an entity after a timeout expires 16 | fn expire(it: *flecs.Iterator(ExpireCallback)) void { 17 | while (it.next()) |components| { 18 | components.timeout.value -= it.iter.delta_time; 19 | if (components.timeout.value <= 0) { 20 | // When deleting the entity, use the world provided by the iterator. 21 | 22 | // To make sure that the storage doesn't change while a system is 23 | // iterating entities, and multiple threads can safely access the 24 | // data, mutations (like a delete) are added to a command queue and 25 | // executed when it's safe to do so. 26 | 27 | // A system should not use the world pointer that is provided by the 28 | // ecs_init function, as this will throw an error that the world is 29 | // in readonly mode (try replacing it->world with it->real_world). 30 | std.log.debug("Expire: {s} deleted!", .{it.entity().getName()}); 31 | it.entity().delete(); 32 | } 33 | } 34 | } 35 | 36 | const PrintExpireCallback = struct { 37 | timeout: *const Timeout, 38 | 39 | pub const name = "PrintExpire"; 40 | pub const run = printExpire; 41 | }; 42 | 43 | // System that prints remaining expiry time 44 | fn printExpire(it: *flecs.Iterator(PrintExpireCallback)) void { 45 | while (it.next()) |components| { 46 | std.log.debug("PrintExpire: {s} has {d} seconds left", .{ it.entity().getName(), components.timeout.value }); 47 | } 48 | } 49 | 50 | const ObserverCallback = struct { 51 | timeout: *const Timeout, 52 | 53 | pub const name = "Expired"; 54 | pub const run = expired; 55 | }; 56 | 57 | // Observer that triggers when the component is actually removed 58 | fn expired(it: *flecs.Iterator(ObserverCallback)) void { 59 | while (it.next()) |_| { 60 | std.log.debug("Expired: {s} actually deleted", .{it.entity().getName()}); 61 | } 62 | } 63 | 64 | pub fn main() !void { 65 | var world = flecs.World.init(); 66 | 67 | world.system(ExpireCallback, .on_update); 68 | world.system(PrintExpireCallback, .on_update); 69 | world.observer(ObserverCallback, .on_remove); 70 | 71 | const e = world.newEntityWithName("MyEntity"); 72 | e.set(&Timeout{ .value = 3 }); 73 | 74 | world.setTargetFps(1); 75 | 76 | var progress: bool = true; 77 | while (progress) { 78 | world.progress(0); 79 | 80 | if (!e.isAlive()) break; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /examples/systems/systems_sync_point.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs"); 3 | const q = flecs.queries; 4 | 5 | const Position = struct { x: f32, y: f32 }; 6 | const Velocity = struct { x: f32, y: f32 }; 7 | 8 | const SetVelocityCallback = struct { 9 | pub const name = "SetVelocity"; 10 | pub const run = setVelocity; 11 | pub const modifiers = .{ q.Filter(Position), q.DontMatch(q.Writeonly(Velocity)) }; 12 | }; 13 | 14 | const MoveCallback = struct { 15 | position: *Position, 16 | velocity: *const Velocity, 17 | 18 | pub const name = "Move"; 19 | pub const run = move; 20 | }; 21 | 22 | const PrintCallback = struct { 23 | position: *const Position, 24 | 25 | pub const name = "PrintPosition"; 26 | pub const run = print; 27 | }; 28 | 29 | fn setVelocity(iter: *flecs.Iterator(SetVelocityCallback)) void { 30 | while (iter.next()) |_| { 31 | iter.entity().set(&Velocity{ .x = 1, .y = 2 }); 32 | } 33 | } 34 | 35 | fn move(iter: *flecs.Iterator(MoveCallback)) void { 36 | while (iter.next()) |components| { 37 | components.position.x += components.velocity.x; 38 | components.position.y += components.velocity.y; 39 | } 40 | } 41 | 42 | fn print(iter: *flecs.Iterator(PrintCallback)) void { 43 | while (iter.next()) |components| { 44 | std.log.debug("{s}: {d}", .{ iter.entity().getName(), components.position }); 45 | } 46 | } 47 | 48 | pub fn main() !void { 49 | var world = flecs.World.init(); 50 | 51 | world.registerComponents(.{ Position, Velocity }); 52 | 53 | // System that sets velocity using set for entities with Position. 54 | // While systems are progressing, operations like set are deferred until 55 | // it is safe to merge. By default this merge happens at the end of the 56 | // frame, but we can annotate systems to give the scheduler more information 57 | // about what it's doing, which allows it to insert sync points earlier. 58 | // 59 | // The parentheses after Velocity indicate that the component is not used 60 | // to match entities, while [out] indicates that it is written. A subsequent 61 | // system that accesses the component will cause the scheduler to insert a 62 | // sync point. 63 | // 64 | // Note that sync points are never necessary/inserted for systems that write 65 | // components provided by their signature, as these writes directly happen 66 | // in the ECS storage and are never deferred. 67 | // 68 | // The [filter] annotation for Position tells the scheduler that while we 69 | // want to match entities with Position, we're not interested in reading or 70 | // writing the component value. 71 | 72 | world.system(SetVelocityCallback, .on_update); 73 | 74 | // This system reads Velocity, which causes the insertion of a sync point. 75 | world.system(MoveCallback, .on_update); 76 | 77 | // Print resulting Position 78 | world.system(PrintCallback, .post_update); 79 | 80 | // Create a few test entities for a Position, Velocity query 81 | const e1 = world.newEntityWithName("e1"); 82 | e1.set(&Position{ .x = 10, .y = 20 }); 83 | e1.set(&Velocity{ .x = 1, .y = 2 }); 84 | 85 | const e2 = world.newEntityWithName("e2"); 86 | e2.set(&Position{ .x = 10, .y = 20 }); 87 | e2.set(&Velocity{ .x = 3, .y = 4 }); 88 | 89 | // Run systems. Debug logging enables us to see the generated schedule 90 | _ = flecs.c.ecs_log_set_level(1); 91 | world.progress(0); 92 | _ = flecs.c.ecs_log_set_level(-1); 93 | 94 | // Output: 95 | // info: pipeline rebuild: 96 | // info: | schedule: threading: 0, staging: 1: 97 | // info: | | system SetVelocity 98 | // info: | | merge 99 | // info: | schedule: threading: 0, staging: 1: 100 | // info: | | system Move 101 | // info: | | system PrintPosition 102 | // info: | | merge 103 | // e1: {11.000000, 22.000000} 104 | // e2: {11.000000, 22.000000} 105 | // 106 | // The "merge" lines indicate sync points. 107 | // 108 | // Removing the '[out] Velocity()' annotation from the system will remove 109 | // the first sync point from the schedule. 110 | // 111 | // To create the same system with ecs_system_init, do: 112 | // ecs_system_init(ecs, &(ecs_system_desc_t) { 113 | // .query.filter.terms = { 114 | // { 115 | // .id = ecs_id(Position), 116 | // .inout = .ecs_in_outNone 117 | // }, 118 | // { 119 | // .id = ecs_id(Velocity), 120 | // .inout = EcsOut, 121 | // .subj.set.mask = EcsNothing 122 | // } 123 | // }, 124 | // .entity = { 125 | // .name = "SetVelocity", 126 | // .add = {EcsOnUpdate} 127 | // }, 128 | // .callback = SetVelocity 129 | // }); 130 | 131 | world.deinit(); 132 | } 133 | -------------------------------------------------------------------------------- /examples/terms.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs"); 3 | 4 | pub const Position = struct { x: f32, y: f32 }; 5 | 6 | pub fn main() !void { 7 | var world = flecs.World.init(); 8 | defer world.deinit(); 9 | 10 | world.registerComponents(.{ Position }); 11 | 12 | const entity1 = world.newEntityWithName("Entity1"); 13 | const entity2 = world.newEntityWithName("Entity2"); 14 | const entity3 = world.newEntityWithName("Entity3"); 15 | const entity4 = world.newEntityWithName("Entity4"); 16 | 17 | entity1.set(Position{ .x = 0, .y = 0 }); 18 | entity2.set(Position{ .x = 2, .y = 2 }); 19 | entity3.set(Position{ .x = 3, .y = 3 }); 20 | entity4.set(Position{ .x = 4, .y = 4 }); 21 | 22 | std.debug.print("\n\niterate Position with a Term\n", .{}); 23 | var term = flecs.Term(Position).init(world); 24 | defer term.deinit(); 25 | 26 | var iter = term.iterator(); 27 | while (iter.next()) |pos| { 28 | std.debug.print("pos: {d}, entity: {d}, name: {s}\n", .{ pos, iter.entity(), iter.entity().getName() }); 29 | } 30 | 31 | std.debug.print("\n\niterate Position with a Term each\n", .{}); 32 | term.each(eachTerm); 33 | } 34 | 35 | fn eachTerm(entity: flecs.Entity, pos: *Position) void { 36 | std.debug.print("pos: {d}, entity: {d}\n", .{ pos, entity }); 37 | } 38 | -------------------------------------------------------------------------------- /examples/tester.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs"); 3 | 4 | pub const Velocity = struct { x: f32, y: f32 }; 5 | pub const Position = struct { x: f32, y: f32 }; 6 | pub const Acceleration = struct { x: f32, y: f32 }; 7 | pub const Player = struct { id: u8 = 0 }; 8 | pub const Enemy = struct { id: u64 = 0 }; 9 | 10 | pub fn main() !void { 11 | var world = flecs.World.init(); 12 | defer world.deinit(); 13 | 14 | const entity1 = world.newEntityWithName("MyEntityYo"); 15 | const entity2 = world.newEntityWithName("MyEntity2"); 16 | const entity3 = world.newEntityWithName("HasAccel"); 17 | const entity4 = world.newEntityWithName("HasNoVel"); 18 | 19 | entity1.set(Position{ .x = 0, .y = 0 }); 20 | entity1.set(Velocity{ .x = 1.1, .y = 1.1 }); 21 | entity1.set(Enemy{ .id = 66 }); 22 | 23 | entity2.set(Position{ .x = 2, .y = 2 }); 24 | entity2.set(Velocity{ .x = 1.2, .y = 1.2 }); 25 | entity2.set(Player{ .id = 3 }); 26 | 27 | entity3.set(Position{ .x = 3, .y = 3 }); 28 | entity3.set(Velocity{ .x = 1.2, .y = 1.2 }); 29 | entity3.set(Player{ .id = 4 }); 30 | 31 | entity4.set(Position{ .x = 4, .y = 4 }); 32 | entity4.set(Acceleration{ .x = 1.2, .y = 1.2 }); 33 | 34 | var builder = flecs.QueryBuilder.init(world) 35 | .withFilter(Position) 36 | .with(Velocity) 37 | .optional(Acceleration) 38 | .optional(Player) 39 | .optional(Enemy); 40 | 41 | var filter = builder.buildFilter(); 42 | defer filter.deinit(); 43 | 44 | std.debug.print("\n\niterate the Filter with a TableIterator\n", .{}); 45 | var table_iter = filter.tableIterator(struct { vel: *Velocity, acc: ?*Acceleration, player: ?*Player, enemy: ?*Enemy }); 46 | while (table_iter.next()) |it| { 47 | var i: usize = 0; 48 | while (i < it.count) : (i += 1) { 49 | const accel = if (it.data.acc) |acc| acc[i] else null; 50 | const player = if (it.data.player) |play| play[i] else null; 51 | const enemy = if (it.data.enemy) |en| en[i] else null; 52 | std.debug.print("i: {d}, vel: {d}, acc: {d}, player: {d}, enemy: {d}\n", .{ i, it.data.vel[i], accel, player, enemy }); 53 | } 54 | } 55 | 56 | std.debug.print("\n\niterate with a Filter each with a single struct of components\n", .{}); 57 | filter.each(eachFilter); 58 | std.debug.print("\n\niterate with a Filter each with a param per component\n", .{}); 59 | filter.each(eachFilterSeperateParams); 60 | } 61 | 62 | fn eachFilter(e: struct { vel: *Velocity, acc: ?*Acceleration, player: ?*Player, enemy: ?*Enemy }) void { 63 | std.debug.print("comps: {any}\n", .{e}); 64 | } 65 | 66 | fn eachFilterSeperateParams(vel: *Velocity, acc: ?*Acceleration, player: ?*Player, enemy: ?*Enemy) void { 67 | std.debug.print("vel: {d}, acc: {d}, player: {d}, enemy: {d}\n", .{ vel, acc, player, enemy }); 68 | } 69 | -------------------------------------------------------------------------------- /src/entity.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs.zig"); 3 | const meta = @import("meta.zig"); 4 | 5 | pub const Entity = struct { 6 | world: *flecs.c.EcsWorld, 7 | id: flecs.EntityId, 8 | 9 | pub fn init(world: *flecs.c.EcsWorld, id: flecs.EntityId) Entity { 10 | return .{ 11 | .world = world, 12 | .id = id, 13 | }; 14 | } 15 | 16 | fn getWorld(self: Entity) flecs.World { 17 | return .{ .world = self.world }; 18 | } 19 | 20 | pub fn format(value: Entity, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 21 | _ = options; 22 | _ = fmt; 23 | try std.fmt.format(writer, "Entity{{ {d} }}", .{value.id}); 24 | } 25 | 26 | pub fn getFullpath(self: Entity) [*c]u8 { 27 | return flecs.c.ecs_get_path_w_sep(self.world, 0, self.id, ".", null); 28 | } 29 | 30 | pub fn setName(self: Entity, name: [*c]const u8) void { 31 | _ = flecs.c.ecs_set_name(self.world, self.id, name); 32 | } 33 | 34 | pub fn getName(self: Entity) [*c]const u8 { 35 | return flecs.c.ecs_get_name(self.world, self.id); 36 | } 37 | 38 | /// add an entity to an entity. This operation adds a single entity to the type of an entity. Type roles may be used in 39 | /// combination with the added entity. 40 | pub fn add(self: Entity, id_or_type: anytype) void { 41 | std.debug.assert(@TypeOf(id_or_type) == flecs.EntityId or @typeInfo(@TypeOf(id_or_type)) == .Type); 42 | const id = if (@TypeOf(id_or_type) == flecs.EntityId) id_or_type else meta.componentId(self.world, id_or_type); 43 | flecs.c.ecs_add_id(self.world, self.id, id); 44 | } 45 | 46 | /// shortcut for addPair(ChildOf, parent). Allowed parent types: Entity, EntityId, type 47 | pub fn childOf(self: Entity, parent: anytype) void { 48 | self.addPair(flecs.c.Constants.EcsChildOf, parent); 49 | } 50 | 51 | /// shortcut for addPair(IsA, base). Allowed base types: Entity, EntityId, type 52 | pub fn isA(self: Entity, base: anytype) void { 53 | self.addPair(flecs.c.EcsIsA, base); 54 | } 55 | 56 | /// adds a relation to the object on the entity. Allowed params: Entity, EntityId, type 57 | pub fn addPair(self: Entity, relation: anytype, object: anytype) void { 58 | flecs.c.ecs_add_id(self.world, self.id, self.getWorld().pair(relation, object)); 59 | } 60 | 61 | /// returns true if the entity has the relation to the object 62 | pub fn hasPair(self: Entity, relation: anytype, object: anytype) bool { 63 | return flecs.c.ecs_has_id(self.world, self.id, self.getWorld().pair(relation, object)); 64 | } 65 | 66 | /// removes a relation to the object from the entity. 67 | pub fn removePair(self: Entity, relation: anytype, object: anytype) void { 68 | return flecs.c.ecs_remove_id(self.world, self.id, self.getWorld().pair(relation, object)); 69 | } 70 | 71 | pub fn setPair(self: Entity, Relation: anytype, comptime object: type, data: Relation) void { 72 | const pair = self.getWorld().pair(Relation, object); 73 | var component = &data; 74 | _ = flecs.c.ecs_set_id(self.world, self.id, pair, @sizeOf(Relation), component); 75 | } 76 | 77 | /// sets a component on entity. Can be either a pointer to a struct or a struct 78 | pub fn set(self: Entity, ptr_or_struct: anytype) void { 79 | std.debug.assert(@typeInfo(@TypeOf(ptr_or_struct)) == .Pointer or @typeInfo(@TypeOf(ptr_or_struct)) == .Struct); 80 | 81 | const T = meta.FinalChild(@TypeOf(ptr_or_struct)); 82 | var component = if (@typeInfo(@TypeOf(ptr_or_struct)) == .Pointer) ptr_or_struct else &ptr_or_struct; 83 | _ = flecs.c.ecs_set_id(self.world, self.id, meta.componentId(self.world, T), @sizeOf(T), component); 84 | } 85 | 86 | /// sets a private instance of a component on entity. Useful for inheritance. 87 | pub fn setOverride(self: Entity, ptr_or_struct: anytype) void { 88 | std.debug.assert(@typeInfo(@TypeOf(ptr_or_struct)) == .Pointer or @typeInfo(@TypeOf(ptr_or_struct)) == .Struct); 89 | 90 | const T = meta.FinalChild(@TypeOf(ptr_or_struct)); 91 | var component = if (@typeInfo(@TypeOf(ptr_or_struct)) == .Pointer) ptr_or_struct else &ptr_or_struct; 92 | const id = meta.componentId(self.world, T); 93 | flecs.c.ecs_add_id(self.world, self.id, flecs.c.Constants.ECS_OVERRIDE | id); 94 | _ = flecs.c.ecs_set_id(self.world, self.id, id, @sizeOf(T), component); 95 | } 96 | 97 | /// sets a component as modified, and will trigger observers after being modified from a system 98 | pub fn setModified(self: Entity, id_or_type: anytype) void { 99 | std.debug.assert(@TypeOf(id_or_type) == flecs.EntityId or @typeInfo(@TypeOf(id_or_type)) == .Type); 100 | const id = if (@TypeOf(id_or_type) == flecs.EntityId) id_or_type else meta.componentId(self.world, id_or_type); 101 | flecs.c.ecs_modified_id(self.world, self.id, id); 102 | } 103 | 104 | /// gets a pointer to a type if the component is present on the entity 105 | pub fn get(self: Entity, comptime T: type) ?*const T { 106 | const ptr = flecs.c.ecs_get_id(self.world, self.id, meta.componentId(self.world, T)); 107 | if (ptr) |p| { 108 | return flecs.componentCast(T, p); 109 | } 110 | return null; 111 | } 112 | 113 | pub fn getMut(self: Entity, comptime T: type) ?*T { 114 | var ptr = flecs.c.ecs_get_mut_id(self.world, self.id, meta.componentId(self.world, T)); 115 | if (ptr) |p| { 116 | return @ptrCast(@alignCast(p)); 117 | } 118 | return null; 119 | } 120 | 121 | /// removes a component from an Entity 122 | pub fn remove(self: Entity, id_or_type: anytype) void { 123 | std.debug.assert(@TypeOf(id_or_type) == flecs.EntityId or @typeInfo(@TypeOf(id_or_type)) == .Type); 124 | const id = if (@TypeOf(id_or_type) == flecs.EntityId) id_or_type else meta.componentId(self.world, id_or_type); 125 | flecs.c.ecs_remove_id(self.world, self.id, id); 126 | } 127 | 128 | /// removes all components from an Entity 129 | pub fn clear(self: Entity) void { 130 | flecs.c.ecs_clear(self.world, self.id); 131 | } 132 | 133 | /// removes the entity from the world. Do not use this Entity after calling this! 134 | pub fn delete(self: Entity) void { 135 | flecs.c.ecs_delete(self.world, self.id); 136 | } 137 | 138 | /// returns true if the entity is alive 139 | pub fn isAlive(self: Entity) bool { 140 | return flecs.c.ecs_is_alive(self.world, self.id); 141 | } 142 | 143 | /// returns true if the entity has a matching component type 144 | pub fn has(self: Entity, id_or_type: anytype) bool { 145 | std.debug.assert(@TypeOf(id_or_type) == flecs.EntityId or @typeInfo(@TypeOf(id_or_type)) == .Type); 146 | const id = if (@TypeOf(id_or_type) == flecs.EntityId) id_or_type else meta.componentId(self.world, id_or_type); 147 | return flecs.c.ecs_has_id(self.world, self.id, id); 148 | } 149 | 150 | /// returns the type of the component, which contains all components 151 | pub fn getType(self: Entity) flecs.Type { 152 | return flecs.Type.init(self.world, flecs.c.ecs_get_type(self.world, self.id)); 153 | } 154 | 155 | /// prints a json representation of an Entity. Note that world.enable_type_reflection should be true to 156 | /// get component values as well. 157 | pub fn printJsonRepresentation(self: Entity) void { 158 | var str = flecs.c.ecs_entity_to_json(self.world, self.id, null); 159 | std.debug.print("{s}\n", .{str}); 160 | flecs.c.ecs_os_api.free_.?(str); 161 | } 162 | }; 163 | -------------------------------------------------------------------------------- /src/filter.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs.zig"); 3 | const utils = @import("utils.zig"); 4 | const meta = @import("meta.zig"); 5 | 6 | pub const Filter = struct { 7 | world: flecs.World, 8 | filter: *flecs.c.EcsFilter = undefined, 9 | 10 | /// filter iterator that lets you fetch components via get/getOpt 11 | /// TODO: is this thing necessary? Seems the other iterators are more then capable compared to this thing. 12 | const FilterIterator = struct { 13 | iter: flecs.c.EcsIter, 14 | index: usize = 0, 15 | 16 | pub fn init(iter: flecs.c.EcsIter) @This() { 17 | return .{ .iter = iter }; 18 | } 19 | 20 | pub fn next(self: *@This()) ?void { 21 | if (self.index >= self.iter.count) { 22 | self.index = 0; 23 | if (!flecs.c.ecs_filter_next(&self.iter)) return null; 24 | } 25 | 26 | self.index += 1; 27 | } 28 | 29 | pub fn entity(self: *@This()) flecs.Entity { 30 | return flecs.Entity.init(self.iter.world.?, self.iter.entities[self.index - 1]); 31 | } 32 | 33 | /// gets the index into the terms array of this type 34 | fn getTermIndex(self: @This(), comptime T: type) usize { 35 | const comp_id = meta.componentHandle(T).*; 36 | var i: usize = 0; 37 | while (i < self.iter.term_count) : (i += 1) { 38 | if (self.iter.terms[i].id == comp_id) return i; 39 | } 40 | unreachable; 41 | } 42 | 43 | /// gets a term that is not optional 44 | pub fn get(self: @This(), comptime T: type) *T { 45 | const index = self.getTermIndex(T); 46 | const column_index = self.iter.terms[index].index; 47 | return &utils.column(&self.iter, T, column_index + 1)[self.index - 1]; 48 | } 49 | 50 | /// gets a term that is not optional but is readonly 51 | pub fn getConst(self: @This(), comptime T: type) *const T { 52 | const index = self.getTermIndex(T); 53 | const column_index = self.iter.terms[index].index; 54 | std.debug.assert(flecs.c.ecs_field_is_readonly(&self.iter, @intCast(index + 1))); 55 | 56 | // const column_index = flecs.c.ecs_iter_find_column(&self.iter, meta.componentHandle(T).*); 57 | return &utils.column(&self.iter, T, column_index + 1)[self.index - 1]; 58 | } 59 | 60 | /// gets a term that is optional. Returns null if it isnt found. 61 | pub fn getOpt(self: @This(), comptime T: type) ?*T { 62 | const index = self.getTermIndex(T); 63 | const column_index = self.iter.terms[index].index; 64 | var skip_term = meta.componentHandle(T).* != flecs.c.ecs_term_id(&self.iter, @intCast(column_index + 1)); 65 | if (skip_term) return null; 66 | 67 | if (utils.columnOpt(&self.iter, T, column_index + 1)) |col| { 68 | return &col[self.index - 1]; 69 | } 70 | return null; 71 | } 72 | 73 | /// gets a term that is optional and readonly. Returns null if it isnt found. 74 | pub fn getConstOpt(self: @This(), comptime T: type) ?*const T { 75 | const index = self.getTermIndex(T); 76 | std.debug.assert(flecs.c.ecs_field_is_readonly(&self.iter, @as(i32, @intCast(index + 1)))); 77 | 78 | const column_index = self.iter.terms[index].index; 79 | var skip_term = meta.componentHandle(T).* != flecs.c.ecs_term_id(&self.iter, @as(usize, @intCast(column_index + 1))); 80 | if (skip_term) return null; 81 | 82 | if (utils.columnOpt(&self.iter, T, column_index + 1)) |col| { 83 | return &col[self.index - 1]; 84 | } 85 | return null; 86 | } 87 | }; 88 | 89 | pub fn init(world: flecs.World, desc: *flecs.c.EcsFilterDesc) @This() { 90 | std.debug.assert(desc.storage == null); 91 | var filter_storage = std.heap.c_allocator.create(flecs.c.EcsFilter) catch unreachable; 92 | @memset(@as([*]u8, @ptrCast(filter_storage))[0..@sizeOf(flecs.c.EcsFilter)], 0); 93 | 94 | filter_storage.hdr.magic = flecs.c.Constants.EcsFilterMagic; 95 | desc.storage = filter_storage; 96 | var out_filter = flecs.c.ecs_filter_init(world.world, desc); 97 | std.debug.assert(out_filter != null); 98 | var filter = @This(){ 99 | .world = world, 100 | .filter = out_filter, 101 | }; 102 | return filter; 103 | } 104 | 105 | pub fn deinit(self: *@This()) void { 106 | flecs.c.ecs_filter_fini(self.filter); 107 | std.heap.c_allocator.destroy(self.filter); 108 | } 109 | 110 | pub fn asString(self: *@This()) [*c]u8 { 111 | return flecs.c.ecs_filter_str(self.world.world, self.filter); 112 | } 113 | 114 | pub fn filterIterator(self: *@This()) FilterIterator { 115 | return FilterIterator.init(flecs.c.ecs_filter_iter(self.world.world, self.filter)); 116 | } 117 | 118 | /// gets an iterator that let you iterate the tables and then it provides an inner iterator to interate entities 119 | pub fn tableIterator(self: *@This(), comptime Components: type) flecs.TableIterator(Components) { 120 | temp_iter_storage = flecs.c.ecs_filter_iter(self.world.world, self.filter); 121 | return flecs.TableIterator(Components).init(&temp_iter_storage, flecs.c.ecs_filter_next); 122 | } 123 | 124 | // storage for the iterator so it can be passed by reference. Do not in-flight two Filters at once! 125 | var temp_iter_storage: flecs.c.EcsIter = undefined; 126 | 127 | /// gets an iterator that iterates all matched entities from all tables in one iteration. Do not create more than one at a time! 128 | pub fn iterator(self: *@This(), comptime Components: type) flecs.Iterator(Components) { 129 | temp_iter_storage = flecs.c.ecs_filter_iter(self.world.world, self.filter); 130 | return flecs.Iterator(Components).init(&temp_iter_storage, flecs.c.ecs_filter_next); 131 | } 132 | 133 | /// allows either a function that takes 1 parameter (a struct with fields that match the components in the query) or multiple paramters 134 | /// (each param should match the components in the query in order) 135 | pub fn each(self: *@This(), comptime function: anytype) void { 136 | // dont allow BoundFn 137 | std.debug.assert(@typeInfo(@TypeOf(function)) == .Fn); 138 | comptime var arg_count = meta.argCount(function); 139 | 140 | if (arg_count == 1) { 141 | const Components = @typeInfo(@TypeOf(function)).Fn.args[0].arg_type.?; 142 | 143 | var iter = self.iterator(Components); 144 | while (iter.next()) |comps| { 145 | @call(.{ .modifier = .always_inline }, function, .{comps}); 146 | } 147 | } else { 148 | const Components = std.meta.ArgsTuple(@TypeOf(function)); 149 | 150 | var iter = self.iterator(Components); 151 | while (iter.next()) |comps| { 152 | @call(.{ .modifier = .always_inline }, function, meta.fieldsTuple(comps)); 153 | } 154 | } 155 | } 156 | }; 157 | -------------------------------------------------------------------------------- /src/flecs.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | pub const c = @import("c.zig"); 3 | 4 | // Import wrapper function 5 | pub usingnamespace c; 6 | 7 | // TODO: why does translate-c fail for cImport but succeeds when used directly? 8 | // const flecs = @cImport(@cInclude("flecs.h")); 9 | // pub usingnamespace flecs; 10 | 11 | pub const queries = @import("queries.zig"); 12 | 13 | pub const EntityId = c.EcsEntity; 14 | pub const Entity = @import("entity.zig").Entity; 15 | pub const World = @import("world.zig").World; 16 | pub const QueryBuilder = @import("query_builder.zig").QueryBuilder; 17 | pub const Term = @import("term.zig").Term; 18 | pub const Filter = @import("filter.zig").Filter; 19 | pub const Query = @import("query.zig").Query; 20 | pub const Type = @import("type.zig").Type; 21 | 22 | pub const Iterator = @import("iterator.zig").Iterator; 23 | pub const TableIterator = @import("table_iterator.zig").TableIterator; 24 | 25 | pub usingnamespace @import("utils.zig"); 26 | pub const meta = @import("meta.zig"); 27 | 28 | // Builtin pipeline tags 29 | pub const Phase = enum(c.EcsId) { 30 | monitor = c.ECS_HI_COMPONENT_ID + 61, 31 | inactive = c.ECS_HI_COMPONENT_ID + 63, 32 | pipeline = c.ECS_HI_COMPONENT_ID + 64, 33 | pre_frame = c.ECS_HI_COMPONENT_ID + 65, 34 | on_load = c.ECS_HI_COMPONENT_ID + 66, 35 | post_load = c.ECS_HI_COMPONENT_ID + 67, 36 | pre_update = c.ECS_HI_COMPONENT_ID + 68, 37 | on_update = c.ECS_HI_COMPONENT_ID + 69, 38 | on_validate = c.ECS_HI_COMPONENT_ID + 70, 39 | post_update = c.ECS_HI_COMPONENT_ID + 71, 40 | pre_store = c.ECS_HI_COMPONENT_ID + 72, 41 | on_store = c.ECS_HI_COMPONENT_ID + 73, 42 | post_frame = c.ECS_HI_COMPONENT_ID + 74, 43 | }; 44 | 45 | pub const Event = enum(c.EcsId) { 46 | // Event. Triggers when an id (component, tag, pair) is added to an entity 47 | on_add = c.ECS_HI_COMPONENT_ID + 33, 48 | // Event. Triggers when an id (component, tag, pair) is removed from an entity 49 | on_remove = c.ECS_HI_COMPONENT_ID + 34, 50 | // Event. Triggers when a component is set for an entity 51 | on_set = c.ECS_HI_COMPONENT_ID + 35, 52 | // Event. Triggers when a component is unset for an entity 53 | un_set = c.ECS_HI_COMPONENT_ID + 36, 54 | // Event. Triggers when an entity is deleted. 55 | on_delete = c.ECS_HI_COMPONENT_ID + 37, 56 | // Event. Exactly-once trigger for when an entity matches/unmatches a filter 57 | monitor = c.ECS_HI_COMPONENT_ID + 61, 58 | _, 59 | }; 60 | 61 | // pub const OperKind = enum(c_int) { 62 | // and_ = c.EcsAnd, 63 | // or_ = c.EcsOr, 64 | // not = c.EcsNot, 65 | // optional = c.EcsOptional, 66 | // and_from = c.EcsAndFrom, 67 | // or_from = c.EcsOrFrom, 68 | // not_from = c.EcsNotFrom, 69 | // }; 70 | 71 | // pub const InOutKind = enum(c_int) { 72 | // default = c..ecs_in_outDefault, // in_out for regular terms, in for shared terms 73 | // filter = c..ecs_in_outNone, // neither read nor written. Cannot have a query term. 74 | // in_out = c..ecs_in_out, // read/write 75 | // in = c.EcsIn, // read only. Query term is const. 76 | // out = c.EcsOut, // write only 77 | // }; 78 | 79 | pub fn pairFirst(id: EntityId) u32 { 80 | return @truncate((id & c.Constants.ECS_COMPONENT_MASK) >> 32); 81 | } 82 | 83 | pub fn pairSecond(id: EntityId) u32 { 84 | return @truncate(id); 85 | } 86 | 87 | /// Returns the base type of the given type, useful for pointers. 88 | pub fn BaseType(comptime T: type) type { 89 | switch (@typeInfo(T)) { 90 | .Pointer => |info| switch (info.size) { 91 | .One => switch (@typeInfo(info.child)) { 92 | .Struct => return info.child, 93 | .Optional => |opt_info| return opt_info.child, 94 | else => {}, 95 | }, 96 | else => {}, 97 | }, 98 | .Optional => |info| return BaseType(info.child), 99 | .Enum, .Struct => return T, 100 | else => {}, 101 | } 102 | @compileError("Expected pointer or optional pointer, found '" ++ @typeName(T) ++ "'"); 103 | } 104 | 105 | /// Casts the anyopaque pointer to a const pointer of the given type. 106 | pub fn ecs_cast(comptime T: type, val: ?*const anyopaque) *const T { 107 | return @ptrCast(@alignCast(val)); 108 | } 109 | 110 | /// Casts the anyopaque pointer to a pointer of the given type. 111 | pub fn ecs_cast_mut(comptime T: type, val: ?*anyopaque) *T { 112 | return @ptrCast(@alignCast(val)); 113 | } 114 | 115 | /// Returns a pointer to the EcsId of the given type. 116 | pub fn ecs_id_handle(comptime T: type) *c.EcsId { 117 | _ = T; 118 | return &(struct { 119 | pub var handle: c.EcsId = std.math.maxInt(c.EcsId); 120 | }.handle); 121 | } 122 | 123 | /// Returns the id assigned to the given type. 124 | pub fn ecs_id(comptime T: type) c.EcsId { 125 | return ecs_id_handle(T).*; 126 | } 127 | 128 | /// Returns the full id of the first element of the pair. 129 | pub fn ecs_pair_first(pair: c.EcsId) c.EcsId { 130 | return @truncate((pair & c.Constants.ECS_COMPONENT_MASK) >> 32); 131 | } 132 | 133 | /// returns the full id of the second element of the pair. 134 | pub fn ecs_pair_second(pair: c.EcsId) c.EcsId { 135 | return @truncate(pair); 136 | } 137 | 138 | /// Returns an EcsId for the given pair. 139 | /// 140 | /// first = EcsEntity or type 141 | /// second = EcsEntity or type 142 | pub fn ecs_pair(first: anytype, second: anytype) c.EcsId { 143 | const First = @TypeOf(first); 144 | const Second = @TypeOf(second); 145 | 146 | const first_type_info = @typeInfo(First); 147 | const second_type_info = @typeInfo(Second); 148 | 149 | std.debug.assert(First == c.EcsEntity or first_type_info == .Type or first_type_info == .Enum); 150 | std.debug.assert(Second == c.EcsEntity or second_type_info == .Type or second_type_info == .Enum); 151 | 152 | const first_id = if (First == c.EcsEntity) first else if (first_type_info == .Enum) ecs_id(First) else ecs_id(first); 153 | const second_id = if (Second == c.EcsEntity) second else if (second_type_info == .Enum) ecs_id(Second) else ecs_id(second); 154 | return c.ecs_make_pair(first_id, second_id); 155 | } 156 | 157 | /// Registers the given type as a new component. 158 | pub fn ecs_component(world: *c.EcsWorld, comptime T: type) void { 159 | std.debug.assert(@typeInfo(T) == .Struct or @typeInfo(T) == .Type or @typeInfo(T) == .Enum); 160 | 161 | var handle = ecs_id_handle(T); 162 | if (handle.* < std.math.maxInt(c.EcsId)) return; 163 | 164 | if (@sizeOf(T) == 0) { 165 | var desc = std.mem.zeroInit(c.EcsEntityDesc, .{ .name = @typeName(T) }); 166 | handle.* = c.ecs_entity_init(world, &desc); 167 | } else { 168 | var entity_desc = std.mem.zeroInit(c.EcsEntityDesc, .{ .name = @typeName(T) }); 169 | var component_desc = std.mem.zeroInit(c.EcsComponentDesc, .{ .entity = c.ecs_entity_init(world, &entity_desc) }); 170 | component_desc.type.alignment = @alignOf(T); 171 | component_desc.type.size = @sizeOf(T); 172 | handle.* = c.ecs_component_init(world, &component_desc); 173 | } 174 | } 175 | 176 | /// Registers a new system with the world run during the given phase. 177 | pub fn ecs_system(world: *c.EcsWorld, name: [*:0]const u8, phase: c.EcsEntity, desc: *c.EcsSystemDesc) void { 178 | var entity_desc = std.mem.zeroes(c.EcsEntityDesc); 179 | entity_desc.id = c.ecs_new_id(world); 180 | entity_desc.name = name; 181 | entity_desc.add[0] = ecs_dependson(phase); 182 | entity_desc.add[1] = phase; 183 | desc.entity = c.ecs_entity_init(world, &entity_desc); 184 | _ = c.ecs_system_init(world, desc); 185 | } 186 | 187 | /// Registers a new observer with the world. 188 | pub fn ecs_observer(world: *c.EcsWorld, name: [*:0]const u8, desc: *c.EcsObserverDesc) void { 189 | var entity_desc = std.mem.zeroes(c.EcsEntityDesc); 190 | entity_desc.id = c.ecs_new_id(world); 191 | entity_desc.name = name; 192 | desc.entity = c.ecs_entity_init(world, &entity_desc); 193 | _ = c.ecs_observer_init(world, desc); 194 | } 195 | 196 | // - New 197 | 198 | /// Returns a new entity with the given component. Pass null if no component is desired. 199 | // pub fn ecs_new(world: *c.EcsWorld, comptime T: ?type) c.EcsEntity { 200 | // if (T) |Type| { 201 | // std.debug.assert(@typeInfo(Type) == .Struct or @typeInfo(Type) == .Type); 202 | // return c.ecs_new_w_id(world, ecs_id(Type)); 203 | // } 204 | 205 | // return c.ecs_new_id(world); 206 | // } 207 | 208 | /// Returns a new entity with the given pair. 209 | /// 210 | /// first = EcsEntity or type 211 | /// second = EcsEntity or type 212 | pub fn ecs_new_w_pair(world: *c.EcsWorld, first: anytype, second: anytype) c.EcsEntity { 213 | return c.ecs_new_w_id(world, ecs_pair(first, second)); 214 | } 215 | 216 | /// Creates count entities in bulk with the given component, returning an array of those entities. 217 | /// Pass null for the component if none is desired. 218 | pub fn ecs_bulk_new(world: *c.EcsWorld, comptime Component: ?type, count: i32) []const c.EcsEntity { 219 | if (Component) |T| { 220 | std.debug.assert(@typeInfo(T) == .Struct or @typeInfo(T) == .Type); 221 | return c.ecs_bulk_new_w_id(world, ecs_id(T), count)[0..@as(usize, count)]; 222 | } 223 | 224 | return c.ecs_bulk_new_w_id(world, 0, count)[0..@as(usize, count)]; 225 | } 226 | 227 | /// Returns a new entity with the given name. 228 | pub fn ecs_new_entity(world: *c.EcsWorld, name: [*:0]const u8) c.EcsEntity { 229 | const desc = std.mem.zeroInit(c.EcsEntityDesc, .{ .name = name }); 230 | return c.ecs_entity_init(world, &desc); 231 | } 232 | 233 | /// Returns a new prefab with the given name. 234 | pub fn ecs_new_prefab(world: *c.EcsWorld, name: [*:0]const u8) c.EcsEntity { 235 | var desc = std.mem.zeroInit(c.EcsEntityDesc, .{ .name = name }); 236 | desc.add[0] = c.Constants.EcsPrefab; 237 | return c.ecs_entity_init(world, &desc); 238 | } 239 | 240 | // - Add 241 | 242 | /// Adds a component to the entity. If the type is a non-zero struct, the values may be undefined! 243 | pub fn ecs_add(world: *c.EcsWorld, entity: c.EcsEntity, t: anytype) void { 244 | const T = @TypeOf(t); 245 | const typeInfo = @typeInfo(T); 246 | 247 | if (typeInfo == .Type) { 248 | c.ecs_add_id(world, entity, ecs_id(t)); 249 | } else { 250 | _ = c.ecs_set_id(world, entity, ecs_id(T), @sizeOf(T), &t); 251 | } 252 | } 253 | 254 | /// Adds the pair to the entity. 255 | /// 256 | /// first = EcsEntity or type 257 | /// second = EcsEntity or type 258 | pub fn ecs_add_pair(world: *c.EcsWorld, entity: c.EcsEntity, first: anytype, second: anytype) void { 259 | const First = @TypeOf(first); 260 | const first_type_info = @typeInfo(First); 261 | 262 | if (first_type_info == .Enum) { 263 | _ = c.ecs_set_id(world, entity, ecs_pair(first, second), @sizeOf(First), &first); 264 | } else c.ecs_add_id(world, entity, ecs_pair(first, second)); 265 | } 266 | 267 | // - Remove 268 | 269 | /// Removes the component or entity from the entity. 270 | /// 271 | /// t = EcsEntity or Type 272 | pub fn ecs_remove(world: *c.EcsWorld, entity: c.EcsEntity, t: anytype) void { 273 | const T = @TypeOf(t); 274 | const type_info = @typeInfo(T); 275 | 276 | std.debug.assert(T == c.EcsEntity or type_info == .Type); 277 | 278 | const id = if (T == c.EcsEntity) t else ecs_id(T); 279 | c.ecs_remove_id(world, entity, id); 280 | } 281 | 282 | /// Removes the pair from the entity. 283 | /// 284 | /// first = EcsEntity or type 285 | /// second = EcsEntity or type 286 | pub fn ecs_remove_pair(world: *c.EcsWorld, entity: c.EcsEntity, first: anytype, second: anytype) void { 287 | c.ecs_remove_id(world, entity, ecs_pair(first, second)); 288 | } 289 | 290 | // - Override 291 | 292 | /// Overrides the component on the entity. 293 | pub fn ecs_override(world: *c.EcsWorld, entity: c.EcsEntity, comptime T: type) void { 294 | std.debug.assert(@typeInfo(T) == .Struct or @typeInfo(T) == .Type); 295 | c.ecs_override_id(world, entity, ecs_id(T)); 296 | } 297 | 298 | /// Overrides the pair on the entity. 299 | /// 300 | /// first = EcsEntity or type 301 | /// second = EcsEntity or type 302 | pub fn ecs_override_pair(world: *c.EcsWorld, entity: c.EcsEntity, first: anytype, second: anytype) void { 303 | c.ecs_override_id(world, entity, ecs_pair(first, second)); 304 | } 305 | 306 | // Bulk remove/delete 307 | 308 | /// Deletes all children from parent entity. 309 | pub fn ecs_delete_children(world: *c.EcsWorld, parent: c.EcsEntity) void { 310 | c.ecs_delete_with(world, ecs_pair(c.Constants.EcsChildOf, parent)); 311 | } 312 | 313 | // - Set 314 | 315 | /// Sets the component on the entity. If the component is not already added, it will automatically be added and set. 316 | pub fn ecs_set(world: *c.EcsWorld, entity: c.EcsEntity, t: anytype) void { 317 | std.debug.assert(@typeInfo(@TypeOf(t)) == .Pointer or @typeInfo(@TypeOf(t)) == .Struct); 318 | const T = BaseType(@TypeOf(t)); 319 | const ptr = if (@typeInfo(@TypeOf(t)) == .Pointer) t else &t; 320 | _ = c.ecs_set_id(world, entity, ecs_id(T), @sizeOf(T), ptr); 321 | } 322 | 323 | /// Sets the component on the first element of the pair. If the component is not already added, it will automatically be added and set. 324 | /// 325 | /// first = pointer or struct 326 | /// second = type or EcsEntity 327 | pub fn ecs_set_pair(world: *c.EcsWorld, entity: c.EcsEntity, first: anytype, second: anytype) void { 328 | const First = @TypeOf(first); 329 | const first_type_info = @typeInfo(First); 330 | const FirstT = BaseType(First); 331 | 332 | std.debug.assert(first_type_info == .Pointer or first_type_info == .Struct); 333 | 334 | const pair_id = ecs_pair(FirstT, second); 335 | const ptr = if (first_type_info == .Pointer) first else &first; 336 | 337 | _ = c.ecs_set_id(world, entity, pair_id, @sizeOf(FirstT), ptr); 338 | } 339 | 340 | /// Sets the component on the second element of the pair. If the component is not already added, it will automatically be added and set. 341 | /// 342 | /// first = type or EcsEntity 343 | /// second = pointer or struct 344 | pub fn ecs_set_pair_second(world: *c.EcsWorld, entity: c.EcsEntity, first: anytype, second: anytype) void { 345 | const Second = @TypeOf(second); 346 | const second_type_info = @typeInfo(Second); 347 | const SecondT = BaseType(Second); 348 | 349 | std.debug.assert(second_type_info == .Pointer or second_type_info == .Struct); 350 | 351 | const pair_id = ecs_pair(first, SecondT); 352 | const ptr = if (second_type_info == .Pointer) second else &second; 353 | 354 | const First = @TypeOf(first); 355 | const first_type_info = @typeInfo(First); 356 | if (first_type_info == .Type and @sizeOf(First) > 0) { 357 | std.log.warn("[{s}] ecs_set_pair_second: Both types are components, attached data will assume the type of {s}.", .{@typeName(First)}); 358 | } 359 | 360 | _ = c.ecs_set_id(world, entity, pair_id, @sizeOf(SecondT), ptr); 361 | } 362 | 363 | // - Get 364 | 365 | /// Gets an optional const pointer to the given component type on the entity. 366 | pub fn ecs_get(world: *c.EcsWorld, entity: c.EcsEntity, comptime T: type) ?*const T { 367 | std.debug.assert(@typeInfo(T) == .Struct or @typeInfo(T) == .Type or @typeInfo(T) == .Enum); 368 | if (c.ecs_get_id(world, entity, ecs_id(T))) |ptr| { 369 | return ecs_cast(T, ptr); 370 | } 371 | return null; 372 | } 373 | 374 | /// Gets an optional pointer to the given component type on the entity. 375 | pub fn ecs_get_mut(world: *c.EcsWorld, entity: c.EcsEntity, comptime T: type) ?*T { 376 | std.debug.assert(@typeInfo(T) == .Struct or @typeInfo(T) == .Type); 377 | if (c.ecs_get_mut_id(world, entity, ecs_id(T))) |ptr| { 378 | return ecs_cast_mut(T, ptr); 379 | } 380 | return null; 381 | } 382 | 383 | /// Gets an optional pointer to the first element of the pair. 384 | /// 385 | /// First = type 386 | /// second = type or entity 387 | pub fn ecs_get_pair(world: *c.EcsWorld, entity: c.EcsEntity, comptime First: type, second: anytype) ?*const First { 388 | std.debug.assert(@typeInfo(First) == .Struct or @typeInfo(First) == .Type or @typeInfo(First) == .Enum); 389 | 390 | const Second = @TypeOf(second); 391 | const second_type_info = @typeInfo(Second); 392 | 393 | std.debug.assert(second_type_info == .Type or Second == c.EcsEntity); 394 | 395 | if (c.ecs_get_id(world, entity, ecs_pair(First, second))) |ptr| { 396 | return ecs_cast(First, ptr); 397 | } 398 | return null; 399 | } 400 | 401 | /// Gets an optional pointer to the second element of the pair. 402 | /// 403 | /// first = type or entity 404 | /// Second = type 405 | pub fn ecs_get_pair_second(world: *c.EcsWorld, entity: c.EcsEntity, first: anytype, comptime Second: type) ?*const Second { 406 | std.debug.assert(@typeInfo(Second) == .Struct or @typeInfo(Second) == .Type); 407 | 408 | const First = @TypeOf(first); 409 | const first_type_info = @typeInfo(First); 410 | 411 | std.debug.assert(first_type_info == .Type or First == c.EcsEntity); 412 | 413 | if (c.ecs_get_id(world, entity, ecs_pair(first, Second))) |ptr| { 414 | return ecs_cast(Second, ptr); 415 | } 416 | return null; 417 | } 418 | 419 | // - Iterators 420 | 421 | /// Returns an optional slice for the type given the field location. 422 | /// Use the entity's index from the iterator to access component. 423 | pub fn ecs_field(it: *c.EcsIter, comptime T: type, index: usize) ?[]T { 424 | if (c.ecs_field_w_size(it, @sizeOf(T), @as(i32, @intCast(index)))) |ptr| { 425 | const c_ptr: [*]T = @ptrCast(@alignCast(ptr)); 426 | return c_ptr[0..@as(usize, @intCast(it.count))]; 427 | } 428 | return null; 429 | } 430 | 431 | // - Utilities for commonly used operations 432 | 433 | /// Returns a pair id for isa e. 434 | pub fn ecs_isa(e: anytype) c.EcsId { 435 | return ecs_pair(c.Constants.EcsIsA, e); 436 | } 437 | 438 | /// Returns a pair id for child of e. 439 | pub fn ecs_childof(e: anytype) c.EcsId { 440 | return ecs_pair(c.Constants.EcsChildOf, e); 441 | } 442 | 443 | /// Returns a pair id for depends on e. 444 | pub fn ecs_dependson(e: anytype) c.EcsId { 445 | return ecs_pair(c.Constants.EcsDependsOn, e); 446 | } 447 | -------------------------------------------------------------------------------- /src/iterator.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs.zig"); 3 | const meta = @import("meta.zig"); 4 | 5 | pub fn Iterator(comptime Components: type) type { 6 | std.debug.assert(@typeInfo(Components) == .Struct); 7 | 8 | // converts the Components struct fields into pointer-to-many arrays 9 | const Columns = meta.TableIteratorData(Components); 10 | 11 | // used internally to store the current tables columns and the array length 12 | const TableColumns = struct { 13 | columns: Columns = undefined, 14 | count: i32, 15 | }; 16 | 17 | return struct { 18 | iter: *flecs.c.EcsIter, 19 | inner_iter: ?TableColumns = null, 20 | index: usize = 0, 21 | nextFn: *const fn ([*c]flecs.c.EcsIter) callconv(.C) bool, 22 | 23 | pub fn init(iter: *flecs.c.EcsIter, comptime nextFn: fn ([*c]flecs.c.EcsIter) callconv(.C) bool) @This() { 24 | meta.validateIterator(Components, iter); 25 | return .{ 26 | .iter = iter, 27 | .nextFn = nextFn, 28 | }; 29 | } 30 | 31 | pub fn entity(self: *@This()) flecs.Entity { 32 | return flecs.Entity.init(self.iter.world.?, self.iter.entities[self.index - 1]); 33 | } 34 | 35 | pub fn world(self: *@This()) flecs.World { 36 | return .{ .world = self.iter.world.? }; 37 | } 38 | 39 | pub fn tableType(self: *@This()) flecs.Type { 40 | return flecs.Type.init(self.iter.world.?, self.iter.type); 41 | } 42 | 43 | pub fn skip(self: *@This()) void { 44 | meta.assertMsg(self.nextFn == flecs.c.ecs_query_next, "skip only valid on Queries!", .{}); 45 | flecs.c.ecs_query_skip(&self.iter); 46 | } 47 | 48 | /// gets the next Entity from the query results if one is available 49 | pub inline fn next(self: *@This()) ?Components { 50 | // outer check for when we need to see if there is another table to iterate 51 | if (self.inner_iter == null) { 52 | self.inner_iter = self.nextTable(); 53 | if (self.inner_iter == null) return null; 54 | self.index = 0; 55 | } 56 | 57 | var comps: Components = undefined; 58 | inline for (@typeInfo(Components).Struct.fields) |field| { 59 | var column = @field(self.inner_iter.?.columns, field.name); 60 | 61 | // for optionals, we have to unwrap the column since it is also optional 62 | if (@typeInfo(field.type) == .Optional) { 63 | if (column) |col| { 64 | @field(comps, field.name) = &col[self.index]; 65 | } else { 66 | @field(comps, field.name) = null; 67 | } 68 | } else { 69 | @field(comps, field.name) = &column[self.index]; 70 | } 71 | } 72 | 73 | self.index += 1; 74 | 75 | // check for iteration of the current tables completion. if its done null the inner_iter so we fetch the next one when called again 76 | if (self.index == self.inner_iter.?.count) self.inner_iter = null; 77 | 78 | return comps; 79 | } 80 | 81 | /// gets the next table from the query results if one is available. Fills the iterator with the columns from the table. 82 | inline fn nextTable(self: *@This()) ?TableColumns { 83 | if (!self.nextFn(self.iter)) return null; 84 | 85 | var iter: TableColumns = .{ .count = self.iter.count }; 86 | var index: usize = 0; 87 | inline for (@typeInfo(Components).Struct.fields, 0..) |field, i| { 88 | // skip filters and EcsNothing masks since they arent returned when we iterate 89 | while (self.iter.terms[index].inout == .ecs_in_out_none or self.iter.terms[index].src.flags == flecs.c.Constants.EcsIsEntity) : (index += 1) {} 90 | 91 | const is_optional = @typeInfo(field.type) == .Optional; 92 | const col_type = meta.FinalChild(field.type); 93 | if (meta.isConst(field.type)) std.debug.assert(flecs.c.ecs_field_is_readonly(self.iter, i + 1)); 94 | 95 | if (is_optional) @field(iter.columns, field.name) = null; 96 | const column_index = self.iter.terms[index].field_index; 97 | const raw_term_id = flecs.c.ecs_field_id(self.iter, column_index + 1); 98 | const term_id = if (flecs.c.ecs_id_is_pair(raw_term_id)) flecs.pairFirst(raw_term_id) else raw_term_id; 99 | var skip_term = if (is_optional) meta.componentHandle(col_type).* != term_id else false; 100 | 101 | // note that an OR is actually a single term! 102 | // std.debug.print("---- col_type: {any}, optional: {any}, i: {d}, col_index: {d}, skip_term: {d}\n", .{ col_type, is_optional, i, column_index, skip_term }); 103 | // std.debug.print("---- compId: {any}, term_id: {any}\n", .{ meta.componentHandle(col_type).*, flecs.c.ecs_term_id(self.iter, @intCast(usize, column_index + 1)) }); 104 | if (!skip_term) { 105 | if (flecs.columnOpt(self.iter, col_type, column_index + 1)) |col| { 106 | @field(iter.columns, field.name) = col; 107 | } 108 | } 109 | index += 1; 110 | } 111 | 112 | return iter; 113 | } 114 | }; 115 | } 116 | -------------------------------------------------------------------------------- /src/meta.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs.zig"); 3 | const meta = @import("meta.zig"); 4 | 5 | const assert = std.debug.assert; 6 | 7 | const TermInfo = @import("term_info.zig").TermInfo; 8 | 9 | /// asserts with a message 10 | pub fn assertMsg(ok: bool, comptime msg: []const u8, args: anytype) void { 11 | if (@import("builtin").mode == .Debug) { 12 | if (!ok) { 13 | std.debug.print("Assertion: " ++ msg ++ "\n", args); 14 | unreachable; 15 | } 16 | } 17 | } 18 | 19 | /// registered component handle cache. Stores the EntityId for the type. 20 | pub fn componentHandle(comptime T: type) *flecs.EntityId { 21 | return flecs.ecs_id_handle(T); 22 | } 23 | 24 | /// gets the EntityId for T creating it if it doesn't already exist 25 | pub fn componentId(world: *flecs.c.EcsWorld, comptime T: type) flecs.EntityId { 26 | var handle = componentHandle(T); 27 | if (handle.* < std.math.maxInt(flecs.EntityId)) { 28 | return handle.*; 29 | } 30 | 31 | if (@sizeOf(T) == 0) { 32 | var desc = std.mem.zeroInit(flecs.c.EcsEntityDesc, .{ .name = @typeName(T) }); 33 | handle.* = flecs.c.ecs_entity_init(world, &desc); 34 | } else { 35 | var edesc = std.mem.zeroInit(flecs.c.EcsEntityDesc, .{ .name = @typeName(T) }); 36 | var desc = std.mem.zeroInit(flecs.c.EcsComponentDesc, .{ 37 | .entity = flecs.c.ecs_entity_init(world, &edesc), 38 | .type = .{ 39 | .size = @sizeOf(T), 40 | .alignment = @alignOf(T), 41 | .hooks = std.mem.zeroInit(flecs.EcsTypeHooks, .{}), 42 | .component = 0, 43 | }, 44 | }); 45 | handle.* = flecs.c.ecs_component_init(world, &desc); 46 | } 47 | 48 | // allow disabling reflection data with a root bool 49 | if (!@hasDecl(@import("root"), "disable_reflection") or !@as(bool, @field(@import("root"), "disable_reflection"))) 50 | registerReflectionData(world, T, handle.*); 51 | 52 | return handle.*; 53 | } 54 | 55 | /// given a pointer or optional pointer returns the base struct type. 56 | pub fn FinalChild(comptime T: type) type { 57 | switch (@typeInfo(T)) { 58 | .Pointer => |info| switch (info.size) { 59 | .One => switch (@typeInfo(info.child)) { 60 | .Struct => return info.child, 61 | .Optional => |opt_info| return opt_info.child, 62 | else => { 63 | @compileError("Expected pointer or optional pointer, found '" ++ @typeInfo(info.child) ++ "'"); 64 | }, 65 | }, 66 | else => {}, 67 | }, 68 | .Optional => |info| return FinalChild(info.child), 69 | .Struct => return T, 70 | else => {}, 71 | } 72 | @compileError("Expected pointer or optional pointer, found '" ++ @typeName(T) ++ "'"); 73 | } 74 | 75 | /// given a pointer or optional pointer returns a pointer-to-many. constness and optionality are retained. 76 | pub fn PointerToMany(comptime T: type) type { 77 | var is_const = false; 78 | var is_optional = false; 79 | var PointerT = T; 80 | 81 | switch (@typeInfo(T)) { 82 | .Optional => |opt_info| switch (@typeInfo(opt_info.child)) { 83 | .Pointer => |ptr_info| { 84 | is_const = ptr_info.is_const; 85 | is_optional = true; 86 | PointerT = opt_info.child; 87 | }, 88 | else => unreachable, 89 | }, 90 | .Pointer => |ptr_info| is_const = ptr_info.is_const, 91 | else => unreachable, 92 | } 93 | 94 | const info = @typeInfo(PointerT).Pointer; 95 | const InnerType = @Type(.{ 96 | .Pointer = .{ 97 | .size = .Many, 98 | .is_const = is_const, 99 | .is_volatile = info.is_volatile, 100 | .alignment = info.alignment, 101 | .address_space = info.address_space, 102 | .child = info.child, 103 | .is_allowzero = info.is_allowzero, 104 | .sentinel = null, 105 | }, 106 | }); 107 | 108 | if (is_optional) return @Type(.{ 109 | .Optional = .{ 110 | .child = InnerType, 111 | }, 112 | }); 113 | 114 | return InnerType; 115 | } 116 | 117 | /// gets the number of arguments in the function 118 | pub fn argCount(comptime function: anytype) usize { 119 | return switch (@typeInfo(@TypeOf(function))) { 120 | .BoundFn => |func_info| func_info.args.len, 121 | .Fn => |func_info| func_info.args.len, 122 | else => assert("invalid function"), 123 | }; 124 | } 125 | 126 | /// given a query struct, returns a type with the exact same fields except the fields are made pointer-to-many. 127 | /// constness and optionality are retained. 128 | pub fn TableIteratorData(comptime Components: type) type { 129 | const src_fields = std.meta.fields(Components); 130 | const StructField = std.builtin.Type.StructField; 131 | var fields: [src_fields.len]StructField = undefined; 132 | 133 | for (src_fields, 0..) |field, i| { 134 | const T = FinalChild(field.type); 135 | fields[i] = .{ 136 | .name = field.name, 137 | .type = PointerToMany(field.type), 138 | .default_value = null, 139 | .is_comptime = false, 140 | .alignment = @alignOf(*T), 141 | }; 142 | } 143 | 144 | return @Type(.{ .Struct = .{ 145 | .layout = .Auto, 146 | .fields = &fields, 147 | .decls = &[_]std.builtin.Type.Declaration{}, 148 | .is_tuple = false, 149 | } }); 150 | } 151 | 152 | /// returns a tuple consisting of the field values of value 153 | pub fn fieldsTuple(value: anytype) FieldsTupleType(@TypeOf(value)) { 154 | const T = @TypeOf(value); 155 | assert(@typeInfo(T) == .Struct); 156 | const ti = @typeInfo(T).Struct; 157 | const FieldsTuple = FieldsTupleType(T); 158 | 159 | var tuple: FieldsTuple = undefined; 160 | comptime var i = 0; 161 | inline while (i < ti.fields.len) : (i += 1) { 162 | tuple[i] = @field(value, ti.fields[i].name); 163 | } 164 | 165 | return tuple; 166 | } 167 | 168 | /// returns the Type of the tuple version of T 169 | pub fn FieldsTupleType(comptime T: type) type { 170 | const ti = @typeInfo(T).Struct; 171 | return @Type(.{ 172 | .Struct = .{ 173 | .layout = ti.layout, 174 | .fields = ti.fields, 175 | .decls = &[0]std.builtin.Type.Declaration{}, 176 | .is_tuple = true, 177 | }, 178 | }); 179 | } 180 | 181 | pub fn validateIterator(comptime Components: type, iter: *const flecs.c.EcsIter) void { 182 | if (@import("builtin").mode == .Debug) { 183 | var index: usize = 0; 184 | const component_info = @typeInfo(Components).Struct; 185 | 186 | inline for (component_info.fields) |field| { 187 | // skip filters since they arent returned when we iterate 188 | while (iter.terms[index].inout == .ecs_in_out_none) : (index += 1) {} 189 | const is_optional = @typeInfo(field.type) == .Optional; 190 | const col_type = FinalChild(field.type); 191 | const type_entity = meta.componentHandle(col_type).*; 192 | 193 | // ensure order matches for terms vs struct fields. note that pairs need to have their first term extracted. 194 | if (flecs.c.ecs_id_is_pair(iter.terms[index].id)) { 195 | assertMsg(flecs.pairFirst(iter.terms[index].id) == type_entity, "Order of struct does not match order of iter.terms! {d} != {d}\n", .{ iter.terms[index].id, type_entity }); 196 | } else { 197 | assertMsg(iter.terms[index].id == type_entity, "Order of struct does not match order of iter.terms! {d} != {d}. term index: {d}\n", .{ iter.terms[index].id, type_entity, index }); 198 | } 199 | 200 | // validate readonly (non-ptr types in the struct) matches up with the inout 201 | const is_const = isConst(field.type); 202 | if (is_const) assert(iter.terms[index].inout == .ecs_in); 203 | if (iter.terms[index].inout == .ecs_in) assert(is_const); 204 | 205 | // validate that optionals (?* types in the struct) match up with valid opers 206 | if (is_optional) assert(iter.terms[index].oper == .ecs_or or iter.terms[index].oper == .ecs_optional); 207 | if (iter.terms[index].oper == .ecs_or or iter.terms[index].oper == .ecs_optional) assert(is_optional); 208 | 209 | index += 1; 210 | } 211 | } 212 | } 213 | 214 | /// ensures an orderBy function for a query/system is legit 215 | pub fn validateOrderByFn(comptime func: anytype) void { 216 | if (@import("builtin").mode == .Debug) { 217 | const ti = @typeInfo(@TypeOf(func)); 218 | assert(ti == .Fn); 219 | assert(ti.Fn.args.len == 4); 220 | 221 | // args are: EntityId, *const T, EntityId, *const T 222 | assert(ti.Fn.args[0].arg_type.? == flecs.EntityId); 223 | assert(ti.Fn.args[2].arg_type.? == flecs.EntityId); 224 | assert(ti.Fn.args[1].arg_type.? == ti.Fn.args[3].arg_type.?); 225 | assert(isConst(ti.Fn.args[1].arg_type.?)); 226 | assert(@typeInfo(ti.Fn.args[1].arg_type.?) == .Pointer); 227 | } 228 | } 229 | 230 | /// ensures the order by type is in the Components struct and that that it isnt an optional term 231 | pub fn validateOrderByType(comptime Components: type, comptime T: type) void { 232 | if (@import("builtin").mode == .Debug) { 233 | var valid = false; 234 | 235 | const component_info = @typeInfo(Components).Struct; 236 | inline for (component_info.fields) |field| { 237 | if (FinalChild(field.type) == T) { 238 | valid = true; 239 | } 240 | } 241 | 242 | // allow types in Filter with no fields 243 | if (@hasDecl(Components, "modifiers")) { 244 | inline for (Components.modifiers) |inout_tuple| { 245 | const ti = TermInfo.init(inout_tuple); 246 | if (ti.inout == .ecs_in_out_none) { 247 | if (ti.term_type == T) 248 | valid = true; 249 | } 250 | } 251 | } 252 | 253 | assertMsg(valid, "type {any} was not found in the struct!", .{T}); 254 | } 255 | } 256 | 257 | /// checks a Pointer or Optional for constness. Any other types passed in will error. 258 | pub fn isConst(comptime T: type) bool { 259 | switch (@typeInfo(T)) { 260 | .Pointer => |ptr| return ptr.is_const, 261 | .Optional => |opt| { 262 | switch (@typeInfo(opt.child)) { 263 | .Pointer => |ptr| return ptr.is_const, 264 | else => {}, 265 | } 266 | }, 267 | else => {}, 268 | } 269 | 270 | @compileError("Invalid type passed to isConst: " ++ @typeName(T)); 271 | } 272 | 273 | /// https://github.com/SanderMertens/flecs/tree/master/examples/c/reflection 274 | fn registerReflectionData(world: *flecs.c.EcsWorld, comptime T: type, entity: flecs.EntityId) void { 275 | // var entityDesc = std.mem.zeroInit(flecs.c.EcsEntityDesc, .{ .entity = entity }); 276 | var desc = std.mem.zeroInit(flecs.c.EcsStructDesc, .{ .entity = entity }); 277 | 278 | switch (@typeInfo(T)) { 279 | .Struct => |si| { 280 | // tags have no size so ignore them 281 | if (@sizeOf(T) == 0) return; 282 | 283 | inline for (si.fields, 0..) |field, i| { 284 | var member = std.mem.zeroes(flecs.c.EcsMember); 285 | member.name = field.name.ptr; 286 | 287 | // TODO: support nested structs 288 | member.type = switch (field.type) { 289 | // Struct => componentId(field.type), 290 | bool => flecs.c.FLECS__Eecs_bool_t, 291 | f32 => flecs.c.FLECS__Eecs_f32_t, 292 | f64 => flecs.c.FLECS__Eecs_f64_t, 293 | u8 => flecs.c.FLECS__Eecs_u8_t, 294 | u16 => flecs.c.FLECS__Eecs_u16_t, 295 | u32 => flecs.c.FLECS__Eecs_u32_t, 296 | flecs.EntityId => blk: { 297 | // bit of a hack, but if the field name has "entity" in it we consider it an Entity reference 298 | if (std.mem.indexOf(u8, field.name, "entity") != null) 299 | break :blk flecs.c.FLECS__Eecs_entity_t; 300 | break :blk flecs.c.FLECS__Eecs_u64_t; 301 | }, 302 | i8 => flecs.c.FLECS__Eecs_i8_t, 303 | i16 => flecs.c.FLECS__Eecs_i16_t, 304 | i32 => flecs.c.FLECS__Eecs_i32_t, 305 | i64 => flecs.c.FLECS__Eecs_i64_t, 306 | usize => flecs.c.FLECS__Eecs_uptr_t, 307 | []const u8 => flecs.c.FLECS__Eecs_string_t, 308 | [*]const u8 => flecs.c.FLECS__Eecs_string_t, 309 | else => switch (@typeInfo(field.type)) { 310 | .Pointer => flecs.c.FLECS__Eecs_uptr_t, 311 | 312 | .Struct => componentId(world, field.type), 313 | 314 | .Enum => blk: { 315 | var enum_desc = std.mem.zeroes(flecs.c.ecs_enum_desc_t); 316 | // TODO 317 | enum_desc.entity = meta.componentHandle(T).*; 318 | 319 | inline for (@typeInfo(field.type).Enum.fields, 0..) |f, index| { 320 | enum_desc.constants[index] = std.mem.zeroInit(flecs.c.ecs_enum_constant_t, .{ 321 | .name = f.name.ptr, 322 | .value = @intCast(f.value), 323 | }); 324 | } 325 | 326 | break :blk flecs.c.ecs_enum_init(world, &enum_desc); 327 | }, 328 | 329 | .Array => blk: { 330 | var array_desc = std.mem.zeroes(flecs.c.ecs_array_desc_t); 331 | array_desc.type = flecs.c.FLECS__Eecs_f32_t; 332 | array_desc.entity = meta.componentHandle(T).*; 333 | array_desc.count = @typeInfo(field.type).Array.len; 334 | 335 | break :blk flecs.c.ecs_array_init(world, &array_desc); 336 | }, 337 | 338 | else => { 339 | std.debug.print("unhandled field type: {any}, ti: {any}\n", .{ field.type, @typeInfo(field.type) }); 340 | unreachable; 341 | }, 342 | }, 343 | }; 344 | desc.members[i] = member; 345 | } 346 | _ = flecs.c.ecs_struct_init(world, &desc); 347 | }, 348 | else => unreachable, 349 | } 350 | } 351 | 352 | /// given a struct of Components with optional embedded "metadata", "name", "order_by" data it generates an EcsFilterDesc 353 | pub fn generateFilterDesc(world: flecs.World, comptime Components: type) flecs.c.EcsFilterDesc { 354 | assert(@typeInfo(Components) == .Struct); 355 | var desc = std.mem.zeroes(flecs.c.EcsFilterDesc); 356 | 357 | // first, extract what we can from the Components fields 358 | const component_info = @typeInfo(Components).Struct; 359 | inline for (component_info.fields, 0..) |field, i| { 360 | desc.terms[i].id = world.componentId(meta.FinalChild(field.type)); 361 | 362 | if (@typeInfo(field.type) == .Optional) 363 | desc.terms[i].oper = .ecs_optional; 364 | 365 | if (meta.isConst(field.type)) 366 | desc.terms[i].inout = .ecs_in; 367 | } 368 | 369 | // optionally, apply any additional modifiers if present. Keep track of the term_index in case we have to add Or + Filters or Ands 370 | var next_term_index = component_info.fields.len; 371 | if (@hasDecl(Components, "modifiers")) { 372 | inline for (Components.modifiers) |inout_tuple| { 373 | const ti = TermInfo.init(inout_tuple); 374 | std.debug.print("{any}: {any}\n", .{ inout_tuple, ti }); 375 | 376 | if (getTermIndex(ti.term_type, ti.field, &desc, component_info.fields)) |term_index| { 377 | // Not terms should not be present in the Components struct 378 | assert(ti.oper != .ecs_not); 379 | 380 | // if we have a Filter on an existing type ensure we also have an Or. That is the only legit case for having a Filter and also 381 | // having the term present in the query. For that case, we will leave both optionals and add the two Or terms. 382 | if (ti.inout == .ecs_in_out_none) { 383 | assert(ti.oper == .ecs_or); 384 | if (ti.or_term_type) |or_term_type| { 385 | // ensure the term is optional. If the second Or term is present ensure it is optional as well. 386 | assert(desc.terms[term_index].oper == .ecs_optional); 387 | if (getTermIndex(or_term_type, null, &desc, component_info.fields)) |or_term_index| { 388 | assert(desc.terms[or_term_index].oper == .ecs_optional); 389 | } 390 | 391 | desc.terms[next_term_index].id = world.componentId(ti.term_type); 392 | desc.terms[next_term_index].inout = ti.inout; 393 | desc.terms[next_term_index].oper = ti.oper; 394 | next_term_index += 1; 395 | 396 | desc.terms[next_term_index].id = world.componentId(or_term_type); 397 | desc.terms[next_term_index].inout = ti.inout; 398 | desc.terms[next_term_index].oper = ti.oper; 399 | next_term_index += 1; 400 | } else unreachable; 401 | } else { 402 | if (ti.inout == .ecs_out) { 403 | assert(desc.terms[term_index].inout == .ecs_in_out_default); 404 | desc.terms[term_index].inout = ti.inout; 405 | } 406 | 407 | // the only valid oper left is Or since Not terms cant be in Components struct 408 | if (ti.oper == .ecs_or) { 409 | assert(desc.terms[term_index].oper == .ecs_optional); 410 | 411 | if (getTermIndex(ti.or_term_type.?, null, &desc, component_info.fields)) |or_term_index| { 412 | assert(desc.terms[or_term_index].oper == .ecs_optional); 413 | desc.terms[or_term_index].oper = ti.oper; 414 | } else unreachable; 415 | desc.terms[term_index].oper = ti.oper; 416 | } 417 | } 418 | 419 | if (ti.mask != 0) { 420 | assert(desc.terms[term_index].subj.set.mask == 0); 421 | desc.terms[term_index].subj.set.mask = ti.mask; 422 | } 423 | 424 | if (ti.obj_type) |obj_type| { 425 | desc.terms[term_index].id = world.pair(ti.relation_type.?, obj_type); 426 | } 427 | } else { 428 | // the term wasnt found so we must have either a Filter, Not or EcsNothing mask 429 | if (ti.inout != .ecs_in_out_none and ti.oper != .ecs_not and ti.mask != .ecs_nothing) std.debug.print("invalid inout found! No matching type found in the Components struct. Only Not and Filters are valid for types not in the struct. This should assert/panic but a zig bug lets us only print it.\n", .{}); 430 | if (ti.inout == .ecs_in_out_none) { 431 | desc.terms[next_term_index].id = world.componentId(ti.term_type); 432 | desc.terms[next_term_index].inout = ti.inout; 433 | next_term_index += 1; 434 | } else if (ti.oper == .ecs_not) { 435 | desc.terms[next_term_index].id = world.componentId(ti.term_type); 436 | desc.terms[next_term_index].oper = ti.oper; 437 | next_term_index += 1; 438 | } else if (ti.mask == .ecs_nothing) { 439 | desc.terms[next_term_index].id = world.componentId(ti.term_type); 440 | desc.terms[next_term_index].inout = ti.inout; 441 | desc.terms[next_term_index].subj.set.mask = ti.mask; 442 | next_term_index += 1; 443 | } else { 444 | std.debug.print("invalid inout applied to a term not in the query. only Not and Filter are allowed for terms not present.\n", .{}); 445 | } 446 | } 447 | } 448 | } 449 | 450 | // optionally add the expression string 451 | if (@hasDecl(Components, "expr")) { 452 | assertMsg(std.meta.Elem(@TypeOf(Components.expr)) == u8, "expr must be a const string. Found: {s}", .{std.meta.Elem(@TypeOf(Components.expr))}); 453 | desc.expr = Components.expr; 454 | } 455 | 456 | return desc; 457 | } 458 | 459 | /// gets the index into the terms array of this type or null if it isnt found (likely a new filter term) 460 | pub fn getTermIndex(comptime T: type, field_name: ?[]const u8, filter: *flecs.c.EcsFilterDesc, fields: []const std.builtin.Type.StructField) ?usize { 461 | if (fields.len == 0) return null; 462 | const comp_id = meta.componentHandle(T).*; 463 | 464 | // if we have a field_name get the index of it so we can match it up to the term index and double check the type matches 465 | const named_field_index: ?usize = if (field_name) |fname| blk: { 466 | const f_idx = inline for (fields, 0..) |field, field_index| { 467 | if (std.mem.eql(u8, field.name, fname)) 468 | break field_index; 469 | }; 470 | break :blk f_idx; 471 | } else null; 472 | 473 | var i: usize = 0; 474 | while (i < fields.len) : (i += 1) { 475 | if (filter.terms[i].id == comp_id) { 476 | if (named_field_index == null) return i; 477 | 478 | // we have a field_name so make sure the term index matches the named field index 479 | if (named_field_index == i) return i; 480 | } 481 | } 482 | return null; 483 | } 484 | -------------------------------------------------------------------------------- /src/queries.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs.zig"); 3 | 4 | // inouts 5 | pub fn Filter(comptime T: type) type { 6 | return struct { 7 | pub const inout: flecs.InOutKind = .filter; 8 | term_type: T, 9 | }; 10 | } 11 | 12 | pub fn Writeonly(comptime T: type) type { 13 | return struct { 14 | pub const inout: flecs.InOutKind = .out; 15 | term_type: T, 16 | }; 17 | } 18 | 19 | pub fn WriteonlyI(comptime T: type, field_param: []const u8) type { 20 | return struct { 21 | pub const inout: flecs.InOutKind = .out; 22 | pub const field = field_param; 23 | term_type: T, 24 | }; 25 | } 26 | 27 | // opers 28 | pub fn Or(comptime T1: type, comptime T2: type) type { 29 | return struct { 30 | term_type1: T1, 31 | term_type2: T2, 32 | }; 33 | } 34 | 35 | pub fn Not(comptime T: type) type { 36 | return struct { 37 | pub const oper: flecs.OperKind = .not; 38 | term_type: T, 39 | }; 40 | } 41 | 42 | // next three are opers that work only on kinds. not implemented yet 43 | fn AndFrom(comptime T1: type, comptime T2: type) type { 44 | return struct { 45 | pub const oper: flecs.OperKind = .and_from; 46 | term_type1: T1, 47 | term_type2: T2, 48 | }; 49 | } 50 | 51 | fn OrFrom(comptime T1: type, comptime T2: type) type { 52 | return struct { 53 | pub const oper: flecs.OperKind = .or_from; 54 | term_type1: T1, 55 | term_type2: T2, 56 | }; 57 | } 58 | 59 | fn NotFrom(comptime T1: type, comptime T2: type) type { 60 | return struct { 61 | pub const oper: flecs.OperKind = .not_from; 62 | term_type1: T1, 63 | term_type2: T2, 64 | }; 65 | } 66 | 67 | /// Non matching term. T should not be present in the query unless it should also match for some reason. 68 | /// Same thing as `Mark(EcsNothing)`. 69 | pub fn DontMatch(comptime T: type) type { 70 | return struct { 71 | pub const mask: u8 = .ecs_nothing; 72 | term_type: T, 73 | }; 74 | } 75 | 76 | pub fn Mask(comptime T: type, mask_param: u8) type { 77 | return struct { 78 | pub const mask: u8 = mask_param; 79 | term_type: T, 80 | }; 81 | } 82 | 83 | pub fn MaskI(comptime T: type, mask_param: u8, field_param: []const u8) type { 84 | return struct { 85 | pub const field = field_param; 86 | pub const mask: u8 = mask_param; 87 | term_type: T, 88 | }; 89 | } 90 | 91 | pub fn Pair(comptime RelationT: type, comptime ObjectT: type) type { 92 | return struct { 93 | relation_type: RelationT, 94 | obj_type: ObjectT, 95 | }; 96 | } 97 | 98 | pub fn PairI(comptime RelationT: type, comptime ObjectT: type, field_param: []const u8) type { 99 | return struct { 100 | pub const field = field_param; 101 | relation_type: RelationT, 102 | obj_type: ObjectT, 103 | }; 104 | } 105 | -------------------------------------------------------------------------------- /src/query.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs.zig"); 3 | const meta = @import("meta.zig"); 4 | 5 | pub const Query = struct { 6 | world: flecs.World, 7 | query: *flecs.c.EcsQuery, 8 | 9 | pub fn init(world: flecs.World, desc: *flecs.c.EcsQueryDesc) @This() { 10 | return .{ .world = world, .query = flecs.c.ecs_query_init(world.world, desc).? }; 11 | } 12 | 13 | pub fn deinit(self: *@This()) void { 14 | flecs.c.ecs_query_fini(self.query); 15 | } 16 | 17 | pub fn asString(self: *@This()) [*c]u8 { 18 | const filter = flecs.c.ecs_query_get_filter(self.query); 19 | return flecs.c.ecs_filter_str(self.world.world, filter); 20 | } 21 | 22 | pub fn changed(self: *@This(), iter: ?*flecs.c.EcsIter) bool { 23 | if (iter) |it| return flecs.c.ecs_query_changed(self.query, it); 24 | return flecs.c.ecs_query_changed(self.query, null); 25 | } 26 | 27 | /// gets an iterator that let you iterate the tables and then it provides an inner iterator to interate entities 28 | pub fn tableIterator(self: *@This(), comptime Components: type) flecs.TableIterator(Components) { 29 | temp_iter_storage = flecs.c.ecs_query_iter(self.world.world, self.query); 30 | return flecs.TableIterator(Components).init(&temp_iter_storage, flecs.c.ecs_query_next); 31 | } 32 | 33 | // storage for the iterator so it can be passed by reference. Do not in-flight two Queries at once! 34 | var temp_iter_storage: flecs.c.EcsIter = undefined; 35 | 36 | /// gets an iterator that iterates all matched entities from all tables in one iteration. Do not create more than one at a time! 37 | pub fn iterator(self: *@This(), comptime Components: type) flecs.Iterator(Components) { 38 | temp_iter_storage = flecs.c.ecs_query_iter(self.world.world, self.query); 39 | return flecs.Iterator(Components).init(&temp_iter_storage, flecs.c.ecs_query_next); 40 | } 41 | 42 | /// allows either a function that takes 1 parameter (a struct with fields that match the components in the query) or multiple paramters 43 | /// (each param should match the components in the query in order) 44 | pub fn each(self: *@This(), comptime function: anytype) void { 45 | // dont allow BoundFn 46 | std.debug.assert(@typeInfo(@TypeOf(function)) == .Fn); 47 | comptime var arg_count = meta.argCount(function); 48 | 49 | if (arg_count == 1) { 50 | const Components = @typeInfo(@TypeOf(function)).Fn.args[0].arg_type.?; 51 | 52 | var iter = self.iterator(Components); 53 | while (iter.next()) |comps| { 54 | @call(.{ .modifier = .always_inline }, function, .{comps}); 55 | } 56 | } else { 57 | const Components = std.meta.ArgsTuple(@TypeOf(function)); 58 | 59 | var iter = self.iterator(Components); 60 | while (iter.next()) |comps| { 61 | @call(.{ .modifier = .always_inline }, function, meta.fieldsTuple(comps)); 62 | } 63 | } 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /src/query_builder.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs.zig"); 3 | 4 | pub const QueryBuilder = struct { 5 | world: flecs.World, 6 | desc: flecs.c.EcsSystemDesc, 7 | terms_count: usize = 0, 8 | 9 | pub fn init(world: flecs.World) @This() { 10 | return .{ 11 | .world = world, 12 | .desc = std.mem.zeroes(flecs.c.EcsSystemDesc), 13 | }; 14 | } 15 | 16 | /// adds an InOut (read/write) component to the query 17 | pub fn with(self: *@This(), comptime T: type) *@This() { 18 | self.desc.query.filter.terms[self.terms_count].id = self.world.componentId(T); 19 | self.terms_count += 1; 20 | return self; 21 | } 22 | 23 | pub fn withReadonly(self: *@This(), comptime T: type) *@This() { 24 | self.desc.query.filter.terms[self.terms_count].id = self.world.componentId(T); 25 | self.desc.query.filter.terms[self.terms_count].inout = .ecs_in; 26 | self.terms_count += 1; 27 | return self; 28 | } 29 | 30 | pub fn withWriteonly(self: *@This(), comptime T: type) *@This() { 31 | self.desc.query.filter.terms[self.terms_count].id = self.world.componentId(T); 32 | self.desc.query.filter.terms[self.terms_count].inout = .ecs_out; 33 | self.terms_count += 1; 34 | return self; 35 | } 36 | 37 | /// the term will be used for the query but it is neither read nor written 38 | pub fn withFilter(self: *@This(), comptime T: type) *@This() { 39 | self.desc.query.filter.terms[self.terms_count].id = self.world.componentId(T); 40 | self.desc.query.filter.terms[self.terms_count].inout = .ecs_in_out_none; 41 | self.terms_count += 1; 42 | return self; 43 | } 44 | 45 | pub fn without(self: *@This(), comptime T: type) *@This() { 46 | self.desc.query.filter.terms[self.terms_count] = std.mem.zeroInit(flecs.c.EcsTerm, .{ 47 | .id = self.world.componentId(T), 48 | .oper = .ecs_not, 49 | }); 50 | self.terms_count += 1; 51 | return self; 52 | } 53 | 54 | pub fn optional(self: *@This(), comptime T: type) *@This() { 55 | self.desc.query.filter.terms[self.terms_count] = std.mem.zeroInit(flecs.c.EcsTerm, .{ 56 | .id = self.world.componentId(T), 57 | .oper = .ecs_optional, 58 | }); 59 | self.terms_count += 1; 60 | return self; 61 | } 62 | 63 | pub fn either(self: *@This(), comptime T1: type, comptime T2: type) *@This() { 64 | self.desc.query.filter.terms[self.terms_count] = std.mem.zeroInit(flecs.c.EcsTerm, .{ 65 | .id = self.world.componentId(T1), 66 | .oper = .ecs_or, 67 | }); 68 | self.terms_count += 1; 69 | self.desc.query.filter.terms[self.terms_count] = std.mem.zeroInit(flecs.c.EcsTerm, .{ 70 | .id = self.world.componentId(T2), 71 | .oper = .ecs_or, 72 | }); 73 | self.terms_count += 1; 74 | return self; 75 | } 76 | 77 | /// the query will need to match `T1 || T2` but it will not return data for either column 78 | pub fn eitherAsFilter(self: *@This(), comptime T1: type, comptime T2: type) *@This() { 79 | _ = self.either(T1, T2); 80 | self.desc.query.filter.terms[self.terms_count - 1].inout = .ecs_in_out_none; 81 | self.desc.query.filter.terms[self.terms_count - 2].inout = .ecs_in_out_none; 82 | return self; 83 | } 84 | 85 | pub fn manualTerm(self: *@This()) *flecs.c.EcsTerm { 86 | self.terms_count += 1; 87 | return &self.desc.query.filter.terms[self.terms_count - 1]; 88 | } 89 | 90 | /// inject a plain old string expression into the builder 91 | pub fn expression(self: *@This(), expr: [*c]const u8) *@This() { 92 | self.desc.filter.expr = expr; 93 | return self; 94 | } 95 | 96 | pub fn singleton(self: *@This(), comptime T: type, entity: flecs.EntityId) *@This() { 97 | self.desc.filter.terms[self.terms_count] = std.mem.zeroInit(flecs.c.EcsTerm, .{ .id = self.world.componentId(T) }); 98 | self.desc.filter.terms[self.terms_count].subj.entity = entity; 99 | self.terms_count += 1; 100 | return self; 101 | } 102 | 103 | pub fn buildFilter(self: *@This()) flecs.Filter { 104 | return flecs.Filter.init(self.world, &self.desc.query.filter); 105 | } 106 | 107 | pub fn buildQuery(self: *@This()) flecs.Query { 108 | return flecs.Query.init(self.world, &self.desc.query); 109 | } 110 | 111 | /// queries/system only 112 | pub fn orderBy(self: *@This(), comptime T: type, orderByFn: fn (flecs.EntityId, ?*const anyopaque, flecs.EntityId, ?*const anyopaque) callconv(.C) c_int) *@This() { 113 | self.desc.query.order_by_component = self.world.componentId(T); 114 | self.desc.query.order_by = orderByFn; 115 | return self; 116 | } 117 | 118 | /// queries/system only 119 | pub fn orderByEntity(self: *@This(), orderByFn: fn (flecs.EntityId, ?*const anyopaque, flecs.EntityId, ?*const anyopaque) callconv(.C) c_int) *@This() { 120 | self.desc.query.order_by = orderByFn; 121 | return self; 122 | } 123 | 124 | /// systems only. This system callback will be called at least once for each table that matches the query 125 | pub fn callback(self: *@This(), cb: fn ([*c]flecs.c.ecs_iter_t) callconv(.C) void) *@This() { 126 | self.callback = cb; 127 | return self; 128 | } 129 | 130 | /// systems only. This system callback will only be called once. The iterator should then be iterated with ecs_iter_next. 131 | pub fn run(self: *@This(), cb: fn ([*c]flecs.c.ecs_iter_t) callconv(.C) void) *@This() { 132 | self.desc.run = cb; 133 | return self; 134 | } 135 | }; 136 | -------------------------------------------------------------------------------- /src/table_iterator.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs.zig"); 3 | const meta = @import("meta.zig"); 4 | 5 | pub fn TableIterator(comptime Components: type) type { 6 | std.debug.assert(@typeInfo(Components) == .Struct); 7 | 8 | const Columns = meta.TableIteratorData(Components); 9 | 10 | return struct { 11 | pub const InnerIterator = struct { 12 | data: Columns = undefined, 13 | count: i32, 14 | }; 15 | 16 | iter: *flecs.c.EcsIter, 17 | nextFn: fn ([*c]flecs.c.EcsIter) callconv(.C) bool, 18 | 19 | pub fn init(iter: *flecs.c.EcsIter, nextFn: fn ([*c]flecs.c.EcsIter) callconv(.C) bool) @This() { 20 | meta.validateIterator(Components, iter); 21 | return .{ 22 | .iter = iter, 23 | .nextFn = nextFn, 24 | }; 25 | } 26 | 27 | pub fn tableType(self: *@This()) flecs.Type { 28 | return flecs.Type.init(self.iter.world.?, self.iter.type); 29 | } 30 | 31 | pub fn skip(self: *@This()) void { 32 | meta.assertMsg(self.nextFn == flecs.c.ecs_query_next, "skip only valid on Queries!", .{}); 33 | flecs.c.ecs_query_skip(self.iter); 34 | } 35 | 36 | pub fn next(self: *@This()) ?InnerIterator { 37 | if (!self.nextFn(self.iter)) return null; 38 | 39 | var iter: InnerIterator = .{ .count = self.iter.count }; 40 | var index: usize = 0; 41 | inline for (@typeInfo(Components).Struct.fields, 0..) |field, i| { 42 | // skip filters since they arent returned when we iterate 43 | while (self.iter.terms[index].inout == .ecs_in_out_none) : (index += 1) {} 44 | 45 | const is_optional = @typeInfo(field.type) == .Optional; 46 | const col_type = meta.FinalChild(field.type); 47 | if (meta.isConst(field.type)) std.debug.assert(flecs.c.ecs_field_is_readonly(self.iter, i + 1)); 48 | 49 | if (is_optional) @field(iter.data, field.name) = null; 50 | const column_index = self.iter.terms[index].index; 51 | var skip_term = if (is_optional) meta.componentHandle(col_type).* != flecs.c.ecs_term_id(&self.iter, @intCast(column_index + 1)) else false; 52 | 53 | // note that an OR is actually a single term! 54 | // std.debug.print("---- col_type: {any}, optional: {any}, i: {d}, col_index: {d}\n", .{ col_type, is_optional, i, column_index }); 55 | if (!skip_term) { 56 | if (flecs.columnOpt(self.iter, col_type, column_index + 1)) |col| { 57 | @field(iter.data, field.name) = col; 58 | } 59 | } 60 | index += 1; 61 | } 62 | 63 | return iter; 64 | } 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /src/term.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs.zig"); 3 | const utils = @import("utils.zig"); 4 | 5 | /// void {} is an allowed T for Terms that iterate only the entities. Void is requiried when using initWithPair 6 | pub fn Term(comptime T: anytype) type { 7 | std.debug.assert(@TypeOf(T) == type or @TypeOf(T) == void); 8 | 9 | return struct { 10 | const Self = @This(); 11 | 12 | world: flecs.World, 13 | term: flecs.c.EcsTerm, 14 | 15 | const Iterator = struct { 16 | iter: flecs.c.EcsIter, 17 | index: usize = 0, 18 | 19 | pub fn init(iter: flecs.c.EcsIter) Iterator { 20 | return .{ .iter = iter }; 21 | } 22 | 23 | pub fn next(self: *Iterator) ?*T { 24 | if (self.index >= self.iter.count) { 25 | self.index = 0; 26 | if (!flecs.c.ecs_term_next(&self.iter)) return null; 27 | } 28 | 29 | self.index += 1; 30 | const array = utils.column(&self.iter, T, 1); 31 | return &array[self.index - 1]; 32 | } 33 | 34 | pub fn entity(self: *Iterator) flecs.Entity { 35 | return flecs.Entity.init(self.iter.world.?, self.iter.entities[self.index - 1]); 36 | } 37 | }; 38 | 39 | const EntityIterator = struct { 40 | iter: flecs.c.EcsIter, 41 | index: usize = 0, 42 | 43 | pub fn init(iter: flecs.c.EcsIter) EntityIterator { 44 | return .{ .iter = iter }; 45 | } 46 | 47 | pub fn next(self: *EntityIterator) ?flecs.Entity { 48 | if (self.index >= self.iter.count) { 49 | self.index = 0; 50 | if (!flecs.c.ecs_term_next(&self.iter)) return null; 51 | } 52 | 53 | self.index += 1; 54 | return flecs.Entity.init(self.iter.world.?, self.iter.entities[self.index - 1]); 55 | } 56 | }; 57 | 58 | pub fn init(world: flecs.World) Self { 59 | var term = std.mem.zeroInit(flecs.c.EcsTerm, .{ .id = world.componentId(T) }); 60 | return .{ .world = world, .term = term }; 61 | } 62 | 63 | pub fn initWithPair(world: flecs.World, pair: flecs.EntityId) Self { 64 | var term = std.mem.zeroInit(flecs.c.EcsTerm, .{ .id = pair }); 65 | return .{ .world = world, .term = term }; 66 | } 67 | 68 | pub fn deinit(self: *Self) void { 69 | flecs.c.ecs_term_fini(&self.term); 70 | } 71 | 72 | // only export each if we have an actualy type T 73 | pub usingnamespace if (@TypeOf(T) == type) struct { 74 | pub fn iterator(self: *Self) Iterator { 75 | return Iterator.init(flecs.c.ecs_term_iter(self.world.world, &self.term)); 76 | } 77 | 78 | pub fn each(self: *Self, function: fn (flecs.Entity, *T) void) void { 79 | var iter = self.iterator(); 80 | while (iter.next()) |comp| { 81 | function(iter.entity(), comp); 82 | } 83 | } 84 | } else struct { 85 | pub fn iterator(self: *Self) EntityIterator { 86 | return EntityIterator.init(flecs.c.ecs_term_iter(self.world.world, &self.term)); 87 | } 88 | }; 89 | 90 | pub fn entityIterator(self: *Self) EntityIterator { 91 | return EntityIterator.init(flecs.c.ecs_term_iter(self.world.world, &self.term)); 92 | } 93 | }; 94 | } 95 | -------------------------------------------------------------------------------- /src/term_info.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs.zig"); 3 | 4 | const assert = std.debug.assert; 5 | 6 | /// given a type that is wrapped by any query modifiers (Or, And, Filter, etc) it unwraps the term data and validates it. 7 | pub const TermInfo = struct { 8 | term_type: type = undefined, 9 | or_term_type: ?type = null, 10 | obj_type: ?type = null, 11 | relation_type: ?type = null, 12 | inout: flecs.c.EcsInOutKind = .ecs_in_out_default, 13 | oper: flecs.c.EcsOperKind = .ecs_and, 14 | mask: u8 = flecs.c.EcsDefaultSet, 15 | field: ?[]const u8 = null, 16 | 17 | pub fn init(comptime T: type) TermInfo { 18 | var term_info = TermInfo{}; 19 | comptime var t = T; 20 | 21 | // dig through all the layers of modifiers which will each have a term_type field 22 | while (std.meta.fieldIndex(t, "term_type")) |index| { 23 | const fields = std.meta.fields(t); 24 | const fi = fields[index]; 25 | 26 | if (@hasDecl(t, "inout")) { 27 | if (term_info.inout != 0) @compileError("Bad inout in query. Previous modifier already set inout. " ++ @typeName(T)); 28 | term_info.inout = @intFromEnum(@field(t, "inout")); 29 | } 30 | if (@hasDecl(t, "oper")) { 31 | if (term_info.oper != 0) @compileError("Bad oper in query. Previous modifier already set oper. " ++ @typeName(T)); 32 | term_info.oper = @intFromEnum(@field(t, "oper")); 33 | } 34 | if (@hasDecl(t, "mask")) { 35 | if (term_info.mask != 0) @compileError("Bad mask in query. Previous modifier already set mask. " ++ @typeName(T)); 36 | term_info.mask = @field(t, "mask"); 37 | } 38 | if (@hasDecl(t, "field")) { 39 | if (term_info.field != null) @compileError("Bad field in query. Previous modifier already set field. " ++ @typeName(T)); 40 | term_info.field = @field(t, "field"); 41 | } 42 | 43 | t = fi.type; 44 | } 45 | 46 | // if the final unwrapped type is an Or it will have term_type1 47 | if (std.meta.fieldIndex(t, "term_type1")) |_| { 48 | const fields = std.meta.fields(t); 49 | t = fields[0].type; 50 | term_info.or_term_type = fields[1].type; 51 | 52 | if (term_info.oper != 0) @compileError("Bad oper in query. Previous modifier already set oper. " ++ @typeName(T)); 53 | term_info.oper = .ecs_or; 54 | } 55 | 56 | // Pairs will have an obj_type as well 57 | if (std.meta.fieldIndex(t, "obj_type")) |obj_idx| { 58 | const fields = std.meta.fields(t); 59 | if (term_info.obj_type != null) @compileError("Bad obj_type in query. Previous modifier already set obj_type. " ++ @typeName(T)); 60 | term_info.obj_type = fields[obj_idx].type; 61 | 62 | if (std.meta.fieldIndex(t, "relation_type")) |relation_idx| { 63 | term_info.relation_type = fields[relation_idx].type; 64 | } else unreachable; 65 | 66 | if (@hasDecl(t, "field")) { 67 | // TODO: this is to prevent multiple nested "I" types (WriteonlyI(PairI)) setting different names but it always triggers even if term_info.field is null 68 | // if (term_info.field == null) @compileError("Bad field in query. Previous modifier already set field. " ++ @typeName(T)); 69 | term_info.field = @field(t, "field"); 70 | } 71 | 72 | t = fields[0].type; 73 | } 74 | 75 | assert(!@hasDecl(t, "term_type") and !@hasDecl(t, "term_type1")); 76 | 77 | term_info.term_type = t; 78 | term_info.validate(); 79 | 80 | return term_info; 81 | } 82 | 83 | fn validate(comptime self: TermInfo) void { 84 | if (self.inout == .ecs_in_out_none and self.oper == .ecs_not) @compileError("Filter cant be combined with Not"); 85 | if (self.oper == .ecs_not and self.inout != .ecs_in_out_default) @compileError("Not cant be combined with any other modifiers"); 86 | } 87 | 88 | pub fn format(comptime value: TermInfo, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { 89 | const inout = switch (value.inout) { 90 | .ecs_in_out_default => "InOutDefault", 91 | .ecs_in_out_none => "Filter", 92 | .ecs_in => "In", 93 | .ecs_out => "Out", 94 | else => unreachable, 95 | }; 96 | const oper = switch (value.oper) { 97 | .ecs_and => "And", 98 | .ecs_or => "Or", 99 | .ecs_not => "Not", 100 | .ecs_optional => "Optional", 101 | else => unreachable, 102 | }; 103 | try std.fmt.format(writer, "TermInfo{{ type = {d}, or_type = {d}, inout: {s}, oper: {s}, mask: {d}, obj_type: {any}, relation_type: {any}, field_name: {s} }}", .{ value.term_type, value.or_term_type, inout, oper, value.mask, value.obj_type, value.relation_type, value.field }); 104 | } 105 | }; 106 | -------------------------------------------------------------------------------- /src/tests.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs.zig"); 3 | const utils = @import("utils.zig"); 4 | 5 | // include all files with tests 6 | comptime { 7 | _ = @import("entity.zig"); 8 | _ = @import("filter.zig"); 9 | _ = @import("flecs.zig"); 10 | _ = @import("iterator.zig"); 11 | _ = @import("meta.zig"); 12 | _ = @import("query_builder.zig"); 13 | _ = @import("query.zig"); 14 | _ = @import("term.zig"); 15 | _ = @import("type_store.zig"); 16 | _ = @import("utils.zig"); 17 | _ = @import("world.zig"); 18 | } 19 | 20 | 21 | pub const V = struct { x: f32, y: f32 }; 22 | pub const P = struct { x: f32, y: f32 }; 23 | pub const A = struct { x: f32, y: f32 }; 24 | 25 | test "type component order doesnt matter" { 26 | var world = flecs.World.init(); 27 | defer world.deinit(); 28 | 29 | const t1 = world.newTypeWithName("Type", .{ V, P, A }); 30 | const t2 = world.newTypeWithName("Type", .{ P, V, A }); 31 | try std.testing.expect(t1 == t2); 32 | 33 | const t3 = world.newType(.{ V, P, A }); 34 | const t4 = world.newType(.{ P, V, A }); 35 | try std.testing.expect(t3 == t4); 36 | } -------------------------------------------------------------------------------- /src/type.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs.zig"); 3 | 4 | pub const Type = struct { 5 | world: *flecs.c.EcsWorld, 6 | type: flecs.c.EcsType, 7 | 8 | pub fn init(world: *flecs.c.EcsWorld, t: flecs.c.EcsType) Type { 9 | return .{ .world = world, .type = t.? }; 10 | } 11 | 12 | /// returns the number of component ids in the type 13 | pub fn count(self: Type) usize { 14 | return @intCast(flecs.c.ecs_vector_count(self.type.?)); 15 | } 16 | 17 | /// returns the formatted list of components in the type 18 | pub fn asString(self: Type) []const u8 { 19 | const str = flecs.c.ecs_type_str(self.world, self.type); 20 | const len = std.mem.len(str); 21 | return str[0..len]; 22 | } 23 | 24 | /// returns an array of component ids 25 | pub fn toArray(self: Type) []const flecs.EntityId { 26 | return @as([*c]const flecs.EntityId, @ptrCast(@alignCast(flecs.c._ecs_vector_first(self.type, @sizeOf(u64), @alignOf(u64)))))[1 .. self.count() + 1]; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/type_store.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | var rng = std.rand.DefaultPrng.init(0x12345678); 4 | 5 | /// allows "namespacing" of the componentHandle. Downside is that it is comptime and each one created has a separate type 6 | /// so it cant be embedded in the World easily. TypeStore(1) and TypeStore(2) would create different handles. 7 | pub fn TypeStore(comptime _: u64) type { 8 | return struct { 9 | fn componentHandle(comptime _: type) *u64 { 10 | return &(struct { 11 | pub var handle: u64 = std.math.maxInt(u64); 12 | }.handle); 13 | } 14 | 15 | pub fn componentId(_: @This(), comptime T: type) u64 { 16 | var handle = componentHandle(T); 17 | if (handle.* < std.math.maxInt(u64)) { 18 | return handle.*; 19 | } 20 | 21 | // TODO: replace with flecs call 22 | handle.* = rng.random().int(u64); 23 | return handle.*; 24 | } 25 | }; 26 | } -------------------------------------------------------------------------------- /src/utils.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs.zig"); 3 | 4 | /// returns the column at index 5 | pub fn column(iter: [*c]const flecs.c.EcsIter, comptime T: type, index: i32) [*]T { 6 | var col = flecs.c.ecs_field_w_size(iter, @sizeOf(T), index); 7 | return @ptrCast(@alignCast(col)); 8 | } 9 | 10 | /// returns null in the case of column not being present or an invalid index 11 | pub fn columnOpt(iter: [*c]const flecs.c.EcsIter, comptime T: type, index: i32) ?[*]T { 12 | if (index <= 0) return null; 13 | var col = flecs.c.ecs_field_w_size(iter, @sizeOf(T), index); 14 | if (col == null) return null; 15 | return @ptrCast(@alignCast(col)); 16 | } 17 | 18 | /// used with ecs_iter_find_column to fetch data from terms not in the query 19 | pub fn columnNonQuery(iter: [*c]const flecs.c.EcsIter, comptime T: type, index: i32) ?[*]T { 20 | if (index <= 0) return null; 21 | var col = flecs.c.ecs_iter_column_w_size(iter, @sizeOf(T), index - 1); 22 | if (col == null) return null; 23 | return @ptrCast(@alignCast(col)); 24 | } 25 | 26 | /// used when the Flecs API provides untyped data to convert to type. Query/system order_by callbacks are one example. 27 | pub fn componentCast(comptime T: type, val: ?*const anyopaque) *const T { 28 | return @ptrCast(@alignCast(val)); 29 | } 30 | -------------------------------------------------------------------------------- /src/world.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs.zig"); 3 | const utils = @import("utils.zig"); 4 | const meta = @import("meta.zig"); 5 | 6 | const Entity = flecs.Entity; 7 | const FlecsOrderByAction = fn (flecs.c.EcsEntity, ?*const anyopaque, flecs.c.EcsEntity, ?*const anyopaque) callconv(.C) c_int; 8 | 9 | fn dummyFn(_: [*c]flecs.c.EcsIter) callconv(.C) void {} 10 | 11 | const SystemParameters = struct { 12 | ctx: ?*anyopaque, 13 | }; 14 | 15 | pub const World = struct { 16 | world: *flecs.c.EcsWorld, 17 | 18 | pub fn init() World { 19 | return .{ .world = flecs.c.ecs_init().? }; 20 | } 21 | 22 | pub fn deinit(self: *World) void { 23 | _ = flecs.c.ecs_fini(self.world); 24 | } 25 | 26 | pub fn setTargetFps(self: World, fps: f32) void { 27 | flecs.c.ecs_set_target_fps(self.world, fps); 28 | } 29 | 30 | /// available at: https://www.flecs.dev/explorer/?remote=true 31 | /// test if running: http://localhost:27750/entity/flecs 32 | pub fn enableWebExplorer(self: World) void { 33 | _ = flecs.c.ecs_set_id(self.world, flecs.c.FLECS__EEcsRest, flecs.c.FLECS__EEcsRest, @sizeOf(flecs.c.EcsRest), &std.mem.zeroes(flecs.c.EcsRest)); 34 | } 35 | 36 | /// -1 log level turns off logging 37 | pub fn setLogLevel(_: World, level: c_int, enable_colors: bool) void { 38 | _ = flecs.c.ecs_log_set_level(level); 39 | _ = flecs.c.ecs_log_enable_colors(enable_colors); 40 | } 41 | 42 | pub fn progress(self: World, delta_time: f32) void { 43 | _ = flecs.c.ecs_progress(self.world, delta_time); 44 | } 45 | 46 | pub fn getTypeStr(self: World, typ: flecs.c.EcsType) [*c]u8 { 47 | return flecs.c.ecs_type_str(self.world, typ); 48 | } 49 | 50 | pub fn newEntity(self: World) Entity { 51 | return Entity.init(self.world, flecs.c.ecs_new_id(self.world)); 52 | } 53 | 54 | pub fn newEntityWithName(self: World, name: [*c]const u8) Entity { 55 | var desc = std.mem.zeroInit(flecs.c.ecs_entity_desc_t, .{ .name = name }); 56 | return Entity.init(self.world, flecs.c.ecs_entity_init(self.world, &desc)); 57 | } 58 | 59 | pub fn newPrefab(self: World, name: [*c]const u8) Entity { 60 | var desc = std.mem.zeroInit(flecs.c.EcsEntityDesc, .{ 61 | .name = name, 62 | .add = [_]flecs.c.EcsId{0} ** 32, 63 | }); 64 | desc.add[0] = flecs.c.Constants.EcsPrefab; 65 | return Entity.init(self.world, flecs.c.ecs_entity_init(self.world, &desc)); 66 | } 67 | 68 | /// Allowed params: Entity, EntityId, type 69 | pub fn pair(self: World, relation: anytype, object: anytype) u64 { 70 | const Relation = @TypeOf(relation); 71 | const Object = @TypeOf(object); 72 | 73 | const rel_info = @typeInfo(Relation); 74 | const obj_info = @typeInfo(Object); 75 | 76 | std.debug.assert(rel_info == .Struct or rel_info == .Type or Relation == flecs.EntityId or Relation == flecs.Entity or Relation == c_int); 77 | std.debug.assert(obj_info == .Struct or obj_info == .Type or Object == flecs.EntityId or Object == flecs.Entity); 78 | 79 | const rel_id = switch (Relation) { 80 | c_int => @as(flecs.EntityId, @intCast(relation)), 81 | type => self.componentId(relation), 82 | flecs.EntityId => relation, 83 | flecs.Entity => relation.id, 84 | else => unreachable, 85 | }; 86 | 87 | const obj_id = switch (Object) { 88 | type => self.componentId(object), 89 | flecs.EntityId => object, 90 | flecs.Entity => object.id, 91 | else => unreachable, 92 | }; 93 | 94 | return flecs.c.Constants.ECS_PAIR | (rel_id << @as(u32, 32)) + @as(u32, @truncate(obj_id)); 95 | } 96 | 97 | /// bulk registers a tuple of Types 98 | pub fn registerComponents(self: World, types: anytype) void { 99 | std.debug.assert(@typeInfo(@TypeOf(types)) == .Struct); 100 | inline for (types) |t| { 101 | _ = self.componentId(t); 102 | } 103 | } 104 | 105 | /// gets the EntityId for T creating it if it doesn't already exist 106 | pub fn componentId(self: World, comptime T: type) flecs.EntityId { 107 | return meta.componentId(self.world, T); 108 | } 109 | 110 | /// creates a new type entity, or finds an existing one. A type entity is an entity with the EcsType component. The name will be generated 111 | /// by adding the Ids of each component so that order doesnt matter. 112 | pub fn newType(self: World, comptime Types: anytype) flecs.EntityId { 113 | var i: flecs.EntityId = 0; 114 | inline for (Types) |T| { 115 | i += self.componentId(T); 116 | } 117 | 118 | const name = std.fmt.allocPrintZ(std.heap.c_allocator, "Type{d}", .{i}) catch unreachable; 119 | return self.newTypeWithName(name, Types); 120 | } 121 | 122 | /// creates a new type entity, or finds an existing one. A type entity is an entity with the EcsType component. 123 | pub fn newTypeWithName(self: World, name: [*c]const u8, comptime Types: anytype) flecs.EntityId { 124 | var desc = std.mem.zeroes(flecs.c.ecs_type_desc_t); 125 | desc.entity = std.mem.zeroInit(flecs.c.ecs_entity_desc_t, .{ .name = name }); 126 | 127 | inline for (Types, 0..) |T, i| { 128 | desc.ids[i] = self.componentId(T); 129 | } 130 | 131 | return flecs.c.ecs_type_init(self.world, &desc); 132 | } 133 | 134 | pub fn newTypeExpr(self: World, name: [*c]const u8, expr: [*c]const u8) flecs.EntityId { 135 | var desc = std.mem.zeroInit(flecs.c.ecs_type_desc_t, .{ .ids_expr = expr }); 136 | desc.entity = std.mem.zeroInit(flecs.c.ecs_entity_desc_t, .{ .name = name }); 137 | 138 | return flecs.c.ecs_type_init(self.world, &desc); 139 | } 140 | 141 | /// this operation will preallocate memory in the world for the specified number of entities 142 | pub fn dim(self: World, entity_count: i32) void { 143 | flecs.c.ecs_dim(self.world, entity_count); 144 | } 145 | 146 | /// this operation will preallocate memory for a type (table) for the specified number of entities 147 | pub fn dimType(self: World, ecs_type: flecs.c.EcsType, entity_count: i32) void { 148 | flecs.c.ecs_dim_type(self.world, ecs_type, entity_count); 149 | } 150 | 151 | pub fn newSystem(self: World, name: [*c]const u8, phase: flecs.Phase, signature: [*c]const u8, action: flecs.c.EcsIterAction) void { 152 | var desc = std.mem.zeroes(flecs.c.EcsSystemDesc); 153 | desc.entity.name = name; 154 | desc.entity.add[0] = @intFromEnum(phase); 155 | desc.query.filter.expr = signature; 156 | // desc.multi_threaded = true; 157 | desc.callback = action; 158 | _ = flecs.c.ecs_system_init(self.world, &desc); 159 | } 160 | 161 | pub fn newRunSystem(self: World, name: [*c]const u8, phase: flecs.Phase, signature: [*c]const u8, action: flecs.c.EcsIterAction) void { 162 | var desc = std.mem.zeroes(flecs.c.EcsSystemDesc); 163 | desc.entity.name = name; 164 | desc.entity.add[0] = @intFromEnum(phase); 165 | desc.query.filter.expr = signature; 166 | // desc.multi_threaded = true; 167 | desc.callback = dummyFn; 168 | desc.run = action; 169 | _ = flecs.c.ecs_system_init(self.world, &desc); 170 | } 171 | 172 | pub fn newWrappedRunSystem(self: World, name: [*c]const u8, phase: flecs.Phase, comptime Components: type, comptime action: fn (*flecs.Iterator(Components)) void, params: SystemParameters) flecs.EntityId { 173 | var edesc = std.mem.zeroes(flecs.c.EcsEntityDesc); 174 | 175 | edesc.id = 0; 176 | edesc.name = name; 177 | edesc.add[0] = flecs.ecs_pair(flecs.c.Constants.EcsDependsOn, @intFromEnum(phase)); 178 | edesc.add[1] = @intFromEnum(phase); 179 | 180 | var desc = std.mem.zeroes(flecs.c.EcsSystemDesc); 181 | desc.entity = flecs.c.ecs_entity_init(self.world, &edesc); 182 | desc.query.filter = meta.generateFilterDesc(self, Components); 183 | desc.callback = dummyFn; 184 | desc.run = wrapSystemFn(Components, action); 185 | desc.ctx = params.ctx; 186 | return flecs.c.ecs_system_init(self.world, &desc); 187 | } 188 | 189 | /// creates a Filter using the passed in struct 190 | pub fn filter(self: World, comptime Components: type) flecs.Filter { 191 | std.debug.assert(@typeInfo(Components) == .Struct); 192 | var desc = meta.generateFilterDesc(self, Components); 193 | return flecs.Filter.init(self, &desc); 194 | } 195 | 196 | /// probably temporary until we find a better way to handle it better, but a way to 197 | /// iterate the passed components of children of the parent entity 198 | pub fn filterParent(self: World, comptime Components: type, parent: flecs.Entity) flecs.Filter { 199 | std.debug.assert(@typeInfo(Components) == .Struct); 200 | var desc = meta.generateFilterDesc(self, Components); 201 | const component_info = @typeInfo(Components).Struct; 202 | desc.terms[component_info.fields.len].id = self.pair(flecs.c.EcsChildOf, parent); 203 | return flecs.Filter.init(self, &desc); 204 | } 205 | 206 | /// creates a Query using the passed in struct 207 | pub fn query(self: World, comptime Components: type) flecs.Query { 208 | std.debug.assert(@typeInfo(Components) == .Struct); 209 | var desc = std.mem.zeroes(flecs.c.ecs_query_desc_t); 210 | desc.filter = meta.generateFilterDesc(self, Components); 211 | 212 | if (@hasDecl(Components, "order_by")) { 213 | meta.validateOrderByFn(Components.order_by); 214 | const ti = @typeInfo(@TypeOf(Components.order_by)); 215 | const OrderByType = meta.FinalChild(ti.Fn.args[1].arg_type.?); 216 | meta.validateOrderByType(Components, OrderByType); 217 | 218 | desc.order_by = wrapOrderByFn(OrderByType, Components.order_by); 219 | desc.order_by_component = self.componentId(OrderByType); 220 | } 221 | 222 | if (@hasDecl(Components, "instanced") and Components.instanced) desc.filter.instanced = true; 223 | 224 | return flecs.Query.init(self, &desc); 225 | } 226 | 227 | /// adds a system to the World using the passed in struct 228 | pub fn system(self: World, comptime Components: type, phase: flecs.Phase) void { 229 | std.debug.assert(@typeInfo(Components) == .Struct); 230 | std.debug.assert(@hasDecl(Components, "run")); 231 | std.debug.assert(@hasDecl(Components, "name")); 232 | 233 | var desc = std.mem.zeroes(flecs.c.EcsSystemDesc); 234 | desc.callback = dummyFn; 235 | desc.entity.name = Components.name; 236 | desc.entity.add[0] = @intFromEnum(phase); 237 | // desc.multi_threaded = true; 238 | desc.run = wrapSystemFn(Components, Components.run); 239 | desc.query.filter = meta.generateFilterDesc(self, Components); 240 | 241 | if (@hasDecl(Components, "order_by")) { 242 | meta.validateOrderByFn(Components.order_by); 243 | const ti = @typeInfo(@TypeOf(Components.order_by)); 244 | const OrderByType = meta.FinalChild(ti.Fn.args[1].arg_type.?); 245 | meta.validateOrderByType(Components, OrderByType); 246 | 247 | desc.query.order_by = wrapOrderByFn(OrderByType, Components.order_by); 248 | desc.query.order_by_component = self.componentId(OrderByType); 249 | } 250 | 251 | if (@hasDecl(Components, "instanced") and Components.instanced) desc.filter.instanced = true; 252 | 253 | _ = flecs.c.ecs_system_init(self.world, &desc); 254 | } 255 | 256 | /// adds an observer system to the World using the passed in struct (see systems) 257 | pub fn observer(self: World, comptime Components: type, event: flecs.Event, ctx: ?*anyopaque) void { 258 | std.debug.assert(@typeInfo(Components) == .Struct); 259 | std.debug.assert(@hasDecl(Components, "run")); 260 | std.debug.assert(@hasDecl(Components, "name")); 261 | 262 | var desc = std.mem.zeroes(flecs.c.EcsObserverDesc); 263 | desc.callback = dummyFn; 264 | desc.ctx = ctx; 265 | // TODO 266 | // desc.entity.name = Components.name; 267 | desc.events[0] = @intFromEnum(event); 268 | 269 | desc.run = wrapSystemFn(Components, Components.run); 270 | desc.filter = meta.generateFilterDesc(self, Components); 271 | 272 | if (@hasDecl(Components, "instanced") and Components.instanced) desc.filter.instanced = true; 273 | 274 | _ = flecs.c.ecs_observer_init(self.world, &desc); 275 | } 276 | 277 | pub fn setName(self: World, entity: flecs.EntityId, name: [*c]const u8) void { 278 | _ = flecs.c.ecs_set_name(self.world, entity, name); 279 | } 280 | 281 | pub fn getName(self: World, entity: flecs.EntityId) [*c]const u8 { 282 | return flecs.c.ecs_get_name(self.world, entity); 283 | } 284 | 285 | /// sets a component on entity. Can be either a pointer to a struct or a struct 286 | pub fn set(self: *World, entity: flecs.EntityId, ptr_or_struct: anytype) void { 287 | std.debug.assert(@typeInfo(@TypeOf(ptr_or_struct)) == .Pointer or @typeInfo(@TypeOf(ptr_or_struct)) == .Struct); 288 | 289 | const T = meta.FinalChild(@TypeOf(ptr_or_struct)); 290 | var component = if (@typeInfo(@TypeOf(ptr_or_struct)) == .Pointer) ptr_or_struct else &ptr_or_struct; 291 | _ = flecs.c.ecs_set_id(self.world, entity, self.componentId(T), @sizeOf(T), component); 292 | } 293 | 294 | pub fn getMut(self: *World, entity: flecs.EntityId, comptime T: type) *T { 295 | var ptr = flecs.c.ecs_get_mut_id(self.world, entity.id, meta.componentId(self.world, T)); 296 | return @ptrCast(@alignCast(ptr.?)); 297 | } 298 | 299 | /// removes a component from an Entity 300 | pub fn remove(self: *World, entity: flecs.EntityId, comptime T: type) void { 301 | flecs.c.ecs_remove_id(self.world, entity, self.componentId(T)); 302 | } 303 | 304 | /// removes all components from an Entity 305 | pub fn clear(self: *World, entity: flecs.EntityId) void { 306 | flecs.c.ecs_clear(self.world, entity); 307 | } 308 | 309 | /// removes the entity from the world 310 | pub fn delete(self: *World, entity: flecs.EntityId) void { 311 | flecs.c.ecs_delete(self.world, entity); 312 | } 313 | 314 | /// deletes all entities with the component 315 | pub fn deleteWith(self: *World, comptime T: type) void { 316 | flecs.c.ecs_delete_with(self.world, self.componentId(T)); 317 | } 318 | 319 | /// remove all instances of the specified component 320 | pub fn removeAll(self: *World, comptime T: type) void { 321 | flecs.c.ecs_remove_all(self.world, self.componentId(T)); 322 | } 323 | 324 | pub fn setSingleton(self: World, ptr_or_struct: anytype) void { 325 | std.debug.assert(@typeInfo(@TypeOf(ptr_or_struct)) == .Pointer or @typeInfo(@TypeOf(ptr_or_struct)) == .Struct); 326 | 327 | const T = meta.FinalChild(@TypeOf(ptr_or_struct)); 328 | var component = if (@typeInfo(@TypeOf(ptr_or_struct)) == .Pointer) ptr_or_struct else &ptr_or_struct; 329 | _ = flecs.c.ecs_set_id(self.world, self.componentId(T), self.componentId(T), @sizeOf(T), component); 330 | } 331 | 332 | // TODO: use ecs_get_mut_id optionally based on a bool perhaps or maybe if the passed in type is a pointer? 333 | pub fn getSingleton(self: World, comptime T: type) ?*const T { 334 | std.debug.assert(@typeInfo(T) == .Struct); 335 | var val = flecs.c.ecs_get_id(self.world, self.componentId(T), self.componentId(T)); 336 | if (val == null) return null; 337 | return @as(*const T, @ptrCast(@alignCast(val))); 338 | } 339 | 340 | pub fn getSingletonMut(self: World, comptime T: type) ?*T { 341 | std.debug.assert(@typeInfo(T) == .Struct); 342 | var val = flecs.c.ecs_get_mut_id(self.world, self.componentId(T), self.componentId(T)); 343 | if (val == null) return null; 344 | return @as(*T, @ptrCast(@alignCast(val))); 345 | } 346 | 347 | pub fn removeSingleton(self: World, comptime T: type) void { 348 | std.debug.assert(@typeInfo(T) == .Struct); 349 | flecs.c.ecs_remove_id(self.world, self.componentId(T), self.componentId(T)); 350 | } 351 | }; 352 | 353 | fn wrapSystemFn(comptime T: type, comptime cb: fn (*flecs.Iterator(T)) void) fn ([*c]flecs.c.EcsIter) callconv(.C) void { 354 | const Closure = struct { 355 | pub const callback: fn (*flecs.Iterator(T)) void = cb; 356 | 357 | pub fn closure(it: [*c]flecs.c.EcsIter) callconv(.C) void { 358 | var iter = flecs.Iterator(T).init(it, flecs.c.ecs_iter_next); 359 | callback(&iter); 360 | } 361 | }; 362 | return Closure.closure; 363 | } 364 | 365 | fn wrapOrderByFn(comptime T: type, comptime cb: fn (flecs.EntityId, *const T, flecs.EntityId, *const T) c_int) FlecsOrderByAction { 366 | const Closure = struct { 367 | pub fn closure(e1: flecs.EntityId, c1: ?*const anyopaque, e2: flecs.EntityId, c2: ?*const anyopaque) callconv(.C) c_int { 368 | return @call(.{ .modifier = .always_inline }, cb, .{ e1, utils.componentCast(T, c1), e2, utils.componentCast(T, c2) }); 369 | } 370 | }; 371 | return Closure.closure; 372 | } 373 | --------------------------------------------------------------------------------