From a352f8350ccb237aec9c59fea52fe89993f2a670 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 13 Jun 2020 18:36:57 -0700 Subject: [PATCH] start of scheduler --- zig-ecs/src/ecs/groups.zig | 12 -- zig-ecs/src/process/process.zig | 92 ++++++++++++++ zig-ecs/src/process/scheduler.zig | 200 ++++++++++++++++++++++++++++++ 3 files changed, 292 insertions(+), 12 deletions(-) create mode 100644 zig-ecs/src/process/process.zig create mode 100644 zig-ecs/src/process/scheduler.zig diff --git a/zig-ecs/src/ecs/groups.zig b/zig-ecs/src/ecs/groups.zig index b2aee4e..64b2132 100644 --- a/zig-ecs/src/ecs/groups.zig +++ b/zig-ecs/src/ecs/groups.zig @@ -299,18 +299,6 @@ pub const OwningGroup = struct { storage.swap(storage.data()[pos], entity); } } - - // for (self.group_data.owned[1..]) |type_id| { - // var other_ptr = self.registry.components.getValue(type_id).?; - // var other = @intToPtr(*Storage(u1), other_ptr); - - // var i: usize = self.group_data.current - 1; - // while (true) : (i -= 1) { - // if (i == 0) break; - // const pos = i - 1; - // const entity = - // } - // } } }; diff --git a/zig-ecs/src/process/process.zig b/zig-ecs/src/process/process.zig new file mode 100644 index 0000000..104d3b1 --- /dev/null +++ b/zig-ecs/src/process/process.zig @@ -0,0 +1,92 @@ +pub const Process = struct { + const State = enum(u8) { + uninitialized, running, paused, succeeded, failed, aborted, finished + }; + + updateFn: fn (self: *Process) void, + initFn: ?fn (self: *Process) void = null, + abortedFn: ?fn (self: *Process) void = null, + failedFn: ?fn (self: *Process) void = null, + succeededFn: ?fn (self: *Process) void = null, + + state: State = .uninitialized, + stopped: bool = false, + + /// Terminates a process with success if it's still alive + pub fn succeed(self: *Process) void { + if (self.alive()) self.state = .succeeded; + } + + /// Terminates a process with errors if it's still alive + pub fn fail(self: *Process) void { + if (self.alive()) self.state = .failed; + } + + /// Stops a process if it's in a running state + pub fn pause(self: *ParentType) void { + if (self.state == .running) self.state = .paused; + } + + /// Restarts a process if it's paused + pub fn unpause(self: *Process) void { + if (self.state == .paused) self.state = .running; + } + + /// Aborts a process if it's still alive + pub fn abort(self: *Process, immediately: bool) void { + if (self.alive()) { + self.state = .aborted; + + if (immediately) { + self.tick(); + } + } + } + + /// Returns true if a process is either running or paused + pub fn alive(self: Process) bool { + return self.state == .running or self.state == .paused; + } + + /// Returns true if a process is already terminated + pub fn dead(self: Process) bool { + return self.state == .finished; + } + + pub fn rejected(self: Process) bool { + return self.stopped; + } + + /// Updates a process and its internal state if required + pub fn tick(self: *Process) void { + switch (self.state) { + .uninitialized => { + if (self.initFn) |func| func(self); + self.state = .running; + }, + .running => { + self.updateFn(self); + }, + else => {}, + } + + // if it's dead, it must be notified and removed immediately + switch (self.state) { + .succeeded => { + if (self.succeededFn) |func| func(self); + self.state = .finished; + }, + .failed => { + if (self.failedFn) |func| func(self); + self.state = .finished; + self.stopped = true; + }, + .aborted => { + if (self.abortedFn) |func| func(self); + self.state = .finished; + self.stopped = true; + }, + else => {}, + } + } +}; diff --git a/zig-ecs/src/process/scheduler.zig b/zig-ecs/src/process/scheduler.zig new file mode 100644 index 0000000..7a2e3a4 --- /dev/null +++ b/zig-ecs/src/process/scheduler.zig @@ -0,0 +1,200 @@ +const std = @import("std"); +const Process = @import("process.zig").Process; + +pub const Scheduler = struct { + handlers: std.ArrayList(ProcessHandler), + allocator: *std.mem.Allocator, + + fn createProcessHandler(comptime T: type) ProcessHandler { + var proc = std.testing.allocator.create(T) catch unreachable; + proc.initialize(); + + // get a closure so that we can safely deinit this later + var handlerDeinitFn = struct { + fn deinit(process: *Process, allocator: *std.mem.Allocator) void { + allocator.destroy(@fieldParentPtr(T, "process", process)); + } + }.deinit; + + return .{ + .process = &proc.process, + .deinitChild = handlerDeinitFn, + }; + } + + const Continuation = struct { + handler: *ProcessHandler, + + pub fn init(handler: *ProcessHandler) Continuation { + return .{.handler = handler}; + } + + // TODO: fix and return when ProcessHandler can have next be a ProcessHandler + pub fn next(self: *@This(), comptime T: type) void { // *@This() + var next_handler = createProcessHandler(T); + self.handler.next = .{.deinitChild = next_handler.deinitChild, .process = next_handler.process}; + } + }; + + // TODO: remove this when ProcessHandler can have next be a ProcessHandler + const NextProcessHandler = struct { + deinitChild: fn (process: *Process, allocator: *std.mem.Allocator) void, + process: *Process, + + pub fn asProcessHandler(self: @This()) ProcessHandler { + return .{.deinitChild = self.deinitChild, .process = self.process}; + } + }; + + const ProcessHandler = struct { + deinitChild: fn (process: *Process, allocator: *std.mem.Allocator) void, + process: *Process, + next: ?NextProcessHandler = null, + + pub fn update(self: *ProcessHandler, allocator: *std.mem.Allocator) bool { + self.process.tick(); + + if (self.process.dead()) { + if (!self.process.rejected() and self.next != null) { + // kill the old Process parent + self.deinitChild(self.process, allocator); + + // overwrite our fields and kick off the next process + self.deinitChild = self.next.?.deinitChild; + self.process = self.next.?.process; + self.next = null; // TODO: when ProcessHandler can have next be a ProcessHandler + return self.update(allocator); + } else { + return true; + } + } + + return false; + } + + pub fn deinit(self: @This(), allocator: *std.mem.Allocator) void { + if (self.next) |next_handler| { + next_handler.asProcessHandler().deinit(allocator); + } + self.deinitChild(self.process, allocator); + } + }; + + pub fn init(allocator: *std.mem.Allocator) Scheduler { + return .{ + .handlers = std.ArrayList(ProcessHandler).init(allocator), + .allocator = allocator, + }; + } + + pub fn deinit(self: Scheduler) void { + for (self.handlers.items) |handler| { + handler.deinit(self.allocator); + } + self.handlers.deinit(); + } + + /// Schedules a process for the next tick + pub fn attach(self: *Scheduler, comptime T: type) Continuation { + std.debug.assert(@hasDecl(T, "initialize")); + std.debug.assert(@hasField(T, "process")); + + var handler = createProcessHandler(T); + handler.process.tick(); + + self.handlers.append(handler) catch unreachable; + return Continuation.init(&self.handlers.items[self.handlers.items.len - 1]); + } + + /// Updates all scheduled processes + pub fn update(self: *Scheduler) void { + if (self.handlers.items.len == 0) return; + + var i: usize = self.handlers.items.len - 1; + while (true) : (i -= 1) { + if (self.handlers.items[i].update(self.allocator)) { + var dead_handler = self.handlers.swapRemove(i); + dead_handler.deinit(self.allocator); + } + + if (i == 0) break; + } + } + + /// gets the number of processes still running + pub fn len(self: Scheduler) usize { + return self.handlers.items.len; + } + + /// resets the scheduler to its initial state and discards all the processes + pub fn clear(self: *Scheduler) void { + for (self.handlers.items) |handler| { + handler.deinit(handler.process, self.allocator); + } + self.handlers.items.len = 0; + } + + /// Aborts all scheduled processes. Unless an immediate operation is requested, the abort is scheduled for the next tick + pub fn abort(self: *Scheduler, immediately: bool) void { + for (self.handlers.items) |handler| { + handler.process.abort(immediately); + } + } +}; + +var fart: usize = 666; + +test "" { + std.debug.warn("\n", .{}); + + const Tester = struct { + process: Process, + fart: usize, + + pub fn initialize(self: *@This()) void { + self.process = .{ + .initFn = init, + .updateFn = update, + .abortedFn = aborted, + .failedFn = failed, + .succeededFn = succeeded, + }; + self.fart = fart; + fart += 111; + } + + fn init(process: *Process) void { + const self = @fieldParentPtr(@This(), "process", process); + std.debug.warn("init {}\n", .{self.fart}); + } + + fn aborted(process: *Process) void { + const self = @fieldParentPtr(@This(), "process", process); + std.debug.warn("aborted {}\n", .{self.fart}); + } + + fn failed(process: *Process) void { + const self = @fieldParentPtr(@This(), "process", process); + std.debug.warn("failed {}\n", .{self.fart}); + } + + fn succeeded(process: *Process) void { + const self = @fieldParentPtr(@This(), "process", process); + std.debug.warn("succeeded {}\n", .{self.fart}); + } + + fn update(process: *Process) void { + const self = @fieldParentPtr(@This(), "process", process); + std.debug.warn("update {}\n", .{self.fart}); + process.succeed(); + } + }; + + var scheduler = Scheduler.init(std.testing.allocator); + defer scheduler.deinit(); + + _ = scheduler.attach(Tester).next(Tester); + scheduler.update(); + scheduler.update(); + scheduler.update(); +}