parent
9dd7ca445a
commit
a352f8350c
@ -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 => {},
|
||||
}
|
||||
}
|
||||
};
|
@ -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();
|
||||
}
|
Loading…
Reference in new issue