Skip to content

Commit b344657

Browse files
authored
feat: add event machinery and global script arguments handling (#57)
1 parent 6c817f8 commit b344657

File tree

7 files changed

+231
-9
lines changed

7 files changed

+231
-9
lines changed

src/ui/components/abstract/ScriptObject.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class ScriptObject extends FrameScriptObject {
2323

2424
set name(name) {
2525
if (this._name) {
26-
this.deregister();
26+
this.unregister();
2727
this._name = null;
2828
}
2929

src/ui/components/simple/Frame.script.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
import EventType from '../../scripting/EventType';
12
import {
23
lua_State,
4+
lua_isstring,
35
lua_pushnil,
46
lua_pushnumber,
7+
lua_tolstring,
8+
luaL_error,
9+
to_jsstring,
510
} from '../../scripting/lua';
611

712
import Frame from './Frame';
@@ -76,7 +81,15 @@ export const HookScript = () => {
7681
return 0;
7782
};
7883

79-
export const RegisterEvent = () => {
84+
export const RegisterEvent = (L: lua_State): number => {
85+
const frame = Frame.getObjectFromStack(L);
86+
87+
if (!lua_isstring(L, 2)) {
88+
return luaL_error(L, 'Usage: %s:RegisterEvent("event")', frame.displayName);
89+
}
90+
91+
const event = to_jsstring(lua_tolstring(L, 2, 0));
92+
frame.registerScriptEvent(event as EventType);
8093
return 0;
8194
};
8295

src/ui/scripting/EventEmitter.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { LinkedList, LinkedListNode } from '../../utils';
2+
3+
import EventType from './EventType';
4+
import FrameScriptObject from './FrameScriptObject';
5+
6+
class EventListenerNode extends LinkedListNode {
7+
listener: FrameScriptObject;
8+
9+
constructor(listener: FrameScriptObject) {
10+
super();
11+
12+
this.listener = listener;
13+
}
14+
}
15+
16+
class EventEmitter {
17+
type: EventType;
18+
listeners: LinkedList<EventListenerNode>;
19+
unregisterListeners: LinkedList<EventListenerNode>;
20+
registerListeners: LinkedList<EventListenerNode>;
21+
signalCount: number;
22+
pendingSignalCount: number;
23+
24+
constructor(type: EventType) {
25+
this.type = type;
26+
this.listeners = LinkedList.using('link');
27+
this.unregisterListeners = LinkedList.using('link');
28+
this.registerListeners = LinkedList.using('link');
29+
this.signalCount = 0;
30+
this.pendingSignalCount = 0;
31+
}
32+
33+
get name() {
34+
return this.type;
35+
}
36+
}
37+
38+
export default EventEmitter;
39+
export { EventListenerNode };

src/ui/scripting/EventType.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
enum EventType {
2+
FRAMES_LOADED = 'FRAMES_LOADED',
3+
SET_GLUE_SCREEN = 'SET_GLUE_SCREEN'
4+
}
5+
6+
export default EventType;

src/ui/scripting/FrameScriptObject.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import EventType from './EventType';
12
import ScriptingContext, { ScriptFunction } from './ScriptingContext';
23
import Script from './Script';
34
import ScriptRegistry from './ScriptRegistry';
@@ -84,16 +85,39 @@ class FrameScriptObject {
8485
}
8586
}
8687

87-
deregister() {
88-
// TODO: Unregister
88+
unregister(_name: string | null = null) {
89+
// TODO: Implementation
90+
}
91+
92+
registerScriptEvent(type: EventType) {
93+
const scripting = ScriptingContext.instance;
94+
95+
const event = scripting.events[type];
96+
if (!event) {
97+
return false;
98+
}
99+
100+
if (event.pendingSignalCount) {
101+
const node = event.unregisterListeners.find((node) => node.listener === this);
102+
if (node) {
103+
event.unregisterListeners.unlink(node);
104+
}
105+
}
106+
107+
const node = event.listeners.find((node) => node.listener === this);
108+
if (!node) {
109+
scripting.registerScriptEvent(this, event);
110+
}
111+
112+
return true;
89113
}
90114

91115
runScript(name: string, argsCount = 0) {
92116
// TODO: This needs to be moved to the caller
93117
const script = this.scripts.get(name);
94118
if (script && script.luaRef) {
95119
// TODO: Pass in remaining arguments
96-
ScriptingContext.instance.executeFunction(script.luaRef, this, argsCount, undefined, undefined);
120+
ScriptingContext.instance.executeFunction(script.luaRef, this, argsCount);
97121
}
98122
}
99123

src/ui/scripting/Script.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ class Script {
2323
this.source = null;
2424
}
2525

26+
get isLuaRegistered() {
27+
return this.luaRef !== null;
28+
}
29+
2630
get wrapper() {
2731
return `return function(${this.args.join(', ')})\n$body\nend`;
2832
}

src/ui/scripting/ScriptingContext.ts

Lines changed: 140 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,23 @@ import {
66
lua_State,
77
lua_atnativeerror,
88
lua_call,
9+
lua_checkstack,
910
lua_createtable,
1011
lua_gc,
1112
lua_getglobal,
1213
lua_getinfo,
1314
lua_getlocal,
1415
lua_getstack,
16+
lua_gettop,
1517
lua_insert,
1618
lua_isstring,
1719
lua_isuserdata,
1820
lua_pcall,
21+
lua_pushboolean,
1922
lua_pushcclosure,
23+
lua_pushnumber,
2024
lua_pushstring,
25+
lua_pushvalue,
2126
lua_rawgeti,
2227
lua_replace,
2328
lua_setglobal,
@@ -36,6 +41,8 @@ import {
3641
import bitLua from './vendor/bit.lua?raw'; // eslint-disable-line import/no-unresolved
3742
import compatLua from './vendor/compat.lua?raw'; // eslint-disable-line import/no-unresolved
3843

44+
import EventType from './EventType';
45+
import EventEmitter, { EventListenerNode } from './EventEmitter';
3946
import FrameScriptObject from './FrameScriptObject';
4047

4148
import * as extraScriptFunctions from './globals/extra';
@@ -54,6 +61,7 @@ class ScriptingContext {
5461
state: lua_State;
5562
errorHandlerRef: lua_Ref;
5663
recursiveTableHash: lua_Ref;
64+
events: Record<EventType, EventEmitter>;
5765

5866
constructor() {
5967
ScriptingContext.instance = this;
@@ -73,6 +81,10 @@ class ScriptingContext {
7381
this.recursiveTableHash = luaL_ref(L, LUA_REGISTRYINDEX);
7482
lua_gc(L, 6, 110);
7583

84+
this.events = Object.assign({}, ...Object.values(EventType).map((type) => ({
85+
[type]: new EventEmitter(type)
86+
})));
87+
7688
// TODO: Is this OK, rather than lua_openbase + friends?
7789
luaL_openlibs(L);
7890
this.execute(bitLua, 'bit.lua');
@@ -140,12 +152,44 @@ class ScriptingContext {
140152
return true;
141153
}
142154

143-
executeFunction(functionRef: lua_Ref, thisArg: FrameScriptObject, givenArgsCount: number, _unk: unknown, _event: unknown) {
155+
executeFunction(functionRef: lua_Ref, thisArg: FrameScriptObject, givenArgsCount: number, _unk?: unknown, event?: EventEmitter) {
144156
const L = this.state;
145157

158+
const stackBase = 1 - givenArgsCount + lua_gettop(L);
146159
let argsCount = givenArgsCount;
147160

148-
// TODO: Global 'this', 'event' and 'argX'
161+
lua_checkstack(L, givenArgsCount + 2);
162+
163+
if (thisArg) {
164+
lua_getglobal(L, 'this');
165+
166+
if (!thisArg.isLuaRegistered) {
167+
thisArg.register();
168+
}
169+
170+
lua_rawgeti(L, LUA_REGISTRYINDEX, thisArg.luaRef!);
171+
lua_setglobal(L, 'this');
172+
}
173+
174+
if (event) {
175+
lua_getglobal(L, 'event');
176+
lua_pushvalue(L, stackBase);
177+
lua_setglobal(L, 'event');
178+
}
179+
180+
const firstArg = event ? 1 : 0;
181+
let globalArgId = 0;
182+
if (firstArg < givenArgsCount) {
183+
for (let i = firstArg; i < givenArgsCount; ++i) {
184+
globalArgId++;
185+
const argName = `arg${globalArgId}`;
186+
lua_getglobal(L, argName);
187+
lua_pushvalue(L, stackBase + firstArg);
188+
lua_setglobal(L, argName);
189+
}
190+
}
191+
192+
lua_checkstack(L, givenArgsCount + 3);
149193

150194
lua_rawgeti(L, LUA_REGISTRYINDEX, this.errorHandlerRef);
151195
lua_rawgeti(L, LUA_REGISTRYINDEX, functionRef);
@@ -159,15 +203,28 @@ class ScriptingContext {
159203
argsCount++;
160204
}
161205

162-
// TODO: Arguments
206+
for (let i = 0; i < givenArgsCount; ++i) {
207+
lua_pushvalue(L, stackBase + i);
208+
}
163209

164210
if (lua_pcall(L, argsCount, 0, -2 - argsCount)) {
165211
lua_settop(L, -2);
166212
}
167213

168214
lua_settop(L, -2);
169215

170-
// TODO: Clean-up
216+
for (let i = globalArgId; i > 0; --i) {
217+
const argName = `arg${globalArgId}`;
218+
lua_setglobal(L, argName);
219+
}
220+
221+
if (event) {
222+
lua_setglobal(L, 'event');
223+
}
224+
225+
if (thisArg) {
226+
lua_setglobal(L, 'this');
227+
}
171228

172229
lua_settop(L, -1 - givenArgsCount);
173230
}
@@ -276,6 +333,85 @@ class ScriptingContext {
276333
lua_pushcclosure(L, func, 0);
277334
lua_setglobal(L, name);
278335
}
336+
337+
registerScriptEvent(object: FrameScriptObject, event: EventEmitter) {
338+
if (event.pendingSignalCount) {
339+
let node = event.registerListeners.find((node) => node.listener === object);
340+
if (node) {
341+
return;
342+
}
343+
344+
node = new EventListenerNode(object);
345+
event.registerListeners.add(node);
346+
} else {
347+
const node = new EventListenerNode(object);
348+
event.listeners.add(node);
349+
}
350+
}
351+
352+
signalEvent(type: EventType, format?: string, ...args: Array<string | number | boolean>) {
353+
const L = this.state;
354+
355+
const event = this.events[type];
356+
if (!event) {
357+
return;
358+
}
359+
360+
let argsCount = 1;
361+
lua_pushstring(L, event.type);
362+
363+
if (format) {
364+
for (const char of format) {
365+
switch (char) {
366+
case 'b':
367+
lua_pushboolean(L, args[argsCount++ - 1] as boolean);
368+
break;
369+
370+
case 'd':
371+
lua_pushnumber(L, args[argsCount++ - 1] as number);
372+
break;
373+
374+
case 'f':
375+
lua_pushnumber(L, args[argsCount++ - 1] as number);
376+
break;
377+
378+
case 's':
379+
lua_pushstring(L, args[argsCount++ - 1] as string);
380+
break;
381+
382+
case 'u':
383+
lua_pushnumber(L, args[argsCount++ - 1] as number);
384+
break;
385+
}
386+
}
387+
}
388+
389+
event.signalCount++;
390+
event.pendingSignalCount++;
391+
392+
lua_checkstack(L, argsCount);
393+
394+
for (const node of event.listeners) {
395+
const unregisterNode = event.unregisterListeners.find((inner) => inner.listener === node.listener);
396+
if (unregisterNode) {
397+
break;
398+
}
399+
400+
const script = node.listener.scripts.get('OnEvent');
401+
if (script?.isLuaRegistered) {
402+
for (let i = 0; i < argsCount; ++i) {
403+
lua_pushvalue(L, -argsCount);
404+
}
405+
406+
this.executeFunction(script.luaRef!, node.listener, argsCount, null, event);
407+
}
408+
}
409+
410+
event.pendingSignalCount--;
411+
412+
// TODO: Unregister listeners
413+
// TODO: Register listeners
414+
}
279415
}
280416

281417
export default ScriptingContext;

0 commit comments

Comments
 (0)