Basic controller impl., some file reordering
This commit is contained in:
249
src/graph.zig
249
src/graph.zig
@@ -1,60 +1,27 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const utils = @import("graph/utils.zig");
|
||||||
|
const Resource = @import("graph/resource.zig");
|
||||||
|
const System = @import("graph/system.zig");
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// - Use arena allocator?
|
// - Use arena allocator?
|
||||||
// - Resolve missing resource problem
|
// - Resolve missing resource problem
|
||||||
// - Split up this file
|
|
||||||
|
pub const Controller = Resource.Controller;
|
||||||
|
|
||||||
const MAX_SYSTEM_REQUESTS = 8;
|
const MAX_SYSTEM_REQUESTS = 8;
|
||||||
const DEFAULT_SYSTEM_CAPACITY = 16;
|
const DEFAULT_SYSTEM_CAPACITY = 16;
|
||||||
|
const DEFAULT_CONTROLLERS = 2;
|
||||||
|
|
||||||
const HashType = u32;
|
const ResourceMap = std.AutoArrayHashMapUnmanaged(utils.Hash, Resource);
|
||||||
pub const HashAlgorithm = std.crypto.hash.blake2.Blake2s(32);
|
|
||||||
|
|
||||||
const Resource = struct {
|
|
||||||
/// Aligned pointer
|
|
||||||
pointer: *anyopaque,
|
|
||||||
/// Storage
|
|
||||||
mem: []u8,
|
|
||||||
};
|
|
||||||
const ResourceMap = std.AutoArrayHashMapUnmanaged(u32, Resource);
|
|
||||||
|
|
||||||
const System = struct {
|
|
||||||
const Request = union(enum) {
|
|
||||||
resource: HashType,
|
|
||||||
// TODO:
|
|
||||||
// - Params
|
|
||||||
// - Controller
|
|
||||||
};
|
|
||||||
const RequestList = []const Request;
|
|
||||||
|
|
||||||
function_runner: *const fn ([]const *anyopaque) void,
|
|
||||||
requested_types: RequestList,
|
|
||||||
|
|
||||||
fn from_function(comptime function: anytype, alloc: std.mem.Allocator) !System {
|
|
||||||
validate_system(function);
|
|
||||||
|
|
||||||
var requests: [@typeInfo(@TypeOf(function)).Fn.params.len]Request = undefined;
|
|
||||||
inline for (0.., @typeInfo(@TypeOf(function)).Fn.params) |i, param| {
|
|
||||||
switch (@typeInfo(param.type.?).Pointer.child) {
|
|
||||||
else => |resource_type| requests[i] = .{ .resource = hash_type(resource_type) },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return System{
|
|
||||||
.requested_types = try alloc.dupe(Request, &requests),
|
|
||||||
.function_runner = generate_runner(function),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
fn deinit(self: System, alloc: std.mem.Allocator) void {
|
|
||||||
alloc.free(self.requested_types);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const SystemQueue = std.ArrayListUnmanaged(System);
|
const SystemQueue = std.ArrayListUnmanaged(System);
|
||||||
|
const Controllers = std.ArrayListUnmanaged(Controller);
|
||||||
|
|
||||||
|
/// Assumed to be thread-safe
|
||||||
alloc: std.mem.Allocator,
|
alloc: std.mem.Allocator,
|
||||||
resources: ResourceMap,
|
resources: ResourceMap,
|
||||||
system_queue: SystemQueue,
|
system_queue: SystemQueue,
|
||||||
|
controllers: Controllers,
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
pub fn init(alloc: std.mem.Allocator) !Self {
|
pub fn init(alloc: std.mem.Allocator) !Self {
|
||||||
@@ -64,17 +31,30 @@ pub fn init(alloc: std.mem.Allocator) !Self {
|
|||||||
var system_queue = try SystemQueue.initCapacity(alloc, DEFAULT_SYSTEM_CAPACITY);
|
var system_queue = try SystemQueue.initCapacity(alloc, DEFAULT_SYSTEM_CAPACITY);
|
||||||
errdefer system_queue.deinit(alloc);
|
errdefer system_queue.deinit(alloc);
|
||||||
|
|
||||||
|
var controllers = try Controllers.initCapacity(alloc, DEFAULT_CONTROLLERS);
|
||||||
|
errdefer controllers.deinit(alloc);
|
||||||
|
|
||||||
|
errdefer for (controllers.items) |*controller| {
|
||||||
|
controller.deinit();
|
||||||
|
};
|
||||||
|
|
||||||
|
for (0..DEFAULT_CONTROLLERS) |_| {
|
||||||
|
const controller = try Controller.create(alloc);
|
||||||
|
controllers.appendAssumeCapacity(controller);
|
||||||
|
}
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.resources = resources,
|
.resources = resources,
|
||||||
.system_queue = system_queue,
|
.system_queue = system_queue,
|
||||||
|
.controllers = controllers,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit(self: *Self) void {
|
||||||
var resource_iter = self.resources.iterator();
|
var resource_iter = self.resources.iterator();
|
||||||
while (resource_iter.next()) |entry| {
|
while (resource_iter.next()) |entry| {
|
||||||
self.alloc.free(entry.value_ptr.mem);
|
entry.value_ptr.deinit(self.alloc);
|
||||||
}
|
}
|
||||||
self.resources.clearAndFree(self.alloc);
|
self.resources.clearAndFree(self.alloc);
|
||||||
self.resources.deinit(self.alloc);
|
self.resources.deinit(self.alloc);
|
||||||
@@ -83,159 +63,105 @@ pub fn deinit(self: *Self) void {
|
|||||||
self.alloc.free(system.requested_types);
|
self.alloc.free(system.requested_types);
|
||||||
}
|
}
|
||||||
self.system_queue.deinit(self.alloc);
|
self.system_queue.deinit(self.alloc);
|
||||||
|
|
||||||
|
for (self.controllers.items) |*controller| {
|
||||||
|
controller.deinit();
|
||||||
|
}
|
||||||
|
self.controllers.deinit(self.alloc);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enqueue_system(self: *Self, comptime function: anytype) !void {
|
fn enqueue_system(self: *Self, system: System) !void {
|
||||||
validate_system(function);
|
|
||||||
const system = try System.from_function(function, self.alloc);
|
|
||||||
errdefer system.deinit(self.alloc);
|
errdefer system.deinit(self.alloc);
|
||||||
|
|
||||||
try self.system_queue.append(self.alloc, system);
|
try self.system_queue.append(self.alloc, system);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_all_systems(self: *Self) GraphError!void {
|
fn run_all_systems(self: *Self) GraphError!void {
|
||||||
while (self.system_queue.items.len > 0) {
|
while (self.system_queue.items.len > 0) {
|
||||||
const next_system = self.system_queue.getLast();
|
const next_system = self.system_queue.pop();
|
||||||
defer next_system.deinit(self.alloc);
|
defer next_system.deinit(self.alloc);
|
||||||
defer _ = self.system_queue.pop();
|
|
||||||
|
|
||||||
try self.run_system(next_system);
|
try self.run_system(next_system);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Does not consume the system
|
/// Does not deallocate the system
|
||||||
fn run_system(self: *Self, system: System) GraphError!void {
|
fn run_system(self: *Self, system: System) GraphError!void {
|
||||||
var buffer: [MAX_SYSTEM_REQUESTS]*anyopaque = undefined;
|
var buffer: [MAX_SYSTEM_REQUESTS]*anyopaque = undefined;
|
||||||
|
var controller: ?Controller = null;
|
||||||
|
errdefer if (controller) |*c| c.deinit();
|
||||||
|
|
||||||
var buffer_len: usize = 0;
|
var buffer_len: usize = 0;
|
||||||
for (system.requested_types) |request| {
|
for (system.requested_types) |request| {
|
||||||
switch (request) {
|
switch (request) {
|
||||||
.resource => |resource| {
|
.resource => |resource| {
|
||||||
buffer[buffer_len] = self.get_anyopaque_resource(resource) orelse return GraphError.MissingResource;
|
buffer[buffer_len] = self.get_anyopaque_resource(resource) orelse return GraphError.MissingResource;
|
||||||
},
|
},
|
||||||
|
.controller => {
|
||||||
|
controller = try self.get_controller();
|
||||||
|
buffer[buffer_len] = @ptrCast(&controller.?);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
buffer_len += 1;
|
buffer_len += 1;
|
||||||
}
|
}
|
||||||
system.function_runner(buffer[0..buffer_len]);
|
system.function_runner(buffer[0..buffer_len]);
|
||||||
|
|
||||||
|
if (controller) |c| {
|
||||||
|
defer controller = null;
|
||||||
|
try self.free_controller(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_commands(self: *Self, commands: []const Controller.Command) !void {
|
||||||
|
for (commands) |command| {
|
||||||
|
switch (command) {
|
||||||
|
.add_resource => |r| try self.add_resource(r),
|
||||||
|
.queue_system => |s| try self.enqueue_system(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_controller(self: *Self) !Controller {
|
||||||
|
if (self.controllers.popOrNull()) |c| {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return Controller.create(self.alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluates and clears the controller (even if errors out)
|
||||||
|
fn free_controller(self: *Self, controller: Controller) !void {
|
||||||
|
var c = controller;
|
||||||
|
try self.apply_commands(c.commands());
|
||||||
|
c.clear();
|
||||||
|
try self.controllers.append(self.alloc, c);
|
||||||
|
// TODO: Handle controller error state
|
||||||
}
|
}
|
||||||
|
|
||||||
pub inline fn get_resource(self: *Self, comptime resource: type) ?*resource {
|
pub inline fn get_resource(self: *Self, comptime resource: type) ?*resource {
|
||||||
validate_resource(resource);
|
utils.validate_resource(resource);
|
||||||
if (get_anyopaque_resource(self, hash_type(resource))) |ptr| {
|
if (get_anyopaque_resource(self, utils.hash_type(resource))) |ptr| {
|
||||||
return @alignCast(@ptrCast(ptr));
|
return @alignCast(@ptrCast(ptr));
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_anyopaque_resource(self: *Self, resource_hash: HashType) ?*anyopaque {
|
fn get_anyopaque_resource(self: *Self, resource_hash: utils.Hash) ?*anyopaque {
|
||||||
if (self.resources.get(resource_hash)) |resource| {
|
if (self.resources.get(resource_hash)) |resource| {
|
||||||
return resource.pointer;
|
return resource.pointer;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copies resource into storage, returning previous value if any
|
/// Discards any previous resource data, resource is assumed to be allocated with `self.alloc`
|
||||||
pub inline fn add_resource(self: *Self, resource: anytype) !?@TypeOf(resource) {
|
pub inline fn add_resource(self: *Self, resource: Resource) !void {
|
||||||
validate_resource(@TypeOf(resource));
|
var previous = try self.resources.fetchPut(self.alloc, resource.hash, resource);
|
||||||
var previous: @TypeOf(resource) = undefined;
|
if (previous) |*p| {
|
||||||
if (try self.add_anyopaque_resource(
|
p.value.deinit(self.alloc);
|
||||||
@ptrCast(&resource),
|
|
||||||
hash_type(@TypeOf(resource)),
|
|
||||||
@sizeOf(@TypeOf(resource)),
|
|
||||||
@alignOf(@TypeOf(resource)),
|
|
||||||
@ptrCast(&previous),
|
|
||||||
)) {
|
|
||||||
return previous;
|
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `previous_output` is expected to be aligned accordingly
|
|
||||||
fn add_anyopaque_resource(
|
|
||||||
self: *Self,
|
|
||||||
resource: *const anyopaque,
|
|
||||||
hash: HashType,
|
|
||||||
size: usize,
|
|
||||||
align_to: usize,
|
|
||||||
previous_output: *anyopaque,
|
|
||||||
) !bool {
|
|
||||||
// TODO: Review this shady function
|
|
||||||
const resource_buffer = try self.alloc.alloc(u8, size + align_to);
|
|
||||||
errdefer self.alloc.free(resource_buffer);
|
|
||||||
|
|
||||||
const align_offset = std.mem.alignPointerOffset(
|
|
||||||
@as([*]u8, @ptrCast(resource_buffer)),
|
|
||||||
align_to,
|
|
||||||
) orelse unreachable;
|
|
||||||
|
|
||||||
@memcpy(
|
|
||||||
resource_buffer[align_offset..size],
|
|
||||||
@as([*]const u8, @ptrCast(resource))[0..size],
|
|
||||||
);
|
|
||||||
const previous = try self.resources.fetchPut(self.alloc, hash, .{
|
|
||||||
.pointer = @ptrCast(resource_buffer[align_offset..]),
|
|
||||||
.mem = resource_buffer,
|
|
||||||
});
|
|
||||||
if (previous) |previous_value| {
|
|
||||||
@memcpy(
|
|
||||||
@as([*]u8, @ptrCast(previous_output)),
|
|
||||||
@as([*]const u8, @ptrCast(&previous_value.value.pointer))[0..size],
|
|
||||||
);
|
|
||||||
self.alloc.free(previous_value.value.mem);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fn hash_type(comptime h_type: type) HashType {
|
|
||||||
return hash_string(@typeName(h_type));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hash_string(comptime name: []const u8) HashType {
|
|
||||||
@setEvalBranchQuota(100000);
|
|
||||||
var output: [@divExact(@bitSizeOf(HashType), 8)]u8 = undefined;
|
|
||||||
|
|
||||||
HashAlgorithm.hash(name, &output, .{});
|
|
||||||
return std.mem.readInt(
|
|
||||||
HashType,
|
|
||||||
output[0..],
|
|
||||||
@import("builtin").cpu.arch.endian(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_resource(comptime resource_type: type) void {
|
|
||||||
switch (@typeInfo(resource_type)) {
|
|
||||||
.Struct, .Enum, .Union => return,
|
|
||||||
else => @compileError("Invalid resource type \"" ++ @typeName(resource_type) ++ "\""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_system(comptime system: anytype) void {
|
|
||||||
const info = @typeInfo(@TypeOf(system));
|
|
||||||
if (info != .Fn) @compileError("System can only be a function, got " ++ @typeName(system));
|
|
||||||
if (info.Fn.return_type != void) @compileError("Systems are not allowed to return any value (" ++ @typeName(info.Fn.return_type.?) ++ " returned)");
|
|
||||||
if (info.Fn.is_var_args) @compileError("System cannot be variadic");
|
|
||||||
if (info.Fn.is_generic) @compileError("System cannot be generic");
|
|
||||||
inline for (info.Fn.params) |param| {
|
|
||||||
if (@typeInfo(param.type.?) != .Pointer) @compileError("Systems can only have pointer parameters");
|
|
||||||
validate_resource(@typeInfo(param.type.?).Pointer.child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_runner(comptime system: anytype) fn ([]const *anyopaque) void {
|
|
||||||
const RunnerImpl = struct {
|
|
||||||
fn runner(resources: []const *anyopaque) void {
|
|
||||||
var args: std.meta.ArgsTuple(@TypeOf(system)) = undefined;
|
|
||||||
inline for (0..@typeInfo(@TypeOf(system)).Fn.params.len) |index| {
|
|
||||||
args[index] = @alignCast(@ptrCast(resources[index]));
|
|
||||||
}
|
|
||||||
@call(.always_inline, system, args);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return RunnerImpl.runner;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const GraphError = error{
|
const GraphError = error{
|
||||||
MissingResource,
|
MissingResource,
|
||||||
|
OutOfMemory,
|
||||||
};
|
};
|
||||||
|
|
||||||
test {
|
test {
|
||||||
@@ -249,19 +175,26 @@ test {
|
|||||||
fn add_ten(rsc: *@This()) void {
|
fn add_ten(rsc: *@This()) void {
|
||||||
rsc.number += 10;
|
rsc.number += 10;
|
||||||
}
|
}
|
||||||
|
fn add_eleven(cmd: *Controller) void {
|
||||||
|
cmd.queue_system(add_ten);
|
||||||
|
cmd.queue_system(add_one);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var graph = try Graph.init(std.testing.allocator);
|
var graph = try Graph.init(std.testing.allocator);
|
||||||
defer graph.deinit();
|
defer graph.deinit();
|
||||||
|
|
||||||
try std.testing.expectEqual(graph.add_resource(TestResource{ .number = 100 }), null);
|
var controller = try graph.get_controller();
|
||||||
|
controller.add_resource(TestResource{ .number = 100 });
|
||||||
|
|
||||||
try graph.enqueue_system(TestResource.add_one);
|
controller.queue_system(TestResource.add_one);
|
||||||
try graph.enqueue_system(TestResource.add_one);
|
controller.queue_system(TestResource.add_one);
|
||||||
try graph.enqueue_system(TestResource.add_one);
|
|
||||||
|
|
||||||
try graph.enqueue_system(TestResource.add_ten);
|
controller.queue_system(TestResource.add_ten);
|
||||||
try graph.enqueue_system(TestResource.add_ten);
|
|
||||||
|
controller.queue_system(TestResource.add_eleven);
|
||||||
|
|
||||||
|
try graph.free_controller(controller);
|
||||||
|
|
||||||
try graph.run_all_systems();
|
try graph.run_all_systems();
|
||||||
|
|
||||||
|
132
src/graph/resource.zig
Normal file
132
src/graph/resource.zig
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const utils = @import("utils.zig");
|
||||||
|
const System = @import("system.zig");
|
||||||
|
const Resource = @This();
|
||||||
|
|
||||||
|
const DEFAULT_CONTROLLER_CAPACITY = 8;
|
||||||
|
|
||||||
|
/// Resource data
|
||||||
|
pointer: *anyopaque,
|
||||||
|
/// Pointer to the memory allocted for this resource
|
||||||
|
buffer: []u8,
|
||||||
|
alignment: u29,
|
||||||
|
hash: utils.Hash,
|
||||||
|
|
||||||
|
pub fn deinit(self: *Resource, alloc: std.mem.Allocator) void {
|
||||||
|
alloc.free(self.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Controller = struct {
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
command_buffer: std.ArrayListUnmanaged(Command),
|
||||||
|
error_state: ErrorState,
|
||||||
|
|
||||||
|
pub const Command = union(enum) {
|
||||||
|
add_resource: Resource,
|
||||||
|
queue_system: System,
|
||||||
|
};
|
||||||
|
pub const ErrorState = union(enum) {
|
||||||
|
ok: void,
|
||||||
|
recoverable: []const u8,
|
||||||
|
unrecoverable: void,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn create(alloc: std.mem.Allocator) !Controller {
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
.command_buffer = try std.ArrayListUnmanaged(Command).initCapacity(alloc, DEFAULT_CONTROLLER_CAPACITY),
|
||||||
|
.error_state = .ok,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns command queue, caller is responsible for freeing it's data
|
||||||
|
/// Call `clean()` afterwards, to clear the command queue
|
||||||
|
pub fn commands(self: *Controller) []const Command {
|
||||||
|
return self.command_buffer.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the command buffer, but does not deallocate it's contents
|
||||||
|
pub fn clear(self: *Controller) void {
|
||||||
|
self.command_buffer.clearRetainingCapacity();
|
||||||
|
switch (self.error_state) {
|
||||||
|
.ok, .unrecoverable => {},
|
||||||
|
.recoverable => |msg| self.alloc.free(msg),
|
||||||
|
}
|
||||||
|
self.error_state = .ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds resource to the global storage, discarding any previously existing data
|
||||||
|
pub inline fn add_resource(self: *Controller, resource: anytype) void {
|
||||||
|
utils.validate_resource(@TypeOf(resource));
|
||||||
|
|
||||||
|
self.add_anyopaque_resource(
|
||||||
|
@ptrCast(&resource),
|
||||||
|
utils.hash_type(@TypeOf(resource)),
|
||||||
|
@sizeOf(@TypeOf(resource)),
|
||||||
|
@alignOf(@TypeOf(resource)),
|
||||||
|
) catch |err| self.fail(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn queue_system(self: *Controller, comptime function: anytype) void {
|
||||||
|
utils.validate_system(function);
|
||||||
|
|
||||||
|
self.queue_system_internal(function) catch |err| self.fail(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn queue_system_internal(self: *Controller, comptime function: anytype) !void {
|
||||||
|
var system = try System.from_function(function, self.alloc);
|
||||||
|
errdefer system.deinit(self.alloc);
|
||||||
|
|
||||||
|
try self.command_buffer.append(self.alloc, .{ .queue_system = system });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `previous_output` is expected to be aligned accordingly
|
||||||
|
fn add_anyopaque_resource(
|
||||||
|
self: *Controller,
|
||||||
|
resource: *const anyopaque,
|
||||||
|
hash: utils.Hash,
|
||||||
|
size: usize,
|
||||||
|
align_to: u29,
|
||||||
|
) !void {
|
||||||
|
// TODO: Review this shady function
|
||||||
|
const resource_buffer = try self.alloc.alloc(u8, size + align_to - 1);
|
||||||
|
errdefer self.alloc.free(resource_buffer);
|
||||||
|
|
||||||
|
const align_offset = std.mem.alignPointerOffset(
|
||||||
|
@as([*]u8, @ptrCast(resource_buffer)),
|
||||||
|
align_to,
|
||||||
|
) orelse unreachable;
|
||||||
|
|
||||||
|
@memcpy(
|
||||||
|
resource_buffer[align_offset..size],
|
||||||
|
@as([*]const u8, @ptrCast(resource))[0..size],
|
||||||
|
);
|
||||||
|
|
||||||
|
try self.command_buffer.append(self.alloc, .{ .add_resource = .{
|
||||||
|
.pointer = @ptrCast(resource_buffer[align_offset..]),
|
||||||
|
.buffer = resource_buffer,
|
||||||
|
.alignment = align_to,
|
||||||
|
.hash = hash,
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
|
||||||
|
const ControllerError = std.mem.Allocator.Error;
|
||||||
|
fn fail(self: *Controller, err: ControllerError) void {
|
||||||
|
if (self.error_state == .unrecoverable) return;
|
||||||
|
if (self.error_state == .recoverable) self.alloc.free(self.error_state.recoverable);
|
||||||
|
switch (err) {
|
||||||
|
error.OutOfMemory => self.error_state = .unrecoverable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Controller) void {
|
||||||
|
for (self.command_buffer.items) |*command| {
|
||||||
|
switch (command.*) {
|
||||||
|
.add_resource => |*resource| resource.deinit(self.alloc),
|
||||||
|
.queue_system => |*system| system.deinit(self.alloc),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.clear();
|
||||||
|
self.command_buffer.deinit(self.alloc);
|
||||||
|
}
|
||||||
|
};
|
35
src/graph/system.zig
Normal file
35
src/graph/system.zig
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const utils = @import("utils.zig");
|
||||||
|
const Controller = @import("resource.zig").Controller;
|
||||||
|
|
||||||
|
function_runner: *const fn ([]const *anyopaque) void,
|
||||||
|
requested_types: []const Request,
|
||||||
|
|
||||||
|
pub const Request = union(enum) {
|
||||||
|
resource: utils.Hash,
|
||||||
|
controller: void,
|
||||||
|
// TODO:
|
||||||
|
// - Params
|
||||||
|
};
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
pub fn from_function(comptime function: anytype, alloc: std.mem.Allocator) !Self {
|
||||||
|
utils.validate_system(function);
|
||||||
|
|
||||||
|
var requests: [@typeInfo(@TypeOf(function)).Fn.params.len]Request = undefined;
|
||||||
|
inline for (0.., @typeInfo(@TypeOf(function)).Fn.params) |i, param| {
|
||||||
|
switch (@typeInfo(param.type.?).Pointer.child) {
|
||||||
|
Controller => requests[i] = .controller,
|
||||||
|
else => |resource_type| requests[i] = .{ .resource = utils.hash_type(resource_type) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Self{
|
||||||
|
.requested_types = try alloc.dupe(Request, &requests),
|
||||||
|
.function_runner = utils.generate_runner(function),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *const Self, alloc: std.mem.Allocator) void {
|
||||||
|
alloc.free(self.requested_types);
|
||||||
|
}
|
63
src/graph/utils.zig
Normal file
63
src/graph/utils.zig
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Resource = @import("resource.zig");
|
||||||
|
const System = @import("system.zig");
|
||||||
|
|
||||||
|
pub const Hash = u32;
|
||||||
|
const HashAlgorithm = std.crypto.hash.blake2.Blake2s(@bitSizeOf(Hash));
|
||||||
|
|
||||||
|
pub inline fn hash_type(comptime h_type: type) Hash {
|
||||||
|
return hash_string(@typeName(h_type));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash_string(comptime name: []const u8) Hash {
|
||||||
|
@setEvalBranchQuota(100000);
|
||||||
|
var output: [@divExact(@bitSizeOf(Hash), 8)]u8 = undefined;
|
||||||
|
|
||||||
|
HashAlgorithm.hash(name, &output, .{});
|
||||||
|
return std.mem.readInt(
|
||||||
|
Hash,
|
||||||
|
output[0..],
|
||||||
|
@import("builtin").cpu.arch.endian(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate_resource(comptime resource_type: type) void {
|
||||||
|
switch (@typeInfo(resource_type)) {
|
||||||
|
.Struct, .Enum, .Union => return,
|
||||||
|
else => @compileError("Invalid resource type \"" ++ @typeName(resource_type) ++ "\""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate_system(comptime system: anytype) void {
|
||||||
|
const info = @typeInfo(@TypeOf(system));
|
||||||
|
if (info != .Fn) @compileError("System can only be a function, got " ++ @typeName(system));
|
||||||
|
if (info.Fn.return_type != void) @compileError("Systems are not allowed to return any value (" ++ @typeName(info.Fn.return_type.?) ++ " returned)");
|
||||||
|
if (info.Fn.is_var_args) @compileError("System cannot be variadic");
|
||||||
|
if (info.Fn.is_generic) @compileError("System cannot be generic");
|
||||||
|
|
||||||
|
const controller_requests: usize = 0;
|
||||||
|
inline for (info.Fn.params) |param| {
|
||||||
|
if (@typeInfo(param.type.?) != .Pointer) @compileError("Systems can only have pointer parameters");
|
||||||
|
switch (@typeInfo(param.type.?).Pointer.child) {
|
||||||
|
Resource.Controller => {
|
||||||
|
// controller_requests += 1;
|
||||||
|
// _ = &controller_requests;
|
||||||
|
},
|
||||||
|
else => |t| validate_resource(t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (controller_requests > 1) @compileError("A system cannot request controller more than once");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_runner(comptime system: anytype) fn ([]const *anyopaque) void {
|
||||||
|
const RunnerImpl = struct {
|
||||||
|
fn runner(resources: []const *anyopaque) void {
|
||||||
|
var args: std.meta.ArgsTuple(@TypeOf(system)) = undefined;
|
||||||
|
inline for (0..@typeInfo(@TypeOf(system)).Fn.params.len) |index| {
|
||||||
|
args[index] = @alignCast(@ptrCast(resources[index]));
|
||||||
|
}
|
||||||
|
@call(.always_inline, system, args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return RunnerImpl.runner;
|
||||||
|
}
|
Reference in New Issue
Block a user