Major Update | Removed World | Use NYXWorld instead
This commit is contained in:
parent
b9ce1f27e9
commit
f72cae1765
22 changed files with 172 additions and 1523 deletions
182
README.md
182
README.md
|
|
@ -1,112 +1,110 @@
|
||||||
**Includes**
|
# NYXApp
|
||||||
- Plugins
|
|
||||||
- Scheduler
|
|
||||||
- Resources
|
|
||||||
- Events
|
|
||||||
- ECS (Archetype)
|
|
||||||
|
|
||||||
**Plugin**
|
Low cortisol **General Purpose App**
|
||||||
``` zig
|
|
||||||
pub const plugin: App.plugin.Plugin = .{
|
# App
|
||||||
.name = "Renderer",
|
```zig
|
||||||
.description = "Vulkan Renderer",
|
// --- APP ---
|
||||||
|
var app: App = .init(std.testing.allocator);
|
||||||
|
defer app.deinit();
|
||||||
|
|
||||||
|
// --- ADD PLUGINS ---
|
||||||
|
try app.plugins.addMany(&.{
|
||||||
|
window_plugin,
|
||||||
|
});
|
||||||
|
try app.plugins.build(&app);
|
||||||
|
```
|
||||||
|
|
||||||
|
# Plugin
|
||||||
|
```zig
|
||||||
|
pub const window_plugin: Plugin = .{
|
||||||
|
.name = "Window",
|
||||||
|
.description = "SDL3 Window",
|
||||||
.build = build,
|
.build = build,
|
||||||
};
|
};
|
||||||
```
|
|
||||||
|
|
||||||
**System**
|
fn build(
|
||||||
``` zig
|
self: *Plugin,
|
||||||
pub fn render(app: *App) !void {
|
_: *App,
|
||||||
// --- GET RESOURCES ---
|
) !void {
|
||||||
const res = try app.resources.getMany(struct {
|
std.debug.print(
|
||||||
window: *Window,
|
"[WINDOW_PLUGIN] {s} - {s}\n",
|
||||||
device: *Device,
|
.{
|
||||||
render_frame: ?*RenderFrame,
|
self.name,
|
||||||
});
|
self.description,
|
||||||
|
},
|
||||||
if (res.render_frame) |rf| {
|
);
|
||||||
_ = rf;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Simple Events**
|
# Schedules
|
||||||
``` zig
|
|
||||||
// --- SEND MANY EVENTS ---
|
**addMany**
|
||||||
try app.events.sendMany(&.{
|
```zig
|
||||||
event.AppExit{
|
try app.schedules.addMany(stage.init, &.{
|
||||||
.reason = "Yes",
|
ensureStuff,
|
||||||
|
otherSystem,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**runMany**
|
||||||
|
```zig
|
||||||
|
try app.schedules.runMany(
|
||||||
|
&.{
|
||||||
|
App.stage.init,
|
||||||
|
App.stage.deinit,
|
||||||
},
|
},
|
||||||
|
&app,
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
# Resources
|
||||||
|
|
||||||
|
**set**
|
||||||
|
```zig
|
||||||
|
const Window = struct { raw: u32 };
|
||||||
|
try app.resources.set(Window{ .raw = 0 });
|
||||||
|
```
|
||||||
|
|
||||||
|
**get**
|
||||||
|
```zig
|
||||||
|
if (app.resources.getPtr(Window)) |win| {
|
||||||
|
std.debug.print("[WINDOW] {}\n", .{win.raw});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**getMany**
|
||||||
|
```zig
|
||||||
|
const res = try app.resources.getMany(struct {
|
||||||
|
window: *Window,
|
||||||
|
renderer: ?*Renderer,
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- MAIN GAME LOOP ---
|
if (res.renderer) |ren| {
|
||||||
while (true) {
|
std.debug.print("[RENDERER] {}\n", .{ren.raw});
|
||||||
// --- USE AND EAT EVENT ---
|
|
||||||
if (app.events.consume(event.AppExit)) |app_exit| {
|
|
||||||
std.debug.print("Exit Reason: {s}\n", .{
|
|
||||||
app_exit.reason,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Event Queue**
|
# Events
|
||||||
``` zig
|
|
||||||
const Register = struct {
|
|
||||||
username: []const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- QUEUE ---
|
**send**
|
||||||
if (app.events.get(event.Register)) |queue| {
|
```zig
|
||||||
// --- GET EVERY EVENT ---
|
try app.events.send(
|
||||||
for (queue.items()) |register| {
|
AppExit{
|
||||||
// --- DEBUG ---
|
.reason = "Why Not?",
|
||||||
std.debug.print("New Register: Username(s)\n", .{
|
},
|
||||||
register.username
|
);
|
||||||
});
|
```
|
||||||
// ...
|
|
||||||
}
|
**consume**
|
||||||
// --- CLEAR EVENT QUEUE ---
|
```zig
|
||||||
queue.clear();
|
if (app.events.consume(AppExit)) |app_exit| {
|
||||||
|
std.debug.print("[APP_EXIT] {s}\n", .{app_exit.reason});
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Simple**
|
# Default Stages
|
||||||
``` zig
|
```zig
|
||||||
test "Main" {
|
|
||||||
// --- APP ---
|
|
||||||
var app: Self = .init(std.testing.allocator);
|
|
||||||
defer app.deinit();
|
|
||||||
|
|
||||||
{ // PLUGINS
|
|
||||||
// --- ADD PLUGINS ---
|
|
||||||
try app.plugins.addMany(&.{
|
|
||||||
example.simple.plugin,
|
|
||||||
});
|
|
||||||
|
|
||||||
// --- BUILD PLUGINS ---
|
|
||||||
try app.plugins.build(.{
|
|
||||||
.app = &app,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // SYSTEMS
|
|
||||||
try app.schedules.runMany(&.{
|
|
||||||
example.simple.stage.Init,
|
|
||||||
example.simple.stage.Inputs,
|
|
||||||
example.simple.stage.Update,
|
|
||||||
example.simple.stage.Draw,
|
|
||||||
example.simple.stage.Deinit,
|
|
||||||
}, .{
|
|
||||||
.app = &app,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Default App Stages**
|
|
||||||
``` zig
|
|
||||||
pub const stage = struct {
|
pub const stage = struct {
|
||||||
// --- LIFETIME ---
|
// --- LIFETIME ---
|
||||||
pub const init = struct {};
|
pub const init = struct {};
|
||||||
|
|
|
||||||
145
src/app.zig
145
src/app.zig
|
|
@ -1,21 +1,25 @@
|
||||||
//! ----------------------------------------------------
|
//! ----------------------------------------------------
|
||||||
//! ----------------------------------------------------
|
//! ----------------------------------------------------
|
||||||
|
|
||||||
// --- LIBS ---
|
//
|
||||||
|
// PRIVATE
|
||||||
|
//
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const root = @import("root");
|
const template = @import("template/root.zig");
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
//
|
||||||
|
// PUBLIC
|
||||||
|
//
|
||||||
|
|
||||||
pub const event = @import("event/root.zig");
|
pub const event = @import("event/root.zig");
|
||||||
pub const plugin = @import("plugin/root.zig");
|
pub const plugin = @import("plugin/root.zig");
|
||||||
pub const resource = @import("resource/root.zig");
|
pub const resource = @import("resource/root.zig");
|
||||||
pub const scheduler = @import("scheduler/root.zig");
|
pub const scheduler = @import("scheduler/root.zig");
|
||||||
pub const ecs = @import("world/root.zig");
|
|
||||||
|
|
||||||
pub const Entity = ecs.Entity;
|
|
||||||
pub const Plugin = plugin.Plugin;
|
pub const Plugin = plugin.Plugin;
|
||||||
|
pub const AppExit = event.AppExit;
|
||||||
const template = @import("template/root.zig");
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
/// ----------------------------------------------------
|
||||||
/// Default app stages
|
/// Default app stages
|
||||||
|
|
@ -36,16 +40,6 @@ pub const stage = struct {
|
||||||
pub const draw = struct {};
|
pub const draw = struct {};
|
||||||
};
|
};
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub const Options = struct {
|
|
||||||
seed: u64 = 8490694,
|
|
||||||
};
|
|
||||||
pub const options: Options = if (@hasDecl(
|
|
||||||
root,
|
|
||||||
"app_options",
|
|
||||||
)) root.app_options else .{};
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// FIELDS
|
// FIELDS
|
||||||
//
|
//
|
||||||
|
|
@ -54,7 +48,6 @@ events: event.Storage,
|
||||||
plugins: plugin.Storage,
|
plugins: plugin.Storage,
|
||||||
resources: resource.Storage,
|
resources: resource.Storage,
|
||||||
schedules: scheduler.Storage,
|
schedules: scheduler.Storage,
|
||||||
world: ecs.World,
|
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
/// ----------------------------------------------------
|
||||||
|
|
@ -65,7 +58,6 @@ pub fn init(alloc: std.mem.Allocator) Self {
|
||||||
.plugins = .init(alloc),
|
.plugins = .init(alloc),
|
||||||
.resources = .init(alloc),
|
.resources = .init(alloc),
|
||||||
.schedules = .init(alloc),
|
.schedules = .init(alloc),
|
||||||
.world = .init(alloc),
|
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -73,7 +65,6 @@ pub fn init(alloc: std.mem.Allocator) Self {
|
||||||
/// ----------------------------------------------------
|
/// ----------------------------------------------------
|
||||||
/// ----------------------------------------------------
|
/// ----------------------------------------------------
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
self.world.deinit();
|
|
||||||
self.events.deinit();
|
self.events.deinit();
|
||||||
self.plugins.deinit();
|
self.plugins.deinit();
|
||||||
self.resources.deinit();
|
self.resources.deinit();
|
||||||
|
|
@ -81,60 +72,80 @@ pub fn deinit(self: *Self) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// EXAMPLES
|
// TESTS
|
||||||
//
|
//
|
||||||
|
|
||||||
const example = @import("example/root.zig");
|
const Window = struct { raw: u32 };
|
||||||
|
const Renderer = struct { raw: u32 };
|
||||||
|
|
||||||
// ----------------------------------------------------
|
const window_plugin: Plugin = .{
|
||||||
// ----------------------------------------------------
|
.name = "Window",
|
||||||
test "Main" {
|
.description = "SDL3 Window",
|
||||||
|
.build = build,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn ensureStuff(app: *Self) !void {
|
||||||
|
try app.resources.set(Window{ .raw = 0 });
|
||||||
|
try app.resources.set(Renderer{ .raw = 0 });
|
||||||
|
|
||||||
|
if (app.resources.getPtr(Window)) |win| {
|
||||||
|
std.debug.print("[WINDOW] {}\n", .{win.raw});
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = try app.resources.getMany(struct {
|
||||||
|
window: *Window,
|
||||||
|
renderer: ?*Renderer,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.renderer) |ren| {
|
||||||
|
std.debug.print("[RENDERER] {}\n", .{ren.raw});
|
||||||
|
}
|
||||||
|
|
||||||
|
try app.events.send(
|
||||||
|
AppExit{
|
||||||
|
.reason = "Why Not?",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (app.events.consume(AppExit)) |app_exit| {
|
||||||
|
std.debug.print("[APP_EXIT] {s}\n", .{app_exit.reason});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build(
|
||||||
|
self: *Plugin,
|
||||||
|
app: *Self,
|
||||||
|
) !void {
|
||||||
|
std.debug.print(
|
||||||
|
"[WINDOW_PLUGIN] {s} - {s}\n",
|
||||||
|
.{
|
||||||
|
self.name,
|
||||||
|
self.description,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
try app.schedules.addMany(stage.init, &.{
|
||||||
|
ensureStuff,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test "main" {
|
||||||
// --- APP ---
|
// --- APP ---
|
||||||
var app: Self = .init(std.testing.allocator);
|
var app: Self = .init(std.testing.allocator);
|
||||||
defer app.deinit();
|
defer app.deinit();
|
||||||
|
|
||||||
// --- SEND MANY EVENTS ---
|
// --- ADD PLUGINS ---
|
||||||
try app.events.sendMany(&.{
|
try app.plugins.addMany(&.{
|
||||||
event.AppExit{
|
window_plugin,
|
||||||
.reason = "Yes",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
try app.plugins.build(&app);
|
||||||
|
|
||||||
// --- MAIN GAME LOOP ---
|
// --- RUN SYSTEMS ---
|
||||||
while (true) {
|
try app.schedules.runMany(
|
||||||
// --- USE AND EAT EVENT ---
|
&.{
|
||||||
if (app.events.consume(event.AppExit)) |app_exit| {
|
stage.init,
|
||||||
std.debug.print("| {s}\n", .{
|
stage.deinit,
|
||||||
app_exit.reason,
|
},
|
||||||
});
|
&app,
|
||||||
break;
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // PLUGINS
|
|
||||||
// --- ADD PLUGINS ---
|
|
||||||
try app.plugins.addMany(&.{
|
|
||||||
example.simple.plugin,
|
|
||||||
});
|
|
||||||
|
|
||||||
// --- BUILD PLUGINS ---
|
|
||||||
try app.plugins.build(&app);
|
|
||||||
}
|
|
||||||
|
|
||||||
{ // SYSTEMS
|
|
||||||
// try app.schedules.runAll(&app);
|
|
||||||
|
|
||||||
try app.schedules.runMany(&.{
|
|
||||||
example.simple.stage.Init,
|
|
||||||
example.simple.stage.Inputs,
|
|
||||||
example.simple.stage.Update,
|
|
||||||
example.simple.stage.Draw,
|
|
||||||
example.simple.stage.Deinit,
|
|
||||||
}, &app);
|
|
||||||
|
|
||||||
try app.schedules.runReversed(
|
|
||||||
example.simple.stage.Deinit,
|
|
||||||
&app,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
pub const simple = @import("simple/root.zig");
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub const Transform = struct {
|
|
||||||
pos: @Vector(2, f32) = .{ 0, 0 },
|
|
||||||
size: @Vector(2, f32) = .{ 1, 1 },
|
|
||||||
|
|
||||||
pub fn format(
|
|
||||||
self: @This(),
|
|
||||||
writer: *std.Io.Writer,
|
|
||||||
) !void {
|
|
||||||
try writer.print("pos=({}, {}) size=({}, {})", .{
|
|
||||||
self.pos[0], self.pos[1],
|
|
||||||
self.size[0], self.size[1],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Material = struct {
|
|
||||||
albedo: @Vector(4, u8) = .{ 255, 255, 255, 255 },
|
|
||||||
|
|
||||||
pub fn format(
|
|
||||||
self: @This(),
|
|
||||||
writer: *std.Io.Writer,
|
|
||||||
) std.Io.Writer.Error!void {
|
|
||||||
try writer.print("rgba({}, {}, {}, {})", .{
|
|
||||||
self.albedo[0],
|
|
||||||
self.albedo[1],
|
|
||||||
self.albedo[2],
|
|
||||||
self.albedo[3],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Player = struct {
|
|
||||||
name: []const u8,
|
|
||||||
|
|
||||||
pub fn format(
|
|
||||||
self: @This(),
|
|
||||||
writer: *std.Io.Writer,
|
|
||||||
) std.Io.Writer.Error!void {
|
|
||||||
try writer.print("\"{s}\"", .{self.name});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Dead = struct { value: bool = false };
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
pub const Login = struct {
|
|
||||||
username: []const u8,
|
|
||||||
password: []const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Register = struct {
|
|
||||||
username: []const u8,
|
|
||||||
password: []const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Connect = struct {
|
|
||||||
ip: []const u8 = "localhost",
|
|
||||||
port: u16 = 4444,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ConnectionAccepted = struct {
|
|
||||||
id: u32,
|
|
||||||
};
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
const App = @import("../../app.zig");
|
|
||||||
const system = @import("system.zig");
|
|
||||||
const stage = @import("stage.zig");
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub const plugin: App.plugin.Plugin = .{
|
|
||||||
.name = "Simple",
|
|
||||||
.description = "Simple plugin example",
|
|
||||||
.dependencies = &.{},
|
|
||||||
.build = build,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
fn build(
|
|
||||||
self: *App.plugin.Plugin,
|
|
||||||
app: *App,
|
|
||||||
) !void {
|
|
||||||
// --- DEBUG ---
|
|
||||||
std.debug.print(
|
|
||||||
"\x1b[33mPLUGIN\x1b[0m\n" ++
|
|
||||||
\\| Name: {s}
|
|
||||||
\\| Description: {s}
|
|
||||||
\\
|
|
||||||
,
|
|
||||||
.{
|
|
||||||
self.name,
|
|
||||||
self.description,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// --- ADD SYSTEMS ---
|
|
||||||
try app.schedules.addMany(stage.Init, &.{
|
|
||||||
system.test_resources,
|
|
||||||
system.test_events,
|
|
||||||
system.test_ecs,
|
|
||||||
});
|
|
||||||
|
|
||||||
// --- RUN BEFORE ---
|
|
||||||
try app.schedules.addBefore(
|
|
||||||
stage.Init,
|
|
||||||
stage.Init,
|
|
||||||
system.draw,
|
|
||||||
);
|
|
||||||
|
|
||||||
try app.schedules.addMany(stage.Deinit, &.{
|
|
||||||
deinit_one,
|
|
||||||
deinit_two,
|
|
||||||
deinit_three,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
fn deinit_one(_: *App) !void {
|
|
||||||
std.debug.print("---> One\n", .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
fn deinit_two(_: *App) !void {
|
|
||||||
std.debug.print("---> Two\n", .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
fn deinit_three(_: *App) !void {
|
|
||||||
std.debug.print("---> Three\n", .{});
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
pub const Server = struct { id: u32 = 0 };
|
|
||||||
pub const Client = struct { id: u32 = 0 };
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
pub const plugin = @import("plugin.zig").plugin;
|
|
||||||
pub const system = @import("system.zig");
|
|
||||||
pub const stage = @import("stage.zig");
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
pub const Init = struct {};
|
|
||||||
pub const Deinit = struct {};
|
|
||||||
pub const Inputs = struct {};
|
|
||||||
pub const Update = struct {};
|
|
||||||
pub const Draw = struct {};
|
|
||||||
|
|
||||||
pub const MainPass = struct {};
|
|
||||||
|
|
@ -1,241 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
const App = @import("../../app.zig");
|
|
||||||
|
|
||||||
const event = @import("event.zig");
|
|
||||||
const resource = @import("resource.zig");
|
|
||||||
const component = @import("component.zig");
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn test_resources(
|
|
||||||
app: *App,
|
|
||||||
) !void {
|
|
||||||
// --- SET NEW ---
|
|
||||||
try app.resources.set(resource.Server{});
|
|
||||||
try app.resources.set(resource.Client{});
|
|
||||||
|
|
||||||
// --- CONTAINS DEBUG ---
|
|
||||||
std.debug.print(
|
|
||||||
"\x1b[34mRESOURCES EXIST\x1b[0m\n" ++
|
|
||||||
\\| Server: {}
|
|
||||||
\\| Client: {}
|
|
||||||
\\
|
|
||||||
,
|
|
||||||
.{
|
|
||||||
app.resources.contains(resource.Server),
|
|
||||||
app.resources.contains(resource.Client),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// --- GET READONLY RESOURCES ---
|
|
||||||
_ = try app.resources.getMany(struct {
|
|
||||||
server: ?*const resource.Server,
|
|
||||||
client: ?*const resource.Client,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const User = struct {
|
|
||||||
id: u32 = 0,
|
|
||||||
username: []const u8,
|
|
||||||
password: []const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn test_events(
|
|
||||||
app: *App,
|
|
||||||
) !void {
|
|
||||||
// --- DEBUG ---
|
|
||||||
std.debug.print(
|
|
||||||
"\x1b[35mEVENTS\x1b[0m\n",
|
|
||||||
.{},
|
|
||||||
);
|
|
||||||
|
|
||||||
// --- CLEAR EVENTS ---
|
|
||||||
defer app.events.clear();
|
|
||||||
|
|
||||||
// --- USER LIST ---
|
|
||||||
var users: std.ArrayList(User) = .empty;
|
|
||||||
defer users.deinit(app.alloc);
|
|
||||||
|
|
||||||
// --- ID`S ---
|
|
||||||
var next_register_id: u32 = 0;
|
|
||||||
var next_connected_id: u32 = 0;
|
|
||||||
|
|
||||||
// --- ADD ADMIN USER ---
|
|
||||||
try users.append(app.alloc, .{
|
|
||||||
.id = next_register_id,
|
|
||||||
.username = "admin",
|
|
||||||
.password = "1234",
|
|
||||||
});
|
|
||||||
|
|
||||||
{ // NEW USER
|
|
||||||
try app.events.sendMany(.{
|
|
||||||
event.Register{
|
|
||||||
.username = "abux",
|
|
||||||
.password = "qwerty",
|
|
||||||
},
|
|
||||||
event.Login{
|
|
||||||
.username = "abux",
|
|
||||||
.password = "qwerty",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- REGISTER LOOP ---
|
|
||||||
if (app.events.get(event.Register)) |queue| {
|
|
||||||
for (queue.items()) |reg| {
|
|
||||||
next_register_id += 1;
|
|
||||||
try users.append(app.alloc, .{
|
|
||||||
.id = next_register_id,
|
|
||||||
.username = reg.username,
|
|
||||||
.password = reg.password,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
queue.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- LOGIN LOOP ---
|
|
||||||
if (app.events.get(event.Login)) |queue| {
|
|
||||||
for (queue.items()) |login| {
|
|
||||||
for (users.items) |user| {
|
|
||||||
// --- CHECK CREDS ---
|
|
||||||
if (!std.mem.eql(u8, user.username, login.username)) continue;
|
|
||||||
if (!std.mem.eql(u8, user.password, login.password)) continue;
|
|
||||||
|
|
||||||
// --- SEND ACCEPTED CONNECTION ---
|
|
||||||
next_connected_id += 1;
|
|
||||||
try app.events.send(event.ConnectionAccepted{
|
|
||||||
.id = next_connected_id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
queue.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- CONNECTED ---
|
|
||||||
if (app.events.consume(event.ConnectionAccepted)) |ca| {
|
|
||||||
std.debug.print(
|
|
||||||
\\ | Connected:
|
|
||||||
\\ | ID: {}
|
|
||||||
\\
|
|
||||||
,
|
|
||||||
.{
|
|
||||||
ca.id,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- DEBUG ---
|
|
||||||
std.debug.print(
|
|
||||||
"\x1b[31mUSERS\x1b[0m\n",
|
|
||||||
.{},
|
|
||||||
);
|
|
||||||
for (users.items) |user| {
|
|
||||||
std.debug.print(
|
|
||||||
\\ | ID: {}
|
|
||||||
\\ | Username: {s}
|
|
||||||
\\ | Password: {s}
|
|
||||||
\\
|
|
||||||
,
|
|
||||||
.{
|
|
||||||
user.id,
|
|
||||||
user.username,
|
|
||||||
user.password,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn test_ecs(
|
|
||||||
app: *App,
|
|
||||||
) !void {
|
|
||||||
// --- SPAWN ENTITY ---
|
|
||||||
const player = try app.world.spawnEntity(.{
|
|
||||||
component.Player{ .name = "abux" },
|
|
||||||
component.Material{},
|
|
||||||
component.Transform{},
|
|
||||||
});
|
|
||||||
try app.world.spawn(.{
|
|
||||||
component.Material{},
|
|
||||||
component.Transform{},
|
|
||||||
});
|
|
||||||
|
|
||||||
// --- DEBUG ---
|
|
||||||
std.debug.print(
|
|
||||||
"\x1b[35mENTITIES\x1b[0m\n",
|
|
||||||
.{},
|
|
||||||
);
|
|
||||||
|
|
||||||
// --- QUERY ---
|
|
||||||
var query = app.world.query(struct {
|
|
||||||
player: ?*component.Player,
|
|
||||||
material: *component.Material,
|
|
||||||
transform: *component.Transform,
|
|
||||||
}, .{});
|
|
||||||
while (query.next()) |e| {
|
|
||||||
// --- UPDATE POS ---
|
|
||||||
e.transform.pos[0] += 1;
|
|
||||||
e.transform.pos[1] += 1;
|
|
||||||
|
|
||||||
// --- CHANGE PLAYER'S COLOR ---
|
|
||||||
if (e.player) |_| {
|
|
||||||
e.material.albedo = .{ 20, 200, 20, 255 };
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- ENTITIES DEBUG ---
|
|
||||||
std.debug.print("\x1b[36m", .{});
|
|
||||||
std.debug.print(
|
|
||||||
\\| ID: {f}
|
|
||||||
\\| Components:
|
|
||||||
\\ | Player: {?f}
|
|
||||||
\\ | Material: {f}
|
|
||||||
\\ | Transform: {f}
|
|
||||||
\\
|
|
||||||
,
|
|
||||||
.{
|
|
||||||
query.entity(),
|
|
||||||
e.player,
|
|
||||||
e.material,
|
|
||||||
e.transform,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
std.debug.print("\x1b[0m", .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (app.world.getComponent(component.Dead, player)) |_| {
|
|
||||||
std.debug.print("------------------>\n", .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
// try app.world.addComponents(player, .{component.Dead{}});
|
|
||||||
try app.world.addComponents(player, .{
|
|
||||||
component.Dead{},
|
|
||||||
});
|
|
||||||
|
|
||||||
const cmps = try app.world.getComponents(struct {
|
|
||||||
mat: *component.Material,
|
|
||||||
dead: ?*component.Dead,
|
|
||||||
}, player);
|
|
||||||
|
|
||||||
if (cmps.dead) |_| {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn draw(
|
|
||||||
app: *App,
|
|
||||||
) !void {
|
|
||||||
_ = app;
|
|
||||||
std.debug.print("----> DRAW\n", .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn mainPass(
|
|
||||||
app: *App,
|
|
||||||
) !void {
|
|
||||||
_ = app;
|
|
||||||
std.debug.print("----> MAIN_PASS\n", .{});
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
//! ----------------------------------------------------
|
||||||
|
//! ----------------------------------------------------
|
||||||
|
|
||||||
const App = @import("../app.zig");
|
const App = @import("../app.zig");
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const root = @import("../app.zig");
|
|
||||||
|
|
||||||
pub fn of(comptime T: type) u64 {
|
pub fn of(comptime T: type) u64 {
|
||||||
return std.hash.Wyhash.hash(
|
return std.hash.Wyhash.hash(
|
||||||
root.options.seed,
|
57691,
|
||||||
@typeName(T),
|
@typeName(T),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,144 +0,0 @@
|
||||||
// --- LIBS ---
|
|
||||||
const std = @import("std");
|
|
||||||
const Signature = @import("signature.zig");
|
|
||||||
const Column = @import("column.zig");
|
|
||||||
const Entity = @import("entity.zig");
|
|
||||||
const TypeID = @import("../template/type_id.zig").of;
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
//
|
|
||||||
// FIELDS
|
|
||||||
//
|
|
||||||
|
|
||||||
signature: Signature,
|
|
||||||
type_map: std.AutoHashMap(u64, usize),
|
|
||||||
entities: std.ArrayList(Entity),
|
|
||||||
columns: std.ArrayList(Column),
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn init(
|
|
||||||
signature: Signature,
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
) Self {
|
|
||||||
return .{
|
|
||||||
.signature = signature,
|
|
||||||
.type_map = .init(alloc),
|
|
||||||
.entities = .empty,
|
|
||||||
.columns = .empty,
|
|
||||||
.alloc = alloc,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
for (self.columns.items) |*col| {
|
|
||||||
col.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.columns.deinit(self.alloc);
|
|
||||||
self.entities.deinit(self.alloc);
|
|
||||||
self.type_map.deinit();
|
|
||||||
|
|
||||||
self.alloc.free(self.signature.ids);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn addColumn(
|
|
||||||
self: *Self,
|
|
||||||
comptime T: type,
|
|
||||||
) !void {
|
|
||||||
const idx = self.columns.items.len;
|
|
||||||
|
|
||||||
try self.columns.append(
|
|
||||||
self.alloc,
|
|
||||||
try .init(T, self.alloc),
|
|
||||||
);
|
|
||||||
|
|
||||||
try self.type_map.put(
|
|
||||||
TypeID(T),
|
|
||||||
idx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn addColumns(
|
|
||||||
self: *Self,
|
|
||||||
components: anytype,
|
|
||||||
) !void {
|
|
||||||
inline for (components) |T| {
|
|
||||||
try self.addColumn(T);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn add(
|
|
||||||
self: *Self,
|
|
||||||
entity: Entity,
|
|
||||||
components: anytype,
|
|
||||||
) !void {
|
|
||||||
const fields = std.meta.fields(@TypeOf(components));
|
|
||||||
|
|
||||||
if (fields.len != self.columns.items.len) {
|
|
||||||
return error.MissingComponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// const row = self.entities.items.len;
|
|
||||||
try self.entities.append(self.alloc, entity);
|
|
||||||
|
|
||||||
inline for (fields) |field| {
|
|
||||||
const component = @field(components, field.name);
|
|
||||||
|
|
||||||
const T = @TypeOf(component);
|
|
||||||
|
|
||||||
const idx = self.type_map.get(TypeID(T)) orelse {
|
|
||||||
return error.ComponentNotFound;
|
|
||||||
};
|
|
||||||
|
|
||||||
try self.columns.items[idx].append(&component);
|
|
||||||
}
|
|
||||||
|
|
||||||
// for (self.columns.items) |*col| {
|
|
||||||
// if (col. != row + 1) {
|
|
||||||
// return error.RowDesync;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn get(
|
|
||||||
self: *Self,
|
|
||||||
comptime T: type,
|
|
||||||
row: usize,
|
|
||||||
) ?*T {
|
|
||||||
const idx = self.type_map.get(TypeID(T)) orelse return null;
|
|
||||||
const ptr = self.columns.items[idx].getPtr(row) orelse return null;
|
|
||||||
return @ptrCast(@alignCast(ptr));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn remove(
|
|
||||||
self: *Self,
|
|
||||||
row: usize,
|
|
||||||
) ?Entity {
|
|
||||||
const last_entity = self.entities.items[self.entities.items.len - 1];
|
|
||||||
|
|
||||||
_ = self.entities.swapRemove(row);
|
|
||||||
|
|
||||||
for (self.columns.items) |*col| {
|
|
||||||
col.swapRemove(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (row < self.entities.items.len) {
|
|
||||||
return last_entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
@ -1,182 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
const TypeID = @import("../template/type_id.zig").of;
|
|
||||||
const TypedColumn = @import("column_typed.zig").TypedColumn;
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
//
|
|
||||||
// FIELDS
|
|
||||||
//
|
|
||||||
|
|
||||||
storage: *anyopaque,
|
|
||||||
|
|
||||||
type_id: u64,
|
|
||||||
|
|
||||||
elem_size: usize,
|
|
||||||
alignment: usize,
|
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
|
|
||||||
append_fn: *const fn (
|
|
||||||
storage: *anyopaque,
|
|
||||||
value: *const anyopaque,
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
) anyerror!void,
|
|
||||||
|
|
||||||
swap_remove_fn: *const fn (
|
|
||||||
storage: *anyopaque,
|
|
||||||
idx: usize,
|
|
||||||
) void,
|
|
||||||
|
|
||||||
get_ptr_fn: *const fn (
|
|
||||||
storage: *anyopaque,
|
|
||||||
idx: usize,
|
|
||||||
) ?*anyopaque,
|
|
||||||
|
|
||||||
deinit_fn: *const fn (
|
|
||||||
storage: *anyopaque,
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
) void,
|
|
||||||
|
|
||||||
init_fn: *const fn (
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
) anyerror!Self,
|
|
||||||
|
|
||||||
/// ------------------------------------------
|
|
||||||
/// ------------------------------------------
|
|
||||||
pub fn init(
|
|
||||||
comptime T: type,
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
) !Self {
|
|
||||||
const typed = try alloc.create(TypedColumn(T));
|
|
||||||
|
|
||||||
typed.* = TypedColumn(T).init();
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.storage = typed,
|
|
||||||
|
|
||||||
.type_id = TypeID(T),
|
|
||||||
|
|
||||||
.elem_size = @sizeOf(T),
|
|
||||||
.alignment = @alignOf(T),
|
|
||||||
|
|
||||||
.alloc = alloc,
|
|
||||||
|
|
||||||
.append_fn = struct {
|
|
||||||
fn f(
|
|
||||||
storage: *anyopaque,
|
|
||||||
value: *const anyopaque,
|
|
||||||
allocator: std.mem.Allocator,
|
|
||||||
) !void {
|
|
||||||
const col: *TypedColumn(T) = @ptrCast(@alignCast(storage));
|
|
||||||
try col.append(value, allocator);
|
|
||||||
}
|
|
||||||
}.f,
|
|
||||||
|
|
||||||
.swap_remove_fn = struct {
|
|
||||||
fn f(
|
|
||||||
storage: *anyopaque,
|
|
||||||
idx: usize,
|
|
||||||
) void {
|
|
||||||
const col: *TypedColumn(T) = @ptrCast(@alignCast(storage));
|
|
||||||
col.swapRemove(idx);
|
|
||||||
}
|
|
||||||
}.f,
|
|
||||||
|
|
||||||
.get_ptr_fn = struct {
|
|
||||||
fn f(
|
|
||||||
storage: *anyopaque,
|
|
||||||
idx: usize,
|
|
||||||
) ?*anyopaque {
|
|
||||||
const col: *TypedColumn(T) = @ptrCast(@alignCast(storage));
|
|
||||||
if (idx >= col.items.items.len) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return &col.items.items[idx];
|
|
||||||
}
|
|
||||||
}.f,
|
|
||||||
|
|
||||||
.deinit_fn = struct {
|
|
||||||
fn f(
|
|
||||||
storage: *anyopaque,
|
|
||||||
allocator: std.mem.Allocator,
|
|
||||||
) void {
|
|
||||||
const col: *TypedColumn(T) = @ptrCast(@alignCast(storage));
|
|
||||||
col.deinit(allocator);
|
|
||||||
allocator.destroy(col);
|
|
||||||
}
|
|
||||||
}.f,
|
|
||||||
|
|
||||||
.init_fn = struct {
|
|
||||||
fn f(a: std.mem.Allocator) !Self {
|
|
||||||
return init(T, a);
|
|
||||||
}
|
|
||||||
}.f,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ------------------------------------------
|
|
||||||
/// ------------------------------------------
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
self.deinit_fn(
|
|
||||||
self.storage,
|
|
||||||
self.alloc,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ------------------------------------------
|
|
||||||
/// Create a new empty Column of the same type
|
|
||||||
/// ------------------------------------------
|
|
||||||
pub fn cloneEmpty(self: *Self) !Self {
|
|
||||||
return self.init_fn(self.alloc);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ------------------------------------------
|
|
||||||
/// Copy a single element from another column
|
|
||||||
/// Both columns must store the same concrete type
|
|
||||||
/// ------------------------------------------
|
|
||||||
pub fn copyElement(
|
|
||||||
self: *Self,
|
|
||||||
other: *Self,
|
|
||||||
idx: usize,
|
|
||||||
) !void {
|
|
||||||
if (other.getPtr(idx)) |ptr| {
|
|
||||||
try self.append(ptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ------------------------------------------
|
|
||||||
/// ------------------------------------------
|
|
||||||
pub fn append(
|
|
||||||
self: *Self,
|
|
||||||
value: *const anyopaque,
|
|
||||||
) !void {
|
|
||||||
try self.append_fn(
|
|
||||||
self.storage,
|
|
||||||
value,
|
|
||||||
self.alloc,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ------------------------------------------
|
|
||||||
/// ------------------------------------------
|
|
||||||
pub fn swapRemove(
|
|
||||||
self: *Self,
|
|
||||||
idx: usize,
|
|
||||||
) void {
|
|
||||||
self.swap_remove_fn(
|
|
||||||
self.storage,
|
|
||||||
idx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ------------------------------------------
|
|
||||||
/// ------------------------------------------
|
|
||||||
pub fn getPtr(
|
|
||||||
self: *Self,
|
|
||||||
idx: usize,
|
|
||||||
) ?*anyopaque {
|
|
||||||
return self.get_ptr_fn(
|
|
||||||
self.storage,
|
|
||||||
idx,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
/// ------------------------------------------
|
|
||||||
/// ------------------------------------------
|
|
||||||
pub fn TypedColumn(comptime T: type) type {
|
|
||||||
return struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
//
|
|
||||||
// FIELDS
|
|
||||||
//
|
|
||||||
|
|
||||||
items: std.ArrayList(T),
|
|
||||||
|
|
||||||
/// ------------------------------------------
|
|
||||||
/// ------------------------------------------
|
|
||||||
pub fn init() @This() {
|
|
||||||
return .{
|
|
||||||
.items = .empty,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ------------------------------------------
|
|
||||||
/// ------------------------------------------
|
|
||||||
pub fn deinit(
|
|
||||||
self: *Self,
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
) void {
|
|
||||||
self.items.deinit(alloc);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ------------------------------------------
|
|
||||||
/// ------------------------------------------
|
|
||||||
pub fn append(
|
|
||||||
self: *Self,
|
|
||||||
value: *const anyopaque,
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
) !void {
|
|
||||||
const typed: *const T = @ptrCast(@alignCast(value));
|
|
||||||
try self.items.append(alloc, typed.*);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ------------------------------------------
|
|
||||||
/// ------------------------------------------
|
|
||||||
pub fn swapRemove(
|
|
||||||
self: *Self,
|
|
||||||
idx: usize,
|
|
||||||
) void {
|
|
||||||
_ = self.items.swapRemove(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ------------------------------------------
|
|
||||||
/// ------------------------------------------
|
|
||||||
pub fn getPtr(
|
|
||||||
self: *Self,
|
|
||||||
idx: usize,
|
|
||||||
) *anyopaque {
|
|
||||||
return &self.items.items[idx];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub const IDType = u64;
|
|
||||||
|
|
||||||
id: IDType,
|
|
||||||
|
|
||||||
pub fn format(
|
|
||||||
self: @This(),
|
|
||||||
writer: *std.Io.Writer,
|
|
||||||
) !void {
|
|
||||||
try writer.print("{d}", .{self.id});
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
archetype_idx: usize,
|
|
||||||
row: usize,
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
const World = @import("world.zig");
|
|
||||||
const Archetype = @import("archetype.zig");
|
|
||||||
const Entity = @import("entity.zig");
|
|
||||||
const QueryFilters = @import("query_filters.zig");
|
|
||||||
const TypeID = @import("../template/type_id.zig").of;
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn Query(
|
|
||||||
comptime Result: type,
|
|
||||||
comptime Filters: QueryFilters,
|
|
||||||
) type {
|
|
||||||
return struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
const result_fields = std.meta.fields(Result);
|
|
||||||
|
|
||||||
//
|
|
||||||
// FIELDS
|
|
||||||
//
|
|
||||||
|
|
||||||
world: *World,
|
|
||||||
arch_cursor: usize,
|
|
||||||
row_cursor: usize,
|
|
||||||
|
|
||||||
current_arch: ?*Archetype,
|
|
||||||
current_row: usize,
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn init(world: *World) Self {
|
|
||||||
return .{
|
|
||||||
.world = world,
|
|
||||||
.arch_cursor = 0,
|
|
||||||
.row_cursor = 0,
|
|
||||||
|
|
||||||
.current_arch = null,
|
|
||||||
.current_row = 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
fn matches(arch: *Archetype) bool {
|
|
||||||
// --- REQUIRED COMPONENTS ---
|
|
||||||
inline for (result_fields) |field| {
|
|
||||||
// TODO: Clean this shit up
|
|
||||||
const FieldT = field.type;
|
|
||||||
const is_optional = @typeInfo(FieldT) == .optional;
|
|
||||||
|
|
||||||
const PtrT = if (is_optional) @typeInfo(FieldT).optional.child else FieldT;
|
|
||||||
const T = std.meta.Child(PtrT);
|
|
||||||
|
|
||||||
if (!arch.type_map.contains(TypeID(T))) {
|
|
||||||
// --- SKIP OPTIONALS ---
|
|
||||||
if (!is_optional) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- WITH ---
|
|
||||||
inline for (Filters.with) |T| {
|
|
||||||
if (!arch.type_map.contains(TypeID(T))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- WITHOUT ---
|
|
||||||
inline for (Filters.without) |T| {
|
|
||||||
if (arch.type_map.contains(TypeID(T))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn next(self: *Self) ?Result {
|
|
||||||
while (self.arch_cursor < self.world.archetypes.items.len) {
|
|
||||||
var arch = &self.world.archetypes.items[self.arch_cursor];
|
|
||||||
|
|
||||||
// --- SKIP ARCHETYPES ---
|
|
||||||
if (!matches(arch)) {
|
|
||||||
self.arch_cursor += 1;
|
|
||||||
self.row_cursor = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- END ROWS ---
|
|
||||||
if (self.row_cursor >= arch.entities.items.len) {
|
|
||||||
self.arch_cursor += 1;
|
|
||||||
self.row_cursor = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const row = self.row_cursor;
|
|
||||||
|
|
||||||
self.row_cursor += 1;
|
|
||||||
self.current_arch = arch;
|
|
||||||
self.current_row = row;
|
|
||||||
|
|
||||||
var result: Result = undefined;
|
|
||||||
|
|
||||||
inline for (result_fields) |field| {
|
|
||||||
// TODO: Clean this shit up
|
|
||||||
const FieldT = field.type;
|
|
||||||
|
|
||||||
const is_optional = @typeInfo(FieldT) == .optional;
|
|
||||||
|
|
||||||
const PtrT = if (is_optional) @typeInfo(FieldT).optional.child else FieldT;
|
|
||||||
|
|
||||||
const T = std.meta.Child(PtrT);
|
|
||||||
|
|
||||||
const ptr = arch.get(T, row);
|
|
||||||
|
|
||||||
if (is_optional) {
|
|
||||||
@field(result, field.name) = ptr;
|
|
||||||
} else {
|
|
||||||
@field(result, field.name) = ptr orelse return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn entity(self: *const Self) Entity {
|
|
||||||
return self.current_arch.?.entities.items[
|
|
||||||
self.current_row
|
|
||||||
];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
with: []const type = &.{},
|
|
||||||
without: []const type = &.{},
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
pub const World = @import("world.zig");
|
|
||||||
pub const Entity = @import("entity.zig");
|
|
||||||
pub const Archetype = @import("archetype.zig");
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
// --- LIBS ---
|
|
||||||
const std = @import("std");
|
|
||||||
const root = @import("../app.zig");
|
|
||||||
const TypeID = @import("../template/type_id.zig").of;
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
//
|
|
||||||
// FIELDS
|
|
||||||
//
|
|
||||||
|
|
||||||
ids: []u64,
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn eql(a: Self, b: Self) bool {
|
|
||||||
return std.mem.eql(u64, a.ids, b.ids);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn hash(self: Self) u64 {
|
|
||||||
return std.hash.Wyhash.hash(
|
|
||||||
root.options.seed,
|
|
||||||
std.mem.sliceAsBytes(self.ids),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,401 +0,0 @@
|
||||||
// --- LIBS ---
|
|
||||||
const std = @import("std");
|
|
||||||
const Archetype = @import("archetype.zig");
|
|
||||||
const Signature = @import("signature.zig");
|
|
||||||
const Entity = @import("entity.zig");
|
|
||||||
const EntityRecord = @import("entity_record.zig");
|
|
||||||
const QueryFilters = @import("query_filters.zig");
|
|
||||||
const Query = @import("query.zig").Query;
|
|
||||||
const TypeID = @import("../template/type_id.zig").of;
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
//
|
|
||||||
// FIELDS
|
|
||||||
//
|
|
||||||
|
|
||||||
archetypes: std.ArrayList(Archetype),
|
|
||||||
entity_map: std.AutoHashMap(Entity, EntityRecord),
|
|
||||||
signature_map: std.AutoHashMap(u64, usize),
|
|
||||||
next_entity: Entity.IDType,
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn init(alloc: std.mem.Allocator) Self {
|
|
||||||
return .{
|
|
||||||
.archetypes = .empty,
|
|
||||||
.entity_map = .init(alloc),
|
|
||||||
.signature_map = .init(alloc),
|
|
||||||
.next_entity = 1,
|
|
||||||
.alloc = alloc,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
// --- ARCHETYPES ---
|
|
||||||
for (self.archetypes.items) |*arch| {
|
|
||||||
arch.deinit();
|
|
||||||
}
|
|
||||||
self.entity_map.deinit();
|
|
||||||
self.archetypes.deinit(self.alloc);
|
|
||||||
self.signature_map.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn spawn(
|
|
||||||
self: *Self,
|
|
||||||
components: anytype,
|
|
||||||
) !void {
|
|
||||||
_ = try self.spawnEntity(components);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn addComponents(
|
|
||||||
self: *Self,
|
|
||||||
entity: Entity,
|
|
||||||
components: anytype,
|
|
||||||
) !void {
|
|
||||||
const record = self.entity_map.get(entity) orelse return;
|
|
||||||
const new_fields = std.meta.fields(@TypeOf(components));
|
|
||||||
const old_count = self.archetypes.items[record.archetype_idx].signature.ids.len;
|
|
||||||
|
|
||||||
// --- BUILD COMBINED SIGNATURE (dedup against existing) ---
|
|
||||||
var ids = try self.alloc.alloc(u64, old_count + new_fields.len);
|
|
||||||
defer self.alloc.free(ids);
|
|
||||||
|
|
||||||
for (self.archetypes.items[record.archetype_idx].signature.ids, 0..) |id, i| {
|
|
||||||
ids[i] = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
var total: usize = old_count;
|
|
||||||
inline for (new_fields) |field| {
|
|
||||||
const component = @field(components, field.name);
|
|
||||||
const id = TypeID(@TypeOf(component));
|
|
||||||
var dominated = false;
|
|
||||||
|
|
||||||
for (ids[0..total]) |existing| {
|
|
||||||
if (existing == id) {
|
|
||||||
dominated = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dominated) {
|
|
||||||
ids[total] = id;
|
|
||||||
total += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (total == old_count) return;
|
|
||||||
|
|
||||||
std.mem.sort(u64, ids[0..total], {}, std.sort.asc(u64));
|
|
||||||
const signature_hash = std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(ids[0..total]));
|
|
||||||
|
|
||||||
// --- FIND OR CREATE NEW ARCHETYPE ---
|
|
||||||
const new_arch_idx =
|
|
||||||
self.signature_map.get(signature_hash) orelse blk: {
|
|
||||||
const old = &self.archetypes.items[record.archetype_idx];
|
|
||||||
const sig_ids = try self.alloc.dupe(u64, ids[0..total]);
|
|
||||||
var arch: Archetype = .init(.{ .ids = sig_ids }, self.alloc);
|
|
||||||
|
|
||||||
// --- CLONE OLD COLUMNS ---
|
|
||||||
for (old.columns.items) |*col| {
|
|
||||||
try arch.columns.append(self.alloc, try col.cloneEmpty());
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- COPY OLD TYPE MAPPINGS ---
|
|
||||||
var old_iter = old.type_map.iterator();
|
|
||||||
while (old_iter.next()) |entry| {
|
|
||||||
try arch.type_map.put(entry.key_ptr.*, entry.value_ptr.*);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- ADD NEW COLUMNS ---
|
|
||||||
inline for (new_fields) |field| {
|
|
||||||
const component = @field(components, field.name);
|
|
||||||
const T = @TypeOf(component);
|
|
||||||
var already = false;
|
|
||||||
for (old.signature.ids) |old_id| {
|
|
||||||
if (old_id == TypeID(T)) {
|
|
||||||
already = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!already) try arch.addColumn(T);
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.archetypes.append(self.alloc, arch);
|
|
||||||
const idx = self.archetypes.items.len - 1;
|
|
||||||
try self.signature_map.put(signature_hash, idx);
|
|
||||||
break :blk idx;
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- RE-FETCH ---
|
|
||||||
const old_arch = &self.archetypes.items[record.archetype_idx];
|
|
||||||
const new_arch = &self.archetypes.items[new_arch_idx];
|
|
||||||
|
|
||||||
// --- COPY OLD COMPONENT VALUES ---
|
|
||||||
try new_arch.entities.append(
|
|
||||||
self.alloc,
|
|
||||||
entity,
|
|
||||||
);
|
|
||||||
|
|
||||||
for (old_arch.columns.items, 0..) |*old_col, i| {
|
|
||||||
if (old_col.getPtr(record.row)) |ptr| {
|
|
||||||
try new_arch.columns.items[i].append(ptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- ADD NEW COMPONENT VALUES ---
|
|
||||||
inline for (new_fields) |field| {
|
|
||||||
const component = @field(components, field.name);
|
|
||||||
const T = @TypeOf(component);
|
|
||||||
|
|
||||||
var already = false;
|
|
||||||
for (old_arch.signature.ids) |old_id| {
|
|
||||||
if (old_id == TypeID(T)) {
|
|
||||||
already = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!already) {
|
|
||||||
const idx = new_arch.type_map.get(TypeID(T)) orelse return error.ComponentNotFound;
|
|
||||||
try new_arch.columns.items[idx].append(&component);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- REMOVE FROM OLD ARCHETYPE ---
|
|
||||||
if (old_arch.remove(record.row)) |moved| {
|
|
||||||
self.entity_map.getPtr(moved).?.row = record.row;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- UPDATE ENTITY MAP ---
|
|
||||||
self.entity_map.getPtr(entity).?.archetype_idx = new_arch_idx;
|
|
||||||
self.entity_map.getPtr(entity).?.row = new_arch.entities.items.len - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn getComponent(
|
|
||||||
self: *Self,
|
|
||||||
comptime T: type,
|
|
||||||
entity: Entity,
|
|
||||||
) ?*T {
|
|
||||||
const record = self.entity_map.get(entity) orelse return null;
|
|
||||||
const arch = &self.archetypes.items[record.archetype_idx];
|
|
||||||
return arch.get(T, record.row);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn getComponents(
|
|
||||||
self: *Self,
|
|
||||||
comptime Result: type,
|
|
||||||
entity: Entity,
|
|
||||||
) error{ComponentNotRegistered}!Result {
|
|
||||||
// --- RESULT ---
|
|
||||||
var result: Result = undefined;
|
|
||||||
|
|
||||||
// --- GET FIELDS ---
|
|
||||||
inline for (std.meta.fields(Result)) |field| {
|
|
||||||
// --- FIELD INFO ---
|
|
||||||
const FieldType = field.type;
|
|
||||||
|
|
||||||
switch (@typeInfo(FieldType)) {
|
|
||||||
.optional => |opt| {
|
|
||||||
const ptr_info = @typeInfo(opt.child).pointer;
|
|
||||||
@field(result, field.name) = self.getComponent(ptr_info.child, entity);
|
|
||||||
},
|
|
||||||
|
|
||||||
.pointer => |ptr| {
|
|
||||||
@field(result, field.name) =
|
|
||||||
self.getComponent(ptr.child, entity) orelse
|
|
||||||
return error.ComponentNotRegistered;
|
|
||||||
},
|
|
||||||
|
|
||||||
else => @compileError(
|
|
||||||
"getComponents fields must be *T or ?*T; field '" ++ field.name ++ "' has type '" ++
|
|
||||||
@typeName(FieldType) ++ "'",
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn hasComponent(
|
|
||||||
self: *Self,
|
|
||||||
comptime T: type,
|
|
||||||
entity: Entity,
|
|
||||||
) bool {
|
|
||||||
return self.getComponent(T, entity) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn despawn(
|
|
||||||
self: *Self,
|
|
||||||
entity: Entity,
|
|
||||||
) void {
|
|
||||||
const record = self.entity_map.get(entity) orelse return;
|
|
||||||
if (self.archetypes.items[record.archetype_idx].remove(record.row)) |moved| {
|
|
||||||
self.entity_map.getPtr(moved).?.row = record.row;
|
|
||||||
}
|
|
||||||
_ = self.entity_map.remove(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn spawnEntity(
|
|
||||||
self: *Self,
|
|
||||||
components: anytype,
|
|
||||||
) !Entity {
|
|
||||||
const Components = @TypeOf(components);
|
|
||||||
|
|
||||||
const fields = std.meta.fields(Components);
|
|
||||||
|
|
||||||
// --- SIGNATURE ---
|
|
||||||
var ids: [fields.len]u64 = undefined;
|
|
||||||
inline for (fields, 0..) |field, i| {
|
|
||||||
const component = @field(
|
|
||||||
components,
|
|
||||||
field.name,
|
|
||||||
);
|
|
||||||
ids[i] = TypeID(@TypeOf(component));
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- SORT TYPE ID'S ---
|
|
||||||
std.mem.sort(
|
|
||||||
u64,
|
|
||||||
&ids,
|
|
||||||
{},
|
|
||||||
std.sort.asc(u64),
|
|
||||||
);
|
|
||||||
|
|
||||||
const signature_hash = std.hash.Wyhash.hash(0, std.mem.sliceAsBytes(&ids));
|
|
||||||
|
|
||||||
// --- FIND OR CREATE ARCHETYPE ---
|
|
||||||
const archetype_idx =
|
|
||||||
self.signature_map.get(signature_hash) orelse blk: {
|
|
||||||
const sig_ids = try self.alloc.dupe(u64, &ids);
|
|
||||||
|
|
||||||
var arch: Archetype = .init(.{ .ids = sig_ids }, self.alloc);
|
|
||||||
|
|
||||||
// --- NEW COLUMNS ---
|
|
||||||
inline for (fields) |field| {
|
|
||||||
const component = @field(components, field.name);
|
|
||||||
try arch.addColumn(@TypeOf(component));
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- ADD ARCHETYPE ---
|
|
||||||
try self.archetypes.append(
|
|
||||||
self.alloc,
|
|
||||||
arch,
|
|
||||||
);
|
|
||||||
|
|
||||||
// --- ADD SIGNATURE ---
|
|
||||||
const idx = self.archetypes.items.len - 1;
|
|
||||||
try self.signature_map.put(
|
|
||||||
signature_hash,
|
|
||||||
idx,
|
|
||||||
);
|
|
||||||
|
|
||||||
break :blk idx;
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- NEW ENTITY ---
|
|
||||||
const entity = self.createEntity();
|
|
||||||
|
|
||||||
// --- ADD ROW ---
|
|
||||||
var arch = &self.archetypes.items[archetype_idx];
|
|
||||||
|
|
||||||
const row = arch.entities.items.len;
|
|
||||||
|
|
||||||
try arch.add(
|
|
||||||
entity,
|
|
||||||
components,
|
|
||||||
);
|
|
||||||
|
|
||||||
// --- STORE ENTITY LOCATION ---
|
|
||||||
try self.entity_map.put(entity, .{
|
|
||||||
.archetype_idx = archetype_idx,
|
|
||||||
.row = row,
|
|
||||||
});
|
|
||||||
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
fn createEntity(self: *Self) Entity {
|
|
||||||
const entity = Entity{ .id = self.next_entity };
|
|
||||||
self.next_entity += 1;
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
fn createArchetype(
|
|
||||||
self: *Self,
|
|
||||||
comptime Components: type,
|
|
||||||
) !usize {
|
|
||||||
const fields =
|
|
||||||
std.meta.fields(Components);
|
|
||||||
|
|
||||||
var ids = try self.alloc.alloc(
|
|
||||||
u64,
|
|
||||||
fields.len,
|
|
||||||
);
|
|
||||||
|
|
||||||
inline for (fields, 0..) |field, i| {
|
|
||||||
ids[i] = TypeID(field.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
const sig: Signature = .{
|
|
||||||
.ids = ids,
|
|
||||||
};
|
|
||||||
|
|
||||||
var arch: Archetype = .init(
|
|
||||||
sig,
|
|
||||||
self.alloc,
|
|
||||||
);
|
|
||||||
|
|
||||||
inline for (fields) |field| {
|
|
||||||
try arch.addColumn(
|
|
||||||
field.type,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.archetypes.append(
|
|
||||||
self.alloc,
|
|
||||||
arch,
|
|
||||||
);
|
|
||||||
|
|
||||||
return self.archetypes.items.len - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
/// Example:
|
|
||||||
/// ```
|
|
||||||
/// var query = world.query(struct {
|
|
||||||
/// player: ?*Player,
|
|
||||||
/// pos: *Position,
|
|
||||||
/// vel: *Velocity,
|
|
||||||
/// }, .{});
|
|
||||||
/// while (query.next()) |entity| {
|
|
||||||
/// if (entity.player) |player| {}
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
/// ----------------------------------------------------
|
|
||||||
pub fn query(
|
|
||||||
self: *Self,
|
|
||||||
comptime Result: type,
|
|
||||||
comptime Filters: QueryFilters,
|
|
||||||
) Query(Result, Filters) {
|
|
||||||
return .init(self);
|
|
||||||
}
|
|
||||||
Loading…
Add table
Reference in a new issue