Repository navigation
Expand file tree
/
Copy pathasync.zig
More file actions
61 lines (56 loc) · 3.14 KB
/
Copy pathasync.zig
File metadata and controls
61 lines (56 loc) · 3.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//! The `openreliant.async` package ([#498](https://github.com/OpenReliant/openreliant/issues/498)):
//! timers (`package`). A script registers a function under a name (`async.register_timer`), and a
//! timer names the function it runs (`async.after`), so that a timer can be kept with a saved game
//! and run after it's loaded, once the script has registered its functions again.
//!
//! A timer counts the seconds its side sees pass: game time for global and object scripts, which
//! stops while the game is paused and between missions (`on_update`), and real time for player and
//! menu scripts (`on_frame`). The timers of an object's scripts go with the object.
const luau = @import("luau.zig");
const State = luau.State;
const runtime_module = @import("runtime.zig");
const Name = runtime_module.Name;
const api = @import("api.zig");
const Call = api.Call;
const data = @import("data.zig");
/// The most timers a side keeps waiting.
pub const max_timers = 4096;
/// What `openreliant.async` holds.
pub const package = struct {
pub const register_timer = api.Native("Registers `handler` under `name`, for the timers of the mod's scripts of the same kind (and on the same object) to run. Register it as the script runs, so that a timer kept with a saved game finds it again after the game is loaded.", "name: string, handler: (data: any) -> ()", api.nothing, registerTimer);
pub const after = api.Function("Runs the function registered under `name` once `seconds` have passed, with `data`, which must be plain data: seconds of game time for global and object scripts, and of real time for player and menu scripts.", &.{ "seconds", "name", "data" }, startTimer);
};
/// `async.register_timer(name, handler)`.
fn registerTimer(call: Call) i32 {
const state = call.state;
const name = state.toString(1) orelse call.raise("expected a name, got {s}", .{state.typeName(1)});
const key = Name.of(name) orelse call.raise("a name has at most {d} bytes", .{runtime_module.max_name});
if (state.typeOf(2) != .function) call.raise("expected a function, got {s}", .{state.typeName(2)});
const context = call.context;
if (context.callbacks == null) {
state.newTable(0, 0);
context.callbacks = state.ref(-1);
state.pop(1);
}
_ = state.pushRef(context.callbacks.?);
state.pushCopy(2);
state.rawSetField(-2, key.slice());
state.pop(1);
return 0;
}
/// `async.after(seconds, name, data)`.
fn startTimer(call: Call, seconds: f32, name: []const u8, payload: ?data.Data) void {
const scripts = call.runtime();
const runner = scripts.runner orelse {
if (payload) |given| scripts.release(given.ref);
call.raise("timers only run while scripts do", .{});
};
const held = Name.of(name) orelse {
if (payload) |given| scripts.release(given.ref);
call.raise("a name has at most {d} bytes", .{runtime_module.max_name});
};
runner.addTimer(.{ .context = call.context, .name = held, .left = @max(seconds, 0), .data = if (payload) |given| given.ref else null }) catch {
if (payload) |given| scripts.release(given.ref);
call.raise("at most {d} timers can wait", .{max_timers});
};
}