Repository navigation
Expand file tree
/
Copy pathinterfaces.zig
More file actions
157 lines (140 loc) · 6.38 KB
/
Copy pathinterfaces.zig
File metadata and controls
157 lines (140 loc) · 6.38 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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
//! Interfaces ([#498](https://github.com/OpenReliant/openreliant/issues/498)): what a script
//! offers other scripts. A script that returns `interface_name` and `interface` offers the table
//! `interface` under that name, and scripts reach it through `openreliant.interfaces`, as
//! `I.<name>`. Global scripts see the interfaces of the global scripts, an object's scripts those
//! of the scripts on the same object, a missile's those on the same missile, and player and menu
//! scripts each other's.
//!
//! A later script that offers an interface of the same name, in a later mod or later in the same
//! mod, overrides the earlier one: scripts see the latest. The later script's
//! `on_interface_override` gets the earlier interface, so it can call through to it. When a script
//! stops, its interfaces go, and the one it overrode comes back.
const std = @import("std");
const Allocator = std.mem.Allocator;
const luau = @import("luau.zig");
const State = luau.State;
const runtime_module = @import("runtime.zig");
const Runtime = runtime_module.Runtime;
const Context = runtime_module.Context;
const RunsOn = runtime_module.RunsOn;
const Name = runtime_module.Name;
const api = @import("api.zig");
const Call = api.Call;
const builtin = @import("builtin_interfaces.zig");
/// Which scripts see an interface.
pub const Scope = union(enum) {
/// The global and mission scripts, or on the presentation side the player and menu scripts.
global,
/// The scripts on one object, or on one missile.
own: RunsOn,
/// The scope of the scripts of `context`.
pub fn of(context: *const Context) ?Scope {
return switch (context.family) {
.global, .player, .menu => .global,
.object => .{ .own = context.runs_on.? },
.load => null,
};
}
fn eql(scope: Scope, other: Scope) bool {
return switch (scope) {
.global => other == .global,
.own => |runs_on| switch (other) {
.global => false,
.own => |other_runs_on| runs_on.eql(other_runs_on),
},
};
}
};
/// An interface a script offers.
const Entry = struct {
scope: Scope,
name: Name,
table: luau.Ref,
/// The script's mod, as opened for it.
context: *const Context,
};
/// The interfaces offered, oldest first.
pub const Interfaces = struct {
gpa: Allocator,
runtime: *Runtime,
offered: std.ArrayList(Entry) = .empty,
builtins: std.EnumArray(builtin.Group, ?luau.Ref) = .initFill(null),
pub const tag = @backingInt(runtime_module.Tag.interfaces);
pub fn deinit(interfaces: *Interfaces) void {
for (interfaces.builtins.values) |ref| if (ref) |held| interfaces.runtime.release(held);
interfaces.offered.deinit(interfaces.gpa);
}
/// Adds `table` under `name`, offered by a script of `context`, which keeps the reference.
/// Returns the interface it overrides, if any.
pub fn offer(interfaces: *Interfaces, context: *const Context, name: Name, table: luau.Ref) Allocator.Error!?luau.Ref {
const scope = Scope.of(context).?;
const base = if (interfaces.find(scope, name.slice())) |entry| entry.table else interfaces.builtinBase(context.family, name.slice());
try interfaces.offered.append(interfaces.gpa, .{ .scope = scope, .name = name, .table = table, .context = context });
return base;
}
fn builtinBase(interfaces: *Interfaces, family: @import("script.zig").Family, name: []const u8) ?luau.Ref {
inline for (comptime std.enums.values(builtin.Group)) |kind| {
if (std.mem.eql(u8, name, @tagName(kind)) and kind.reachable(family)) {
if (interfaces.builtins.get(kind)) |ref| return ref;
const ref = interfaces.runtime.make(builtin.push, .{kind}) orelse return null;
interfaces.builtins.set(kind, ref);
return ref;
}
}
return null;
}
/// Takes away the interfaces the scripts of `context` offered, as they stop. The references
/// stay with the scripts' own records (`runtime.Offered`).
pub fn removeContext(interfaces: *Interfaces, context: *const Context) void {
var kept: usize = 0;
for (interfaces.offered.items) |entry| {
if (entry.context == context) continue;
interfaces.offered.items[kept] = entry;
kept += 1;
}
interfaces.offered.shrinkRetainingCapacity(kept);
}
/// The latest interface named `name` that scripts of `scope` see.
fn find(interfaces: *const Interfaces, scope: Scope, name: []const u8) ?*const Entry {
var at = interfaces.offered.items.len;
while (at > 0) {
at -= 1;
const entry = &interfaces.offered.items[at];
if (entry.scope.eql(scope) and std.mem.eql(u8, entry.name.slice(), name)) return entry;
}
return null;
}
/// Registers the metatable of `I`, the package `openreliant.interfaces`.
pub fn register(state: *State) void {
state.registerUserdata(tag, "interfaces", &.{
.{ "__index", luau.wrap(get) },
.{ "__newindex", luau.wrap(set) },
});
}
/// Pushes the package `openreliant.interfaces`, which reads the interfaces of whichever script
/// looks them up.
pub fn push(interfaces: *Interfaces, state: *State) void {
const held = state.newUserdata(*Interfaces, tag);
held.* = interfaces;
}
/// `__index`: the latest interface of the name the caller's scripts see, or nil.
fn get(state: *State) i32 {
const interfaces = state.checkUserdata(*Interfaces, 1, tag, "the interfaces package").*;
const name = state.toString(2) orelse state.raise("interfaces: expected a name, got {s}", .{state.typeName(2)});
const call: Call = .of(state, "interfaces");
const scope = Scope.of(call.context) orelse state.raise("{t} scripts have no interfaces yet", .{call.context.family});
const entry = interfaces.find(scope, name) orelse {
if (interfaces.builtinBase(call.context.family, name)) |ref| {
_ = state.pushRef(ref);
return 1;
}
state.pushNil();
return 1;
};
_ = state.pushRef(entry.table);
return 1;
}
fn set(state: *State) i32 {
state.raise("interfaces are offered by returning interface_name and interface, not set", .{});
}
};