├── .devcontainer ├── Dockerfile ├── devcontainer.json └── postCreateCommand.sh ├── .gitignore ├── .gitmodules ├── .vscode ├── launch.json ├── tasks.json └── update_flecs.sh ├── 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 | zig-cache 2 | .DS_Store 3 | zig-out 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prime31/zig-flecs/1350d78d00dd6528b2d645a1e7275e1b6390a288/.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 "----- download flecs.h and flecs.c" 5 | curl -O https://raw.githubusercontent.com/SanderMertens/flecs/master/flecs.h 6 | curl -O https://raw.githubusercontent.com/SanderMertens/flecs/master/flecs.c 7 | 8 | echo "----- translate-c the new flecs.h file" 9 | zig translate-c flecs.h > ../c.zig 10 | 11 | echo "----- done" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Flecs Zig Bindings 2 | Flecs v3's node system is still under development so the code in this repository is likely to change over time to match the latest Flecs changes. 3 | 4 | #### TODO: 5 | - add `ecs_emit` now that observers are working 6 | - add multi world support. `meta.componentId` needs to somehow get scoped to a world without having to resport to `World(store_id: u8)` as the type 7 | - consider making `TermInfo` fields optional and use that optionality instead of the default value to determine which are set 8 | - query maker struct: consider making the `order_by`, `instanced` and all other options match the exactly layout of `ecs_query_desc_t` so we can just overlay it directly similar to how `mem.zeroInit` does it. If too many things need to be configurable this will be necessary. 9 | - switch support 10 | 11 | 12 | ### New Iterator API and Query Builder 13 | An expermiental filter/query/system builder + iterator builder in once concept is now in the codebase. You define a struct (`MoveSystemData` below) with 14 | the fields being the terms you want from the query returned to you. Flecs allows some more complex features that you can access by providing data in the 15 | `modifiers` static. Terms that arent actually returned (such as filters and not terms) can be defined here. You can also annotate the fields in the struct 16 | with additional modifiers to make them part of an `Or` term or mark them as writeonly. 17 | 18 | ```zig 19 | const q = flecs.queries; 20 | 21 | const MoveSystemData = struct { 22 | vel: *const Velocity, // In + And 23 | acc: ?*Acceleration, // needs metadata. could be Or or Optional. If no metadata can assume Optional. 24 | player: ?*Player, 25 | enemy: ?*Enemy, 26 | 27 | // allowed modifiers: Filter, Not, WriteOnly, Or, Mask, Pair (soon AndFrom, OrFrom, NotFrom for using type collections) 28 | pub const modifiers = .{ q.Filter(PopTart), q.Filter(q.Or(Player, Enemy)), q.Writeonly(Acceleration), q.Not(SomethingWeDontWantMatching) }; 29 | pub const order_by = orderBy; // used only by systems and queries 30 | pub const name = "SuperSystem"; 31 | }; 32 | 33 | var filter = world.filter(MoveSystemData); 34 | // iterate filter as below 35 | ``` 36 | 37 | Note that there are some special modifiers, mainly `Or` and `Pair`. When nesting these types they must be the _innermost modifier_. For example, if you want to have a `Pair` with its relation being writeonly the `Pair` must be nested inside the `Writeonly` modifier. The following would make the `Position` component writeonly: `q.Writeonly(q.Pair(Position, World))`. 38 | 39 | There are times when you may have the same component type as multiple fields in your query struct. For example, you may have an object with a local `Position` and a world `Position`. By default, modifiers act on the first component that matches their type. For cases where you want to add a modifier to a different term there are modifiers with the `I` suffix that let you pass in a field name to target the field you want to. 40 | 41 | A slightly more complex example is below to illustrate this. The positions are defined as `Pairs` using the `Local` and `World` tag structs (zero-field structs). This example also illustrates nested modifiers for a `Pair`. The second modifier makes the `pos_world` field writeonly. Note that the `Pair` is the innermost modifier and the field name only needs to be present on the outermost modifier. 42 | 43 | ```zig 44 | const DoublePosition = struct { 45 | pos_local: *const Position, 46 | pos_world: *Position, 47 | 48 | pub var modifiers = .{ q.PairI(Position, Local, "pos_local"), q.WriteonlyI(q.Pair(Position, World), "pos_world") }; 49 | }; 50 | ``` 51 | 52 | 53 | ### Random Info 54 | Reflection metadata is enabled by default. To disable it in your root zig file add `pub const disable_reflection = true;` 55 | 56 | 57 | ### Terms 58 | Terms are used to iterate a single component type. 59 | 60 | ```zig 61 | var term = flecs.Term(Position).init(world); 62 | defer term.deinit(); 63 | 64 | // iterate with a standard iterator 65 | var term_iter = term.iterator(); 66 | while (term_iter.next()) |pos| { 67 | std.debug.print("pos: {d}, entity: {d}\n", .{ pos, term_iter.entity() }); 68 | } 69 | 70 | // iterate with a function called for each entity that has the component 71 | term.each(eachTerm); 72 | 73 | fn eachTerm(entity: flecs.EntityId, pos: *Position) void { 74 | std.debug.print("pos: {d}, entity: {d}\n", .{ pos, entity }); 75 | } 76 | ``` 77 | 78 | 79 | ### Filter 80 | A filter is a list of terms that are matched against entities. Filters are cheap to create and match entities as iteration takes place. This makes them a good fit for scenarios where an application doesn't know in advance what it has to query for, a typical use case for this being runtime tags. Another advantage of filters is that while they can be reused, their cheap creation time doesn't require it. 81 | 82 | ```zig 83 | // QueryBuilder is used to define what you want to filter for 84 | var builder = flecs.QueryBuilder.init(world) 85 | .withReadonly(Position) 86 | .with(Velocity) 87 | .optional(Acceleration) 88 | .either(Player, Enemy); 89 | 90 | var filter = builder.buildFilter(); 91 | defer filter.deinit(); 92 | 93 | // iterate with a standard iterator 94 | var filter_iter = filter.iterator(); 95 | while (filter_iter.next()) |_| { 96 | // the appropriate get* method must be used and is validated in debug builds. `get` is for mutable components, `getConst` is for readonly components 97 | // and `getOpt/getOptConst` are for optional components and those added to the QueryBuilder with `either` 98 | std.debug.print("pos: {d}, vel: {d}, player: {d}\n", 99 | .{ filter_iter.getConst(Position), filter_iter.get(Velocity), filter_iter.getOpt(Player) }); 100 | } 101 | 102 | // iterate with a function called for each entity that matches the filter. The same rules apply as above for the struct passed in. 103 | filter.each(eachFilter); 104 | 105 | // save as `each` with the only difference being each component is a separate parameter. 106 | filter.each(eachFilterSeperateParams); 107 | 108 | fn eachFilter(e: struct { pos: *const Position, vel: *Velocity, acc: ?*Acceleration, player: ?*Player, enemy: ?*Enemy }) void { 109 | std.debug.print("comps: {any}\n", .{e}); 110 | } 111 | 112 | fn eachFilterSeperateParams(pos: *const Position, vel: *Velocity, acc: ?*Acceleration, player: ?*Player, enemy: ?*Enemy) void { 113 | std.debug.print("pos: {d}, vel: {d}, acc: {d}, player: {d}, enemy: {d}\n", .{ pos, vel, acc, player, enemy }); 114 | } 115 | ``` 116 | 117 | 118 | ### Queries 119 | A query is like a filter in that it is a list of terms that is matched with entities. The difference with a filter is that queries cache their results, which makes them more expensive to create, but cheaper to iterate. 120 | 121 | ```zig 122 | // QueryBuilder is used to define what you want to filter for 123 | var builder = flecs.QueryBuilder.init(world) 124 | .withReadonly(Position) 125 | .with(Velocity) 126 | .optional(Acceleration) 127 | .either(Player, Enemy) 128 | .orderBy(Position, orderBy); 129 | 130 | var query = builder.buildQuery(); 131 | defer query.deinit(); 132 | 133 | std.debug.print("\n\niterate a Query with an Iterator\n", .{}); 134 | var entity_iter = query.Iterator(struct { pos: *const Position, vel: *Velocity, acc: ?*Acceleration, player: ?*Player, enemy: ?*Enemy }); 135 | while (entity_iter.next()) |comps| { 136 | std.debug.print("comps: {any}\n", .{comps}); 137 | } 138 | 139 | std.debug.print("\n\niterate a Query with a TableIterator\n", .{}); 140 | var table_iter = query.tableIterator(struct { pos: *const Position, vel: *Velocity, acc: ?*Acceleration, player: ?*Player, enemy: ?*Enemy }); 141 | while (table_iter.next()) |it| { 142 | var i: usize = 0; 143 | while (i < it.count) : (i += 1) { 144 | const accel = if (it.data.acc) |acc| acc[i] else null; 145 | const player = if (it.data.player) |play| play[i] else null; 146 | const enemy = if (it.data.enemy) |en| en[i] else null; 147 | 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 }); 148 | } 149 | } 150 | 151 | std.debug.print("\n\niterate with a Query each\n", .{}); 152 | query.each(eachQuery); 153 | 154 | std.debug.print("\n\niterate with a Query each\n", .{}); 155 | query.each(eachQuerySeperateParams); 156 | 157 | fn eachQuery(e: struct { pos: *const Position, vel: *Velocity, acc: ?*Acceleration, player: ?*Player, enemy: ?*Enemy }) void { 158 | std.debug.print("comps: {any}\n", .{e}); 159 | } 160 | 161 | fn eachQuerySeperateParams(pos: *const Position, vel: *Velocity, acc: ?*Acceleration, player: ?*Player, enemy: ?*Enemy) void { 162 | std.debug.print("pos: {d}, vel: {d}, acc: {d}, player: {d}, enemy: {d}\n", .{ pos, vel, acc, player, enemy }); 163 | } 164 | ``` 165 | 166 | 167 | ### Systems (zigifed wrapper coming soon) 168 | A system is a query combined with a callback. Systems can be either ran manually or ran as part of an ECS-managed main loop. 169 | 170 | 171 | ### Observers/Triggers (coming soon) 172 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const builtin = @import("builtin"); 2 | const std = @import("std"); 3 | const Builder = std.build.Builder; 4 | 5 | pub const LibType = enum(i32) { 6 | static, 7 | exe_compiled, 8 | }; 9 | 10 | pub fn build(b: *std.build.Builder) anyerror!void { 11 | const target = b.standardTargetOptions(.{}); 12 | 13 | const examples = getAllExamples(b, "examples"); 14 | 15 | const examples_step = b.step("all_examples", "build all examples"); 16 | b.default_step.dependOn(examples_step); 17 | 18 | for (examples) |example| { 19 | const name = example[0]; 20 | const source = example[1]; 21 | 22 | var exe = b.addExecutable(name, source); 23 | exe.setTarget(target); 24 | exe.setOutputDir("zig-cache/bin"); 25 | 26 | if (!std.mem.eql(u8, name, "generator")) { 27 | exe.setBuildMode(b.standardReleaseOptions()); 28 | examples_step.dependOn(&exe.step); 29 | exe.install(); 30 | } 31 | 32 | // for some reason exe_compiled + debug build results in "illegal instruction 4" on Windows. Investigate at some point. 33 | linkArtifact(b, exe, target, if (target.isWindows()) .static else .exe_compiled, ""); 34 | 35 | const run_cmd = exe.run(); 36 | const exe_step = b.step(name, b.fmt("run {s}.zig", .{name})); 37 | exe_step.dependOn(&run_cmd.step); 38 | } 39 | 40 | // only mac and linux get the update_flecs command 41 | if (!target.isWindows()) { 42 | var exe = b.addSystemCommand(&[_][]const u8{ "zsh", ".vscode/update_flecs.sh" }); 43 | const exe_step = b.step("update_flecs", b.fmt("updates Flecs.h/c and runs translate-c", .{})); 44 | exe_step.dependOn(&exe.step); 45 | } 46 | 47 | const exe_tests = b.addTest("src/tests.zig"); 48 | exe_tests.setTarget(target); 49 | exe_tests.setBuildMode(b.standardReleaseOptions()); 50 | linkArtifact(b, exe_tests, target, if (target.isWindows()) .static else .exe_compiled, ""); 51 | 52 | const test_step = b.step("test", "Run unit tests"); 53 | test_step.dependOn(&exe_tests.step); 54 | } 55 | 56 | fn getAllExamples(b: *std.build.Builder, root_directory: []const u8) [][2][]const u8 { 57 | var list = std.ArrayList([2][]const u8).init(b.allocator); 58 | 59 | var recursor = struct { 60 | fn search(alloc: std.mem.Allocator, directory: []const u8, filelist: *std.ArrayList([2][]const u8)) void { 61 | var dir = std.fs.cwd().openDir(directory, .{ .iterate = true }) catch unreachable; 62 | defer dir.close(); 63 | 64 | var iter = dir.iterate(); 65 | while (iter.next() catch unreachable) |entry| { 66 | if (entry.kind == .File) { 67 | if (std.mem.endsWith(u8, entry.name, ".zig")) { 68 | const abs_path = std.fs.path.join(alloc, &[_][]const u8{ directory, entry.name }) catch unreachable; 69 | const name = std.fs.path.basename(abs_path); 70 | 71 | filelist.append([2][]const u8 {name[0..name.len - 4], abs_path}) catch unreachable; 72 | } 73 | } else if (entry.kind == .Directory) { 74 | const abs_path = std.fs.path.join(alloc, &[_][]const u8{ directory, entry.name }) catch unreachable; 75 | search(alloc, abs_path, filelist); 76 | } 77 | } 78 | } 79 | }.search; 80 | 81 | recursor(b.allocator, root_directory, &list); 82 | 83 | return list.toOwnedSlice(); 84 | } 85 | 86 | /// prefix_path is used to add package paths. It should be the the same path used to include this build file 87 | pub fn linkArtifact(b: *Builder, artifact: *std.build.LibExeObjStep, target: std.zig.CrossTarget, lib_type: LibType, comptime prefix_path: []const u8) void { 88 | if (prefix_path.len > 0 and !std.mem.endsWith(u8, prefix_path, "/")) @panic("prefix-path must end with '/' if it is not empty"); 89 | 90 | switch (lib_type) { 91 | .static => { 92 | const lib = b.addStaticLibrary("flecs", null); 93 | lib.setBuildMode(std.builtin.Mode.ReleaseFast); 94 | lib.setTarget(target); 95 | 96 | compileFlecs(b, lib, target, prefix_path); 97 | lib.install(); 98 | 99 | artifact.linkLibrary(lib); 100 | }, 101 | .exe_compiled => { 102 | compileFlecs(b, artifact, target, prefix_path); 103 | }, 104 | } 105 | 106 | artifact.addPackagePath("flecs", prefix_path ++ "src/flecs.zig"); 107 | } 108 | 109 | fn compileFlecs(b: *Builder, exe: *std.build.LibExeObjStep, target: std.zig.CrossTarget, comptime prefix_path: []const u8) void { 110 | exe.linkLibC(); 111 | exe.addIncludeDir(prefix_path ++ "src/c"); 112 | 113 | var buildFlags = std.ArrayList([]const u8).init(b.allocator); 114 | if (target.isWindows()) { 115 | exe.linkSystemLibrary("Ws2_32"); 116 | 117 | if (exe.build_mode != std.builtin.Mode.Debug) { 118 | buildFlags.append("-O2") catch unreachable; 119 | } else { 120 | buildFlags.append("-g") catch unreachable; 121 | } 122 | } 123 | 124 | exe.addCSourceFile(prefix_path ++ "src/c/flecs.c", buildFlags.items); 125 | } -------------------------------------------------------------------------------- /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, @intToFloat(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", .{@intToFloat(f64, end) / 1000000000}); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /examples/entities/entities_basics.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const flecs = @import("flecs"); 3 | 4 | const Position = struct { x: f32, y: f32 }; 5 | const Walking = struct {}; 6 | 7 | pub fn main() !void { 8 | var world = flecs.World.init(); 9 | 10 | // Create an entity with name Bob 11 | const bob = world.newEntityWithName("Bob"); 12 | 13 | // The set operation finds or creates a component, and sets it. 14 | bob.set(&Position{ .x = 10, .y = 20 }); 15 | 16 | // The add operation adds a component without setting a value. This is 17 | // useful for tags, or when adding a component with its default value. 18 | bob.add(Walking); 19 | 20 | // Get the value for the Position component 21 | if (bob.get(Position)) |position| { 22 | std.log.debug("position: {d}", .{position}); 23 | } 24 | 25 | // Overwrite the value of the Position component 26 | bob.set(&Position{ .x = 20, .y = 30 }); 27 | 28 | // Create another named entity 29 | const alice = world.newEntityWithName("Alice"); 30 | alice.set(&Position{ .x = 10, .y = 20 }); 31 | alice.add(Walking); 32 | 33 | // TODO: add a getType method and wrapper for flecs types 34 | // Print all the components the entity has. This will output: 35 | // Position, Walking, (Identifier,Name) 36 | // const alice_type = alice.getType(); 37 | 38 | // Remove tag 39 | alice.remove(Walking); 40 | 41 | // Iterate all entities with Position 42 | var term = flecs.Term(Position).init(world); 43 | defer term.deinit(); 44 | var it = term.iterator(); 45 | 46 | while (it.next()) |position| { 47 | std.log.debug("{s}: {d}", .{ it.entity().getName(), position }); 48 | } 49 | 50 | world.deinit(); 51 | } 52 | -------------------------------------------------------------------------------- /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 = flecs.c.EcsIn }); 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 = flecs.c.EcsOut }); 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 = flecs.c.EcsIn; 50 | query_t.filter.terms[2].oper = flecs.c.EcsOptional; 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 = EcsInOutFilter 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.ecs_world_t, 7 | id: flecs.EntityId, 8 | 9 | pub fn init(world: *flecs.c.ecs_world_t, 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.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, 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.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 is_added = false; 115 | var ptr = flecs.c.ecs_get_mut_id(self.world, self.id, meta.componentId(self.world, T), &is_added); 116 | if (ptr) |p| { 117 | return @ptrCast(*T, @alignCast(@alignOf(T), p)); 118 | } 119 | return null; 120 | } 121 | 122 | /// removes a component from an Entity 123 | pub fn remove(self: Entity, id_or_type: anytype) void { 124 | std.debug.assert(@TypeOf(id_or_type) == flecs.EntityId or @typeInfo(@TypeOf(id_or_type)) == .Type); 125 | const id = if (@TypeOf(id_or_type) == flecs.EntityId) id_or_type else meta.componentId(self.world, id_or_type); 126 | flecs.c.ecs_remove_id(self.world, self.id, id); 127 | } 128 | 129 | /// removes all components from an Entity 130 | pub fn clear(self: Entity) void { 131 | flecs.c.ecs_clear(self.world, self.id); 132 | } 133 | 134 | /// removes the entity from the world. Do not use this Entity after calling this! 135 | pub fn delete(self: Entity) void { 136 | flecs.c.ecs_delete(self.world, self.id); 137 | } 138 | 139 | /// returns true if the entity is alive 140 | pub fn isAlive(self: Entity) bool { 141 | return flecs.c.ecs_is_alive(self.world, self.id); 142 | } 143 | 144 | /// returns true if the entity has a matching component type 145 | pub fn has(self: Entity, id_or_type: anytype) bool { 146 | std.debug.assert(@TypeOf(id_or_type) == flecs.EntityId or @typeInfo(@TypeOf(id_or_type)) == .Type); 147 | const id = if (@TypeOf(id_or_type) == flecs.EntityId) id_or_type else meta.componentId(self.world, id_or_type); 148 | return flecs.c.ecs_has_id(self.world, self.id, id); 149 | } 150 | 151 | /// returns the type of the component, which contains all components 152 | pub fn getType(self: Entity) flecs.Type { 153 | return flecs.Type.init(self.world, flecs.c.ecs_get_type(self.world, self.id)); 154 | } 155 | 156 | /// prints a json representation of an Entity. Note that world.enable_type_reflection should be true to 157 | /// get component values as well. 158 | pub fn printJsonRepresentation(self: Entity) void { 159 | var str = flecs.c.ecs_entity_to_json(self.world, self.id, null); 160 | std.debug.print("{s}\n", .{str}); 161 | flecs.c.ecs_os_api.free_.?(str); 162 | } 163 | }; 164 | -------------------------------------------------------------------------------- /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.ecs_filter_t = 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.ecs_iter_t, 14 | index: usize = 0, 15 | 16 | pub fn init(iter: flecs.c.ecs_iter_t) @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_term_is_readonly(&self.iter, @intCast(i32, 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(usize, 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_term_is_readonly(&self.iter, @intCast(i32, 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, @intCast(usize, 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.ecs_filter_desc_t) @This() { 90 | var filter = @This(){ 91 | .world = world, 92 | .filter = std.heap.c_allocator.create(flecs.c.ecs_filter_t) catch unreachable, 93 | }; 94 | std.debug.assert(flecs.c.ecs_filter_init(world.world, filter.filter, desc) == 0); 95 | return filter; 96 | } 97 | 98 | pub fn deinit(self: *@This()) void { 99 | flecs.c.ecs_filter_fini(self.filter); 100 | std.heap.c_allocator.destroy(self.filter); 101 | } 102 | 103 | pub fn asString(self: *@This()) [*c]u8 { 104 | return flecs.c.ecs_filter_str(self.world.world, self.filter); 105 | } 106 | 107 | pub fn filterIterator(self: *@This()) FilterIterator { 108 | return FilterIterator.init(flecs.c.ecs_filter_iter(self.world.world, self.filter)); 109 | } 110 | 111 | /// gets an iterator that let you iterate the tables and then it provides an inner iterator to interate entities 112 | pub fn tableIterator(self: *@This(), comptime Components: type) flecs.TableIterator(Components) { 113 | temp_iter_storage = flecs.c.ecs_filter_iter(self.world.world, self.filter); 114 | return flecs.TableIterator(Components).init(&temp_iter_storage, flecs.c.ecs_filter_next); 115 | } 116 | 117 | // storage for the iterator so it can be passed by reference. Do not in-flight two Filters at once! 118 | var temp_iter_storage: flecs.c.ecs_iter_t = undefined; 119 | 120 | /// gets an iterator that iterates all matched entities from all tables in one iteration. Do not create more than one at a time! 121 | pub fn iterator(self: *@This(), comptime Components: type) flecs.Iterator(Components) { 122 | temp_iter_storage = flecs.c.ecs_filter_iter(self.world.world, self.filter); 123 | return flecs.Iterator(Components).init(&temp_iter_storage, flecs.c.ecs_filter_next); 124 | } 125 | 126 | /// allows either a function that takes 1 parameter (a struct with fields that match the components in the query) or multiple paramters 127 | /// (each param should match the components in the query in order) 128 | pub fn each(self: *@This(), comptime function: anytype) void { 129 | // dont allow BoundFn 130 | std.debug.assert(@typeInfo(@TypeOf(function)) == .Fn); 131 | comptime var arg_count = meta.argCount(function); 132 | 133 | if (arg_count == 1) { 134 | const Components = @typeInfo(@TypeOf(function)).Fn.args[0].arg_type.?; 135 | 136 | var iter = self.iterator(Components); 137 | while (iter.next()) |comps| { 138 | @call(.{ .modifier = .always_inline }, function, .{comps}); 139 | } 140 | } else { 141 | const Components = std.meta.ArgsTuple(@TypeOf(function)); 142 | 143 | var iter = self.iterator(Components); 144 | while (iter.next()) |comps| { 145 | @call(.{ .modifier = .always_inline }, function, meta.fieldsTuple(comps)); 146 | } 147 | } 148 | } 149 | }; 150 | -------------------------------------------------------------------------------- /src/flecs.zig: -------------------------------------------------------------------------------- 1 | pub const c = @import("c.zig"); 2 | const std = @import("std"); 3 | 4 | // TODO: why does translate-c fail for cImport but succeeds when used directly? 5 | // const flecs = @cImport(@cInclude("flecs.h")); 6 | // pub usingnamespace flecs; 7 | 8 | pub const queries = @import("queries.zig"); 9 | 10 | pub const EntityId = c.ecs_entity_t; 11 | pub const Entity = @import("entity.zig").Entity; 12 | pub const World = @import("world.zig").World; 13 | pub const QueryBuilder = @import("query_builder.zig").QueryBuilder; 14 | pub const Term = @import("term.zig").Term; 15 | pub const Filter = @import("filter.zig").Filter; 16 | pub const Query = @import("query.zig").Query; 17 | pub const Type = @import("type.zig").Type; 18 | 19 | pub const Iterator = @import("iterator.zig").Iterator; 20 | pub const TableIterator = @import("table_iterator.zig").TableIterator; 21 | 22 | pub usingnamespace @import("utils.zig"); 23 | pub const meta = @import("meta.zig"); 24 | 25 | // Builtin pipeline tags 26 | pub const Phase = enum(c.ecs_id_t) { 27 | monitor = c.ECS_HI_COMPONENT_ID + 61, 28 | inactive = c.ECS_HI_COMPONENT_ID + 63, 29 | pipeline = c.ECS_HI_COMPONENT_ID + 64, 30 | pre_frame = c.ECS_HI_COMPONENT_ID + 65, 31 | on_load = c.ECS_HI_COMPONENT_ID + 66, 32 | post_load = c.ECS_HI_COMPONENT_ID + 67, 33 | pre_update = c.ECS_HI_COMPONENT_ID + 68, 34 | on_update = c.ECS_HI_COMPONENT_ID + 69, 35 | on_validate = c.ECS_HI_COMPONENT_ID + 70, 36 | post_update = c.ECS_HI_COMPONENT_ID + 71, 37 | pre_store = c.ECS_HI_COMPONENT_ID + 72, 38 | on_store = c.ECS_HI_COMPONENT_ID + 73, 39 | post_frame = c.ECS_HI_COMPONENT_ID + 74, 40 | }; 41 | 42 | pub const Event = enum(c.ecs_id_t) { 43 | // Event. Triggers when an id (component, tag, pair) is added to an entity 44 | on_add = c.ECS_HI_COMPONENT_ID + 30, 45 | // Event. Triggers when an id (component, tag, pair) is removed from an entity 46 | on_remove = c.ECS_HI_COMPONENT_ID + 31, 47 | // Event. Triggers when a component is set for an entity 48 | on_set = c.ECS_HI_COMPONENT_ID + 32, 49 | // Event. Triggers when a component is unset for an entity 50 | un_set = c.ECS_HI_COMPONENT_ID + 33, 51 | // Event. Triggers when an entity is deleted. 52 | on_delete = c.ECS_HI_COMPONENT_ID + 34, 53 | // Event. Exactly-once trigger for when an entity matches/unmatches a filter 54 | monitor = c.ECS_HI_COMPONENT_ID + 61, 55 | }; 56 | 57 | pub const OperKind = enum(c_int) { 58 | and_ = c.EcsAnd, 59 | or_ = c.EcsOr, 60 | not = c.EcsNot, 61 | optional = c.EcsOptional, 62 | and_from = c.EcsAndFrom, 63 | or_from = c.EcsOrFrom, 64 | not_from = c.EcsNotFrom, 65 | }; 66 | 67 | pub const InOutKind = enum(c_int) { 68 | default = c.EcsInOutDefault, // in_out for regular terms, in for shared terms 69 | filter = c.EcsInOutFilter, // neither read nor written. Cannot have a query term. 70 | in_out = c.EcsInOut, // read/write 71 | in = c.EcsIn, // read only. Query term is const. 72 | out = c.EcsOut, // write only 73 | }; 74 | 75 | pub fn pairFirst(id: EntityId) u32 { 76 | return @truncate(u32, (id & c.ECS_COMPONENT_MASK) >> 32); 77 | } 78 | 79 | pub fn pairSecond(id: EntityId) u32 { 80 | return @truncate(u32, id); 81 | } 82 | -------------------------------------------------------------------------------- /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.ecs_iter_t, 19 | inner_iter: ?TableColumns = null, 20 | index: usize = 0, 21 | nextFn: fn ([*c]flecs.c.ecs_iter_t) callconv(.C) bool, 22 | 23 | pub fn init(iter: *flecs.c.ecs_iter_t, nextFn: fn ([*c]flecs.c.ecs_iter_t) 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.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) |field, i| { 88 | // skip filters and EcsNothing masks since they arent returned when we iterate 89 | while (self.iter.terms[index].inout == flecs.c.EcsInOutFilter or self.iter.terms[index].subj.set.mask == flecs.c.EcsNothing) : (index += 1) {} 90 | 91 | const is_optional = @typeInfo(field.field_type) == .Optional; 92 | const col_type = meta.FinalChild(field.field_type); 93 | if (meta.isConst(field.field_type)) std.debug.assert(flecs.c.ecs_term_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].index; 97 | const raw_term_id = flecs.c.ecs_term_id(self.iter, @intCast(usize, 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 | _ = T; 22 | return &(struct { 23 | pub var handle: flecs.EntityId = std.math.maxInt(u64); 24 | }.handle); 25 | } 26 | 27 | /// gets the EntityId for T creating it if it doesn't already exist 28 | pub fn componentId(world: *flecs.c.ecs_world_t, comptime T: type) flecs.EntityId { 29 | var handle = componentHandle(T); 30 | if (handle.* < std.math.maxInt(flecs.EntityId)) { 31 | return handle.*; 32 | } 33 | 34 | if (@sizeOf(T) == 0) { 35 | var desc = std.mem.zeroInit(flecs.c.ecs_entity_desc_t, .{ .name = @typeName(T) }); 36 | handle.* = flecs.c.ecs_entity_init(world, &desc); 37 | } else { 38 | var desc = std.mem.zeroInit(flecs.c.ecs_component_desc_t, .{ 39 | .entity = std.mem.zeroInit(flecs.c.ecs_entity_desc_t, .{ .name = @typeName(T) }), 40 | .size = @sizeOf(T), 41 | .alignment = @alignOf(T), 42 | }); 43 | handle.* = flecs.c.ecs_component_init(world, &desc); 44 | } 45 | 46 | // allow disabling reflection data with a root bool 47 | if (!@hasDecl(@import("root"), "disable_reflection") or !@as(bool, @field(@import("root"), "disable_reflection"))) 48 | registerReflectionData(world, T, handle.*); 49 | 50 | return handle.*; 51 | } 52 | 53 | /// given a pointer or optional pointer returns the base struct type. 54 | pub fn FinalChild(comptime T: type) type { 55 | switch (@typeInfo(T)) { 56 | .Pointer => |info| switch (info.size) { 57 | .One => switch (@typeInfo(info.child)) { 58 | .Struct => return info.child, 59 | .Optional => |opt_info| return opt_info.child, 60 | else => {}, 61 | }, 62 | else => {}, 63 | }, 64 | .Optional => |info| return FinalChild(info.child), 65 | .Struct => return T, 66 | else => {}, 67 | } 68 | @compileError("Expected pointer or optional pointer, found '" ++ @typeName(T) ++ "'"); 69 | } 70 | 71 | /// given a pointer or optional pointer returns a pointer-to-many. constness and optionality are retained. 72 | pub fn PointerToMany(comptime T: type) type { 73 | var is_const = false; 74 | var is_optional = false; 75 | var PointerT = T; 76 | 77 | switch (@typeInfo(T)) { 78 | .Optional => |opt_info| switch (@typeInfo(opt_info.child)) { 79 | .Pointer => |ptr_info| { 80 | is_const = ptr_info.is_const; 81 | is_optional = true; 82 | PointerT = opt_info.child; 83 | }, 84 | else => unreachable, 85 | }, 86 | .Pointer => |ptr_info| is_const = ptr_info.is_const, 87 | else => unreachable, 88 | } 89 | 90 | const info = @typeInfo(PointerT).Pointer; 91 | const InnerType = @Type(.{ 92 | .Pointer = .{ 93 | .size = .Many, 94 | .is_const = is_const, 95 | .is_volatile = info.is_volatile, 96 | .alignment = info.alignment, 97 | .address_space = info.address_space, 98 | .child = info.child, 99 | .is_allowzero = info.is_allowzero, 100 | .sentinel = null, 101 | }, 102 | }); 103 | 104 | if (is_optional) return @Type(.{ 105 | .Optional = .{ 106 | .child = InnerType, 107 | }, 108 | }); 109 | 110 | return InnerType; 111 | } 112 | 113 | /// gets the number of arguments in the function 114 | pub fn argCount(comptime function: anytype) usize { 115 | return switch (@typeInfo(@TypeOf(function))) { 116 | .BoundFn => |func_info| func_info.args.len, 117 | .Fn => |func_info| func_info.args.len, 118 | else => assert("invalid function"), 119 | }; 120 | } 121 | 122 | /// given a query struct, returns a type with the exact same fields except the fields are made pointer-to-many. 123 | /// constness and optionality are retained. 124 | pub fn TableIteratorData(comptime Components: type) type { 125 | const src_fields = std.meta.fields(Components); 126 | const StructField = std.builtin.TypeInfo.StructField; 127 | var fields: [src_fields.len]StructField = undefined; 128 | 129 | for (src_fields) |field, i| { 130 | const T = FinalChild(field.field_type); 131 | fields[i] = .{ 132 | .name = field.name, 133 | .field_type = PointerToMany(field.field_type), 134 | .default_value = null, 135 | .is_comptime = false, 136 | .alignment = @alignOf(*T), 137 | }; 138 | } 139 | 140 | return @Type(.{ .Struct = .{ 141 | .layout = .Auto, 142 | .fields = &fields, 143 | .decls = &[_]std.builtin.TypeInfo.Declaration{}, 144 | .is_tuple = false, 145 | } }); 146 | } 147 | 148 | /// returns a tuple consisting of the field values of value 149 | pub fn fieldsTuple(value: anytype) FieldsTupleType(@TypeOf(value)) { 150 | const T = @TypeOf(value); 151 | assert(@typeInfo(T) == .Struct); 152 | const ti = @typeInfo(T).Struct; 153 | const FieldsTuple = FieldsTupleType(T); 154 | 155 | var tuple: FieldsTuple = undefined; 156 | comptime var i = 0; 157 | inline while (i < ti.fields.len) : (i += 1) { 158 | tuple[i] = @field(value, ti.fields[i].name); 159 | } 160 | 161 | return tuple; 162 | } 163 | 164 | /// returns the Type of the tuple version of T 165 | pub fn FieldsTupleType(comptime T: type) type { 166 | const ti = @typeInfo(T).Struct; 167 | return @Type(.{ 168 | .Struct = .{ 169 | .layout = ti.layout, 170 | .fields = ti.fields, 171 | .decls = &[0]std.builtin.TypeInfo.Declaration{}, 172 | .is_tuple = true, 173 | }, 174 | }); 175 | } 176 | 177 | pub fn validateIterator(comptime Components: type, iter: *const flecs.c.ecs_iter_t) void { 178 | if (@import("builtin").mode == .Debug) { 179 | var index: usize = 0; 180 | const component_info = @typeInfo(Components).Struct; 181 | 182 | inline for (component_info.fields) |field| { 183 | // skip filters since they arent returned when we iterate 184 | while (iter.terms[index].inout == flecs.c.EcsInOutFilter) : (index += 1) {} 185 | const is_optional = @typeInfo(field.field_type) == .Optional; 186 | const col_type = FinalChild(field.field_type); 187 | const type_entity = meta.componentHandle(col_type).*; 188 | 189 | // ensure order matches for terms vs struct fields. note that pairs need to have their first term extracted. 190 | if (flecs.c.ecs_id_is_pair(iter.terms[index].id)) { 191 | 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 }); 192 | } else { 193 | 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 }); 194 | } 195 | 196 | // validate readonly (non-ptr types in the struct) matches up with the inout 197 | const is_const = isConst(field.field_type); 198 | if (is_const) assert(iter.terms[index].inout == flecs.c.EcsIn); 199 | if (iter.terms[index].inout == flecs.c.EcsIn) assert(is_const); 200 | 201 | // validate that optionals (?* types in the struct) match up with valid opers 202 | if (is_optional) assert(iter.terms[index].oper == flecs.c.EcsOr or iter.terms[index].oper == flecs.c.EcsOptional); 203 | if (iter.terms[index].oper == flecs.c.EcsOr or iter.terms[index].oper == flecs.c.EcsOptional) assert(is_optional); 204 | 205 | index += 1; 206 | } 207 | } 208 | } 209 | 210 | /// ensures an orderBy function for a query/system is legit 211 | pub fn validateOrderByFn(comptime func: anytype) void { 212 | if (@import("builtin").mode == .Debug) { 213 | const ti = @typeInfo(@TypeOf(func)); 214 | assert(ti == .Fn); 215 | assert(ti.Fn.args.len == 4); 216 | 217 | // args are: EntityId, *const T, EntityId, *const T 218 | assert(ti.Fn.args[0].arg_type.? == flecs.EntityId); 219 | assert(ti.Fn.args[2].arg_type.? == flecs.EntityId); 220 | assert(ti.Fn.args[1].arg_type.? == ti.Fn.args[3].arg_type.?); 221 | assert(isConst(ti.Fn.args[1].arg_type.?)); 222 | assert(@typeInfo(ti.Fn.args[1].arg_type.?) == .Pointer); 223 | } 224 | } 225 | 226 | /// ensures the order by type is in the Components struct and that that it isnt an optional term 227 | pub fn validateOrderByType(comptime Components: type, comptime T: type) void { 228 | if (@import("builtin").mode == .Debug) { 229 | var valid = false; 230 | 231 | const component_info = @typeInfo(Components).Struct; 232 | inline for (component_info.fields) |field| { 233 | if (FinalChild(field.field_type) == T) { 234 | valid = true; 235 | } 236 | } 237 | 238 | // allow types in Filter with no fields 239 | if (@hasDecl(Components, "modifiers")) { 240 | inline for (Components.modifiers) |inout_tuple| { 241 | const ti = TermInfo.init(inout_tuple); 242 | if (ti.inout == flecs.c.EcsInOutFilter) { 243 | if (ti.term_type == T) 244 | valid = true; 245 | } 246 | } 247 | } 248 | 249 | assertMsg(valid, "type {any} was not found in the struct!", .{T}); 250 | } 251 | } 252 | 253 | /// checks a Pointer or Optional for constness. Any other types passed in will error. 254 | pub fn isConst(comptime T: type) bool { 255 | switch (@typeInfo(T)) { 256 | .Pointer => |ptr| return ptr.is_const, 257 | .Optional => |opt| { 258 | switch (@typeInfo(opt.child)) { 259 | .Pointer => |ptr| return ptr.is_const, 260 | else => {}, 261 | } 262 | }, 263 | else => {}, 264 | } 265 | 266 | @compileError("Invalid type passed to isConst: " ++ @typeName(T)); 267 | } 268 | 269 | /// https://github.com/SanderMertens/flecs/tree/master/examples/c/reflection 270 | fn registerReflectionData(world: *flecs.c.ecs_world_t, comptime T: type, entity: flecs.EntityId) void { 271 | var entityDesc = std.mem.zeroInit(flecs.c.ecs_entity_desc_t, .{ .entity = entity }); 272 | var desc = std.mem.zeroInit(flecs.c.ecs_struct_desc_t, .{ .entity = entityDesc }); 273 | 274 | switch (@typeInfo(T)) { 275 | .Struct => |si| { 276 | // tags have no size so ignore them 277 | if (@sizeOf(T) == 0) return; 278 | 279 | inline for (si.fields) |field, i| { 280 | var member = std.mem.zeroes(flecs.c.ecs_member_t); 281 | member.name = field.name.ptr; 282 | 283 | // TODO: support nested structs 284 | member.type = switch (field.field_type) { 285 | // Struct => componentId(field.field_type), 286 | bool => flecs.c.FLECS__Eecs_bool_t, 287 | f32 => flecs.c.FLECS__Eecs_f32_t, 288 | f64 => flecs.c.FLECS__Eecs_f64_t, 289 | u8 => flecs.c.FLECS__Eecs_u8_t, 290 | u16 => flecs.c.FLECS__Eecs_u16_t, 291 | u32 => flecs.c.FLECS__Eecs_u32_t, 292 | flecs.EntityId => blk: { 293 | // bit of a hack, but if the field name has "entity" in it we consider it an Entity reference 294 | if (std.mem.indexOf(u8, field.name, "entity") != null) 295 | break :blk flecs.c.FLECS__Eecs_entity_t; 296 | break :blk flecs.c.FLECS__Eecs_u64_t; 297 | }, 298 | i8 => flecs.c.FLECS__Eecs_i8_t, 299 | i16 => flecs.c.FLECS__Eecs_i16_t, 300 | i32 => flecs.c.FLECS__Eecs_i32_t, 301 | i64 => flecs.c.FLECS__Eecs_i64_t, 302 | usize => flecs.c.FLECS__Eecs_uptr_t, 303 | []const u8 => flecs.c.FLECS__Eecs_string_t, 304 | [*]const u8 => flecs.c.FLECS__Eecs_string_t, 305 | else => switch (@typeInfo(field.field_type)) { 306 | .Pointer => flecs.c.FLECS__Eecs_uptr_t, 307 | 308 | .Struct => componentId(world, field.field_type), 309 | 310 | .Enum => blk: { 311 | var enum_desc = std.mem.zeroes(flecs.c.ecs_enum_desc_t); 312 | enum_desc.entity.entity = meta.componentHandle(T).*; 313 | 314 | inline for (@typeInfo(field.field_type).Enum.fields) |f, index| { 315 | enum_desc.constants[index] = std.mem.zeroInit(flecs.c.ecs_enum_constant_t, .{ 316 | .name = f.name.ptr, 317 | .value = @intCast(i32, f.value), 318 | }); 319 | } 320 | 321 | break :blk flecs.c.ecs_enum_init(world, &enum_desc); 322 | }, 323 | 324 | else => { 325 | std.debug.print("unhandled field type: {any}, ti: {any}\n", .{ field.field_type, @typeInfo(field.field_type) }); 326 | unreachable; 327 | }, 328 | }, 329 | }; 330 | desc.members[i] = member; 331 | } 332 | _ = flecs.c.ecs_struct_init(world, &desc); 333 | }, 334 | else => unreachable, 335 | } 336 | } 337 | 338 | /// given a struct of Components with optional embedded "metadata", "name", "order_by" data it generates an ecs_filter_desc_t 339 | pub fn generateFilterDesc(world: flecs.World, comptime Components: type) flecs.c.ecs_filter_desc_t { 340 | assert(@typeInfo(Components) == .Struct); 341 | var desc = std.mem.zeroes(flecs.c.ecs_filter_desc_t); 342 | 343 | // first, extract what we can from the Components fields 344 | const component_info = @typeInfo(Components).Struct; 345 | inline for (component_info.fields) |field, i| { 346 | desc.terms[i].id = world.componentId(meta.FinalChild(field.field_type)); 347 | 348 | if (@typeInfo(field.field_type) == .Optional) 349 | desc.terms[i].oper = flecs.c.EcsOptional; 350 | 351 | if (meta.isConst(field.field_type)) 352 | desc.terms[i].inout = flecs.c.EcsIn; 353 | } 354 | 355 | // optionally, apply any additional modifiers if present. Keep track of the term_index in case we have to add Or + Filters or Ands 356 | var next_term_index = component_info.fields.len; 357 | if (@hasDecl(Components, "modifiers")) { 358 | inline for (Components.modifiers) |inout_tuple| { 359 | const ti = TermInfo.init(inout_tuple); 360 | // std.debug.print("{any}: {any}\n", .{ inout_tuple, ti }); 361 | 362 | if (getTermIndex(ti.term_type, ti.field, &desc, component_info.fields)) |term_index| { 363 | // Not terms should not be present in the Components struct 364 | assert(ti.oper != flecs.c.EcsNot); 365 | 366 | // 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 367 | // having the term present in the query. For that case, we will leave both optionals and add the two Or terms. 368 | if (ti.inout == flecs.c.EcsInOutFilter) { 369 | assert(ti.oper == flecs.c.EcsOr); 370 | if (ti.or_term_type) |or_term_type| { 371 | // ensure the term is optional. If the second Or term is present ensure it is optional as well. 372 | assert(desc.terms[term_index].oper == flecs.c.EcsOptional); 373 | if (getTermIndex(or_term_type, null, &desc, component_info.fields)) |or_term_index| { 374 | assert(desc.terms[or_term_index].oper == flecs.c.EcsOptional); 375 | } 376 | 377 | desc.terms[next_term_index].id = world.componentId(ti.term_type); 378 | desc.terms[next_term_index].inout = ti.inout; 379 | desc.terms[next_term_index].oper = ti.oper; 380 | next_term_index += 1; 381 | 382 | desc.terms[next_term_index].id = world.componentId(or_term_type); 383 | desc.terms[next_term_index].inout = ti.inout; 384 | desc.terms[next_term_index].oper = ti.oper; 385 | next_term_index += 1; 386 | } else unreachable; 387 | } else { 388 | if (ti.inout == flecs.c.EcsOut) { 389 | assert(desc.terms[term_index].inout == flecs.c.EcsInOutDefault); 390 | desc.terms[term_index].inout = ti.inout; 391 | } 392 | 393 | // the only valid oper left is Or since Not terms cant be in Components struct 394 | if (ti.oper == flecs.c.EcsOr) { 395 | assert(desc.terms[term_index].oper == flecs.c.EcsOptional); 396 | 397 | if (getTermIndex(ti.or_term_type.?, null, &desc, component_info.fields)) |or_term_index| { 398 | assert(desc.terms[or_term_index].oper == flecs.c.EcsOptional); 399 | desc.terms[or_term_index].oper = ti.oper; 400 | } else unreachable; 401 | desc.terms[term_index].oper = ti.oper; 402 | } 403 | } 404 | 405 | if (ti.mask != 0) { 406 | assert(desc.terms[term_index].subj.set.mask == 0); 407 | desc.terms[term_index].subj.set.mask = ti.mask; 408 | } 409 | 410 | if (ti.obj_type) |obj_type| { 411 | desc.terms[term_index].id = world.pair(ti.relation_type.?, obj_type); 412 | } 413 | } else { 414 | // the term wasnt found so we must have either a Filter, Not or EcsNothing mask 415 | if (ti.inout != flecs.c.EcsInOutFilter and ti.oper != flecs.c.EcsNot and ti.mask != flecs.c.EcsNothing) 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", .{}); 416 | if (ti.inout == flecs.c.EcsInOutFilter) { 417 | desc.terms[next_term_index].id = world.componentId(ti.term_type); 418 | desc.terms[next_term_index].inout = ti.inout; 419 | next_term_index += 1; 420 | } else if (ti.oper == flecs.c.EcsNot) { 421 | desc.terms[next_term_index].id = world.componentId(ti.term_type); 422 | desc.terms[next_term_index].oper = ti.oper; 423 | next_term_index += 1; 424 | } else if (ti.mask == flecs.c.EcsNothing) { 425 | desc.terms[next_term_index].id = world.componentId(ti.term_type); 426 | desc.terms[next_term_index].inout = ti.inout; 427 | desc.terms[next_term_index].subj.set.mask = ti.mask; 428 | next_term_index += 1; 429 | } else { 430 | std.debug.print("invalid inout applied to a term not in the query. only Not and Filter are allowed for terms not present.\n", .{}); 431 | } 432 | } 433 | } 434 | } 435 | 436 | // optionally add the expression string 437 | if (@hasDecl(Components, "expr")) { 438 | assertMsg(std.meta.Elem(@TypeOf(Components.expr)) == u8, "expr must be a const string. Found: {s}", .{std.meta.Elem(@TypeOf(Components.expr))}); 439 | desc.expr = Components.expr; 440 | } 441 | 442 | return desc; 443 | } 444 | 445 | /// gets the index into the terms array of this type or null if it isnt found (likely a new filter term) 446 | pub fn getTermIndex(comptime T: type, field_name: ?[]const u8, filter: *flecs.c.ecs_filter_desc_t, fields: []const std.builtin.TypeInfo.StructField) ?usize { 447 | if (fields.len == 0) return null; 448 | const comp_id = meta.componentHandle(T).*; 449 | 450 | // 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 451 | const named_field_index: ?usize = if (field_name) |fname| blk: { 452 | const f_idx = inline for (fields) |field, field_index| { 453 | if (std.mem.eql(u8, field.name, fname)) 454 | break field_index; 455 | }; 456 | break :blk f_idx; 457 | } else null; 458 | 459 | var i: usize = 0; 460 | while (i < fields.len) : (i += 1) { 461 | if (filter.terms[i].id == comp_id) { 462 | if (named_field_index == null) return i; 463 | 464 | // we have a field_name so make sure the term index matches the named field index 465 | if (named_field_index == i) return i; 466 | } 467 | } 468 | return null; 469 | } 470 | -------------------------------------------------------------------------------- /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: []const u8) type { 20 | return struct { 21 | pub const inout: flecs.InOutKind = .out; 22 | pub const field = field; 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 = flecs.c.EcsNothing; 72 | term_type: T, 73 | }; 74 | } 75 | 76 | pub fn Mask(comptime T: type, mask: u8) type { 77 | return struct { 78 | pub const mask: u8 = mask; 79 | term_type: T, 80 | }; 81 | } 82 | 83 | pub fn MaskI(comptime T: type, mask: u8, field: []const u8) type { 84 | return struct { 85 | pub const field = field; 86 | pub const mask: u8 = mask; 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: []const u8) type { 99 | return struct { 100 | pub const field = field; 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.ecs_query_t, 8 | 9 | pub fn init(world: flecs.World, desc: *flecs.c.ecs_query_desc_t) @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.ecs_iter_t) 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.ecs_iter_t = 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.ecs_system_desc_t, 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.ecs_system_desc_t), 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 = flecs.c.EcsIn; 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 = flecs.c.EcsOut; 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 = flecs.c.EcsInOutFilter; 41 | self.terms_count += 1; 42 | return self; 43 | } 44 | 45 | pub fn without(self: *@This(), comptime T: type) *@This() { 46 | self.desc.filter.terms[self.terms_count] = std.mem.zeroInit(flecs.c.ecs_term_t, .{ 47 | .id = self.world.componentId(T), 48 | .oper = flecs.c.EcsNot, 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.ecs_term_t, .{ 56 | .id = self.world.componentId(T), 57 | .oper = flecs.c.EcsOptional, 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.ecs_term_t, .{ 65 | .id = self.world.componentId(T1), 66 | .oper = flecs.c.EcsOr, 67 | }); 68 | self.terms_count += 1; 69 | self.desc.query.filter.terms[self.terms_count] = std.mem.zeroInit(flecs.c.ecs_term_t, .{ 70 | .id = self.world.componentId(T2), 71 | .oper = flecs.c.EcsOr, 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 = flecs.c.EcsInOutFilter; 81 | self.desc.query.filter.terms[self.terms_count - 2].inout = flecs.c.EcsInOutFilter; 82 | return self; 83 | } 84 | 85 | /// inject a plain old string expression into the builder 86 | pub fn expression(self: *@This(), expr: [*c]const u8) *@This() { 87 | self.desc.filter.expr = expr; 88 | return self; 89 | } 90 | 91 | pub fn singleton(self: *@This(), comptime T: type, entity: flecs.EntityId) *@This() { 92 | self.desc.filter.terms[self.terms_count] = std.mem.zeroInit(flecs.c.ecs_term_t, .{ .id = self.world.componentId(T) }); 93 | self.desc.filter.terms[self.terms_count].subj.entity = entity; 94 | self.terms_count += 1; 95 | return self; 96 | } 97 | 98 | pub fn buildFilter(self: *@This()) flecs.Filter { 99 | return flecs.Filter.init(self.world, &self.desc.query.filter); 100 | } 101 | 102 | pub fn buildQuery(self: *@This()) flecs.Query { 103 | return flecs.Query.init(self.world, &self.desc.query); 104 | } 105 | 106 | /// queries/system only 107 | pub fn orderBy(self: *@This(), comptime T: type, orderByFn: fn (flecs.EntityId, ?*const anyopaque, flecs.EntityId, ?*const anyopaque) callconv(.C) c_int) *@This() { 108 | self.desc.query.order_by_component = self.world.componentId(T); 109 | self.desc.query.order_by = orderByFn; 110 | return self; 111 | } 112 | 113 | /// queries/system only 114 | pub fn orderByEntity(self: *@This(), orderByFn: fn (flecs.EntityId, ?*const anyopaque, flecs.EntityId, ?*const anyopaque) callconv(.C) c_int) *@This() { 115 | self.desc.query.order_by = orderByFn; 116 | return self; 117 | } 118 | 119 | /// systems only. This system callback will be called at least once for each table that matches the query 120 | pub fn callback(self: *@This(), cb: fn ([*c]flecs.c.ecs_iter_t) callconv(.C) void) *@This() { 121 | self.callback = cb; 122 | return self; 123 | } 124 | 125 | /// systems only. This system callback will only be called once. The iterator should then be iterated with ecs_iter_next. 126 | pub fn run(self: *@This(), cb: fn ([*c]flecs.c.ecs_iter_t) callconv(.C) void) *@This() { 127 | self.desc.run = cb; 128 | return self; 129 | } 130 | }; 131 | -------------------------------------------------------------------------------- /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.ecs_iter_t, 17 | nextFn: fn ([*c]flecs.c.ecs_iter_t) callconv(.C) bool, 18 | 19 | pub fn init(iter: *flecs.c.ecs_iter_t, nextFn: fn ([*c]flecs.c.ecs_iter_t) 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) |field, i| { 42 | // skip filters since they arent returned when we iterate 43 | while (self.iter.terms[index].inout == flecs.c.EcsInOutFilter) : (index += 1) {} 44 | 45 | const is_optional = @typeInfo(field.field_type) == .Optional; 46 | const col_type = meta.FinalChild(field.field_type); 47 | if (meta.isConst(field.field_type)) std.debug.assert(flecs.c.ecs_term_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(usize, 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.ecs_term_t, 14 | 15 | const Iterator = struct { 16 | iter: flecs.c.ecs_iter_t, 17 | index: usize = 0, 18 | 19 | pub fn init(iter: flecs.c.ecs_iter_t) 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.ecs_iter_t, 41 | index: usize = 0, 42 | 43 | pub fn init(iter: flecs.c.ecs_iter_t) 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.ecs_term_t, .{ .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.ecs_term_t, .{ .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.ecs_inout_kind_t = flecs.c.EcsInOutDefault, 13 | oper: flecs.c.ecs_oper_kind_t = flecs.c.EcsAnd, 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 = @enumToInt(@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 = @enumToInt(@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.field_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].field_type; 50 | term_info.or_term_type = fields[1].field_type; 51 | 52 | if (term_info.oper != 0) @compileError("Bad oper in query. Previous modifier already set oper. " ++ @typeName(T)); 53 | term_info.oper = flecs.c.EcsOr; 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].field_type; 61 | 62 | if (std.meta.fieldIndex(t, "relation_type")) |relation_idx| { 63 | term_info.relation_type = fields[relation_idx].field_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].field_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 == flecs.c.EcsInOutFilter and self.oper == flecs.c.EcsNot) @compileError("Filter cant be combined with Not"); 85 | if (self.oper == flecs.c.EcsNot and self.inout != flecs.c.EcsInOutDefault) @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 | flecs.c.EcsInOutDefault => "InOutDefault", 91 | flecs.c.EcsInOutFilter => "Filter", 92 | flecs.c.EcsIn => "In", 93 | flecs.c.EcsOut => "Out", 94 | else => unreachable, 95 | }; 96 | const oper = switch (value.oper) { 97 | flecs.c.EcsAnd => "And", 98 | flecs.c.EcsOr => "Or", 99 | flecs.c.EcsNot => "Not", 100 | flecs.c.EcsOptional => "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.ecs_world_t, 6 | type: flecs.c.ecs_type_t, 7 | 8 | pub fn init(world: *flecs.c.ecs_world_t, t: flecs.c.ecs_type_t) 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(usize, 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 @ptrCast([*c]const flecs.EntityId, @alignCast(@alignOf(u64), 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.ecs_iter_t, comptime T: type, index: i32) [*]T { 6 | var col = flecs.c.ecs_term_w_size(iter, @sizeOf(T), index); 7 | return @ptrCast([*]T, @alignCast(@alignOf(T), 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.ecs_iter_t, comptime T: type, index: i32) ?[*]T { 12 | if (index <= 0) return null; 13 | var col = flecs.c.ecs_term_w_size(iter, @sizeOf(T), index); 14 | if (col == null) return null; 15 | return @ptrCast([*]T, @alignCast(@alignOf(T), 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.ecs_iter_t, 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([*]T, @alignCast(@alignOf(T), 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(*const T, @alignCast(@alignOf(T), 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.ecs_entity_t, ?*const anyopaque, flecs.c.ecs_entity_t, ?*const anyopaque) callconv(.C) c_int; 8 | 9 | fn dummyFn(_: [*c]flecs.c.ecs_iter_t) callconv(.C) void {} 10 | 11 | pub const World = struct { 12 | world: *flecs.c.ecs_world_t, 13 | 14 | pub fn init() World { 15 | return .{ .world = flecs.c.ecs_init().? }; 16 | } 17 | 18 | pub fn deinit(self: *World) void { 19 | _ = flecs.c.ecs_fini(self.world); 20 | } 21 | 22 | pub fn setTargetFps(self: World, fps: f32) void { 23 | flecs.c.ecs_set_target_fps(self.world, fps); 24 | } 25 | 26 | /// available at: https://www.flecs.dev/explorer/?remote=true 27 | /// test if running: http://localhost:27750/entity/flecs 28 | pub fn enableWebExplorer(self: World) void { 29 | _ = 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)); 30 | } 31 | 32 | /// -1 log level turns off logging 33 | pub fn setLogLevel(_: World, level: c_int, enable_colors: bool) void { 34 | _ = flecs.c.ecs_log_set_level(level); 35 | _ = flecs.c.ecs_log_enable_colors(enable_colors); 36 | } 37 | 38 | pub fn progress(self: World, delta_time: f32) void { 39 | _ = flecs.c.ecs_progress(self.world, delta_time); 40 | } 41 | 42 | pub fn getTypeStr(self: World, typ: flecs.c.ecs_type_t) [*c]u8 { 43 | return flecs.c.ecs_type_str(self.world, typ); 44 | } 45 | 46 | pub fn newEntity(self: World) Entity { 47 | return Entity.init(self.world, flecs.c.ecs_new_id(self.world)); 48 | } 49 | 50 | pub fn newEntityWithName(self: World, name: [*c]const u8) Entity { 51 | var desc = std.mem.zeroInit(flecs.c.ecs_entity_desc_t, .{ .name = name }); 52 | return Entity.init(self.world, flecs.c.ecs_entity_init(self.world, &desc)); 53 | } 54 | 55 | pub fn newPrefab(self: World, name: [*c]const u8) flecs.Entity { 56 | var desc = std.mem.zeroInit(flecs.c.ecs_entity_desc_t, .{ 57 | .name = name, 58 | .add = [_]flecs.c.ecs_id_t{0} ** 32, 59 | }); 60 | desc.add[0] = flecs.c.EcsPrefab; 61 | return Entity.init(self.world, flecs.c.ecs_entity_init(self.world, &desc)); 62 | } 63 | 64 | /// Allowed params: Entity, EntityId, type 65 | pub fn pair(self: World, relation: anytype, object: anytype) u64 { 66 | const Relation = @TypeOf(relation); 67 | const Object = @TypeOf(object); 68 | 69 | const rel_info = @typeInfo(Relation); 70 | const obj_info = @typeInfo(Object); 71 | 72 | std.debug.assert(rel_info == .Struct or rel_info == .Type or Relation == flecs.EntityId or Relation == flecs.Entity or Relation == c_int); 73 | std.debug.assert(obj_info == .Struct or obj_info == .Type or Object == flecs.EntityId or Object == flecs.Entity); 74 | 75 | const rel_id = switch (Relation) { 76 | c_int => @intCast(flecs.EntityId, relation), 77 | type => self.componentId(relation), 78 | flecs.EntityId => relation, 79 | flecs.Entity => relation.id, 80 | else => unreachable, 81 | }; 82 | 83 | const obj_id = switch (Object) { 84 | type => self.componentId(object), 85 | flecs.EntityId => object, 86 | flecs.Entity => object.id, 87 | else => unreachable, 88 | }; 89 | 90 | return flecs.c.ECS_PAIR | (rel_id << @as(u32, 32)) + @truncate(u32, obj_id); 91 | } 92 | 93 | /// bulk registers a tuple of Types 94 | pub fn registerComponents(self: World, types: anytype) void { 95 | std.debug.assert(@typeInfo(@TypeOf(types)) == .Struct); 96 | inline for (types) |t| { 97 | _ = self.componentId(t); 98 | } 99 | } 100 | 101 | /// gets the EntityId for T creating it if it doesn't already exist 102 | pub fn componentId(self: World, comptime T: type) flecs.EntityId { 103 | return meta.componentId(self.world, T); 104 | } 105 | 106 | /// 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 107 | /// by adding the Ids of each component so that order doesnt matter. 108 | pub fn newType(self: World, comptime Types: anytype) flecs.EntityId { 109 | var i: flecs.EntityId = 0; 110 | inline for (Types) |T| { 111 | i += self.componentId(T); 112 | } 113 | 114 | const name = std.fmt.allocPrintZ(std.heap.c_allocator, "Type{d}", .{i}) catch unreachable; 115 | return self.newTypeWithName(name, Types); 116 | } 117 | 118 | /// creates a new type entity, or finds an existing one. A type entity is an entity with the EcsType component. 119 | pub fn newTypeWithName(self: World, name: [*c]const u8, comptime Types: anytype) flecs.EntityId { 120 | var desc = std.mem.zeroes(flecs.c.ecs_type_desc_t); 121 | desc.entity = std.mem.zeroInit(flecs.c.ecs_entity_desc_t, .{ .name = name }); 122 | 123 | inline for (Types) |T, i| { 124 | desc.ids[i] = self.componentId(T); 125 | } 126 | 127 | return flecs.c.ecs_type_init(self.world, &desc); 128 | } 129 | 130 | pub fn newTypeExpr(self: World, name: [*c]const u8, expr: [*c]const u8) flecs.EntityId { 131 | var desc = std.mem.zeroInit(flecs.c.ecs_type_desc_t, .{ .ids_expr = expr }); 132 | desc.entity = std.mem.zeroInit(flecs.c.ecs_entity_desc_t, .{ .name = name }); 133 | 134 | return flecs.c.ecs_type_init(self.world, &desc); 135 | } 136 | 137 | /// this operation will preallocate memory in the world for the specified number of entities 138 | pub fn dim(self: World, entity_count: i32) void { 139 | flecs.c.ecs_dim(self.world, entity_count); 140 | } 141 | 142 | /// this operation will preallocate memory for a type (table) for the specified number of entities 143 | pub fn dimType(self: World, ecs_type: flecs.c.ecs_type_t, entity_count: i32) void { 144 | flecs.c.ecs_dim_type(self.world, ecs_type, entity_count); 145 | } 146 | 147 | pub fn newSystem(self: World, name: [*c]const u8, phase: flecs.Phase, signature: [*c]const u8, action: flecs.c.ecs_iter_action_t) void { 148 | var desc = std.mem.zeroes(flecs.c.ecs_system_desc_t); 149 | desc.entity.name = name; 150 | desc.entity.add[0] = @enumToInt(phase); 151 | desc.query.filter.expr = signature; 152 | // desc.multi_threaded = true; 153 | desc.callback = action; 154 | _ = flecs.c.ecs_system_init(self.world, &desc); 155 | } 156 | 157 | pub fn newRunSystem(self: World, name: [*c]const u8, phase: flecs.Phase, signature: [*c]const u8, action: flecs.c.ecs_iter_action_t) void { 158 | var desc = std.mem.zeroes(flecs.c.ecs_system_desc_t); 159 | desc.entity.name = name; 160 | desc.entity.add[0] = @enumToInt(phase); 161 | desc.query.filter.expr = signature; 162 | // desc.multi_threaded = true; 163 | desc.callback = dummyFn; 164 | desc.run = action; 165 | _ = flecs.c.ecs_system_init(self.world, &desc); 166 | } 167 | 168 | pub fn newWrappedRunSystem(self: World, name: [*c]const u8, phase: flecs.Phase, comptime Components: type, comptime action: fn (*flecs.Iterator(Components)) void) void { 169 | var desc = std.mem.zeroes(flecs.c.ecs_system_desc_t); 170 | desc.entity.name = name; 171 | desc.entity.add[0] = @enumToInt(phase); 172 | desc.query.filter = meta.generateFilterDesc(self, Components); 173 | // desc.multi_threaded = true; 174 | desc.callback = dummyFn; 175 | desc.run = wrapSystemFn(Components, action); 176 | _ = flecs.c.ecs_system_init(self.world, &desc); 177 | } 178 | 179 | /// creates a Filter using the passed in struct 180 | pub fn filter(self: World, comptime Components: type) flecs.Filter { 181 | std.debug.assert(@typeInfo(Components) == .Struct); 182 | var desc = meta.generateFilterDesc(self, Components); 183 | return flecs.Filter.init(self, &desc); 184 | } 185 | 186 | /// probably temporary until we find a better way to handle it better, but a way to 187 | /// iterate the passed components of children of the parent entity 188 | pub fn filterParent(self: World, comptime Components: type, parent: flecs.Entity) flecs.Filter { 189 | std.debug.assert(@typeInfo(Components) == .Struct); 190 | var desc = meta.generateFilterDesc(self, Components); 191 | const component_info = @typeInfo(Components).Struct; 192 | desc.terms[component_info.fields.len].id = self.pair(flecs.c.EcsChildOf, parent); 193 | return flecs.Filter.init(self, &desc); 194 | } 195 | 196 | /// creates a Query using the passed in struct 197 | pub fn query(self: World, comptime Components: type) flecs.Query { 198 | std.debug.assert(@typeInfo(Components) == .Struct); 199 | var desc = std.mem.zeroes(flecs.c.ecs_query_desc_t); 200 | desc.filter = meta.generateFilterDesc(self, Components); 201 | 202 | if (@hasDecl(Components, "order_by")) { 203 | meta.validateOrderByFn(Components.order_by); 204 | const ti = @typeInfo(@TypeOf(Components.order_by)); 205 | const OrderByType = meta.FinalChild(ti.Fn.args[1].arg_type.?); 206 | meta.validateOrderByType(Components, OrderByType); 207 | 208 | desc.order_by = wrapOrderByFn(OrderByType, Components.order_by); 209 | desc.order_by_component = self.componentId(OrderByType); 210 | } 211 | 212 | if (@hasDecl(Components, "instanced") and Components.instanced) desc.filter.instanced = true; 213 | 214 | return flecs.Query.init(self, &desc); 215 | } 216 | 217 | /// adds a system to the World using the passed in struct 218 | pub fn system(self: World, comptime Components: type, phase: flecs.Phase) void { 219 | std.debug.assert(@typeInfo(Components) == .Struct); 220 | std.debug.assert(@hasDecl(Components, "run")); 221 | std.debug.assert(@hasDecl(Components, "name")); 222 | 223 | var desc = std.mem.zeroes(flecs.c.ecs_system_desc_t); 224 | desc.callback = dummyFn; 225 | desc.entity.name = Components.name; 226 | desc.entity.add[0] = @enumToInt(phase); 227 | // desc.multi_threaded = true; 228 | desc.run = wrapSystemFn(Components, Components.run); 229 | desc.query.filter = meta.generateFilterDesc(self, Components); 230 | 231 | if (@hasDecl(Components, "order_by")) { 232 | meta.validateOrderByFn(Components.order_by); 233 | const ti = @typeInfo(@TypeOf(Components.order_by)); 234 | const OrderByType = meta.FinalChild(ti.Fn.args[1].arg_type.?); 235 | meta.validateOrderByType(Components, OrderByType); 236 | 237 | desc.query.order_by = wrapOrderByFn(OrderByType, Components.order_by); 238 | desc.query.order_by_component = self.componentId(OrderByType); 239 | } 240 | 241 | if (@hasDecl(Components, "instanced") and Components.instanced) desc.filter.instanced = true; 242 | 243 | _ = flecs.c.ecs_system_init(self.world, &desc); 244 | } 245 | 246 | /// adds an observer system to the World using the passed in struct (see systems) 247 | pub fn observer(self: World, comptime Components: type, event: flecs.Event) void { 248 | std.debug.assert(@typeInfo(Components) == .Struct); 249 | std.debug.assert(@hasDecl(Components, "run")); 250 | std.debug.assert(@hasDecl(Components, "name")); 251 | 252 | var desc = std.mem.zeroes(flecs.c.ecs_observer_desc_t); 253 | desc.callback = dummyFn; 254 | desc.entity.name = Components.name; 255 | desc.events[0] = @enumToInt(event); 256 | 257 | desc.run = wrapSystemFn(Components, Components.run); 258 | desc.filter = meta.generateFilterDesc(self, Components); 259 | 260 | if (@hasDecl(Components, "instanced") and Components.instanced) desc.filter.instanced = true; 261 | 262 | _ = flecs.c.ecs_observer_init(self.world, &desc); 263 | } 264 | 265 | pub fn setName(self: World, entity: flecs.EntityId, name: [*c]const u8) void { 266 | _ = flecs.c.ecs_set_name(self.world, entity, name); 267 | } 268 | 269 | pub fn getName(self: World, entity: flecs.EntityId) [*c]const u8 { 270 | return flecs.c.ecs_get_name(self.world, entity); 271 | } 272 | 273 | /// sets a component on entity. Can be either a pointer to a struct or a struct 274 | pub fn set(self: *World, entity: flecs.EntityId, ptr_or_struct: anytype) void { 275 | std.debug.assert(@typeInfo(@TypeOf(ptr_or_struct)) == .Pointer or @typeInfo(@TypeOf(ptr_or_struct)) == .Struct); 276 | 277 | const T = meta.FinalChild(@TypeOf(ptr_or_struct)); 278 | var component = if (@typeInfo(@TypeOf(ptr_or_struct)) == .Pointer) ptr_or_struct else &ptr_or_struct; 279 | _ = flecs.c.ecs_set_id(self.world, entity, self.componentId(T), @sizeOf(T), component); 280 | } 281 | 282 | /// removes a component from an Entity 283 | pub fn remove(self: *World, entity: flecs.EntityId, comptime T: type) void { 284 | flecs.c.ecs_remove_id(self.world, entity, self.componentId(T)); 285 | } 286 | 287 | /// removes all components from an Entity 288 | pub fn clear(self: *World, entity: flecs.EntityId) void { 289 | flecs.c.ecs_clear(self.world, entity); 290 | } 291 | 292 | /// removes the entity from the world 293 | pub fn delete(self: *World, entity: flecs.EntityId) void { 294 | flecs.c.ecs_delete(self.world, entity); 295 | } 296 | 297 | /// deletes all entities with the component 298 | pub fn deleteWith(self: *World, comptime T: type) void { 299 | flecs.c.ecs_delete_with(self.world, self.componentId(T)); 300 | } 301 | 302 | /// remove all instances of the specified component 303 | pub fn removeAll(self: *World, comptime T: type) void { 304 | flecs.c.ecs_remove_all(self.world, self.componentId(T)); 305 | } 306 | 307 | pub fn setSingleton(self: World, ptr_or_struct: anytype) void { 308 | std.debug.assert(@typeInfo(@TypeOf(ptr_or_struct)) == .Pointer or @typeInfo(@TypeOf(ptr_or_struct)) == .Struct); 309 | 310 | const T = meta.FinalChild(@TypeOf(ptr_or_struct)); 311 | var component = if (@typeInfo(@TypeOf(ptr_or_struct)) == .Pointer) ptr_or_struct else &ptr_or_struct; 312 | _ = flecs.c.ecs_set_id(self.world, self.componentId(T), self.componentId(T), @sizeOf(T), component); 313 | } 314 | 315 | // TODO: use ecs_get_mut_id optionally based on a bool perhaps or maybe if the passed in type is a pointer? 316 | pub fn getSingleton(self: World, comptime T: type) ?*const T { 317 | std.debug.assert(@typeInfo(T) == .Struct); 318 | var val = flecs.c.ecs_get_id(self.world, self.componentId(T), self.componentId(T)); 319 | if (val == null) return null; 320 | return @ptrCast(*const T, @alignCast(@alignOf(T), val)); 321 | } 322 | 323 | pub fn getSingletonMut(self: World, comptime T: type) ?*T { 324 | std.debug.assert(@typeInfo(T) == .Struct); 325 | var is_added: bool = undefined; 326 | var val = flecs.c.ecs_get_mut_id(self.world, self.componentId(T), self.componentId(T), &is_added); 327 | if (val == null) return null; 328 | return @ptrCast(*T, @alignCast(@alignOf(T), val)); 329 | } 330 | 331 | pub fn removeSingleton(self: World, comptime T: type) void { 332 | std.debug.assert(@typeInfo(T) == .Struct); 333 | flecs.c.ecs_remove_id(self.world, self.componentId(T), self.componentId(T)); 334 | } 335 | }; 336 | 337 | fn wrapSystemFn(comptime T: type, comptime cb: fn (*flecs.Iterator(T)) void) fn ([*c]flecs.c.ecs_iter_t) callconv(.C) void { 338 | const Closure = struct { 339 | pub var callback: fn (*flecs.Iterator(T)) void = cb; 340 | 341 | pub fn closure(it: [*c]flecs.c.ecs_iter_t) callconv(.C) void { 342 | callback(&flecs.Iterator(T).init(it, flecs.c.ecs_iter_next)); 343 | } 344 | }; 345 | return Closure.closure; 346 | } 347 | 348 | fn wrapOrderByFn(comptime T: type, comptime cb: fn (flecs.EntityId, *const T, flecs.EntityId, *const T) c_int) FlecsOrderByAction { 349 | const Closure = struct { 350 | pub fn closure(e1: flecs.EntityId, c1: ?*const anyopaque, e2: flecs.EntityId, c2: ?*const anyopaque) callconv(.C) c_int { 351 | return @call(.{ .modifier = .always_inline }, cb, .{ e1, utils.componentCast(T, c1), e2, utils.componentCast(T, c2) }); 352 | } 353 | }; 354 | return Closure.closure; 355 | } 356 | --------------------------------------------------------------------------------