Skip to content

Commit

Permalink
feat: add event machinery and global script arguments handling
Browse files Browse the repository at this point in the history
  • Loading branch information
timkurvers committed Apr 12, 2024
1 parent 6c817f8 commit dac08ad
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 9 deletions.
2 changes: 1 addition & 1 deletion src/ui/components/abstract/ScriptObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class ScriptObject extends FrameScriptObject {

set name(name) {
if (this._name) {
this.deregister();
this.unregister();
this._name = null;
}

Expand Down
15 changes: 14 additions & 1 deletion src/ui/components/simple/Frame.script.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import EventType from '../../scripting/EventType';
import {
lua_State,
lua_isstring,
lua_pushnil,
lua_pushnumber,
lua_tolstring,
luaL_error,
to_jsstring,
} from '../../scripting/lua';

import Frame from './Frame';
Expand Down Expand Up @@ -76,7 +81,15 @@ export const HookScript = () => {
return 0;
};

export const RegisterEvent = () => {
export const RegisterEvent = (L: lua_State): number => {
const frame = Frame.getObjectFromStack(L);

if (!lua_isstring(L, 2)) {
return luaL_error(L, 'Usage: %s:RegisterEvent("event")', frame.displayName);
}

const event = to_jsstring(lua_tolstring(L, 2, 0));
frame.registerScriptEvent(event as EventType);
return 0;
};

Expand Down
39 changes: 39 additions & 0 deletions src/ui/scripting/EventEmitter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { LinkedList, LinkedListNode } from '../../utils';

import EventType from './EventType';
import FrameScriptObject from './FrameScriptObject';

class EventListenerNode extends LinkedListNode {
listener: FrameScriptObject;

constructor(listener: FrameScriptObject) {
super();

this.listener = listener;
}
}

class EventEmitter {
type: EventType;
listeners: LinkedList<EventListenerNode>;
unregisterListeners: LinkedList<EventListenerNode>;
registerListeners: LinkedList<EventListenerNode>;
signalCount: number;
pendingSignalCount: number;

constructor(type: EventType) {
this.type = type;
this.listeners = LinkedList.using('link');
this.unregisterListeners = LinkedList.using('link');
this.registerListeners = LinkedList.using('link');
this.signalCount = 0;
this.pendingSignalCount = 0;
}

get name() {
return this.type;
}
}

export default EventEmitter;
export { EventListenerNode };
6 changes: 6 additions & 0 deletions src/ui/scripting/EventType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
enum EventType {
FRAMES_LOADED = 'FRAMES_LOADED',
SET_GLUE_SCREEN = 'SET_GLUE_SCREEN'
}

export default EventType;
30 changes: 27 additions & 3 deletions src/ui/scripting/FrameScriptObject.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import EventType from './EventType';
import ScriptingContext, { ScriptFunction } from './ScriptingContext';
import Script from './Script';
import ScriptRegistry from './ScriptRegistry';
Expand Down Expand Up @@ -84,16 +85,39 @@ class FrameScriptObject {
}
}

deregister() {
// TODO: Unregister
unregister(_name: string | null = null) {
// TODO: Implementation
}

registerScriptEvent(type: EventType) {
const scripting = ScriptingContext.instance;

const event = scripting.events[type];
if (!event) {
return false;
}

if (event.pendingSignalCount) {
const node = event.unregisterListeners.find((node) => node.listener === this);
if (node) {
event.unregisterListeners.unlink(node);
}
}

const node = event.listeners.find((node) => node.listener === this);
if (!node) {
scripting.registerScriptEvent(this, event);
}

return true;
}

runScript(name: string, argsCount = 0) {
// TODO: This needs to be moved to the caller
const script = this.scripts.get(name);
if (script && script.luaRef) {
// TODO: Pass in remaining arguments
ScriptingContext.instance.executeFunction(script.luaRef, this, argsCount, undefined, undefined);
ScriptingContext.instance.executeFunction(script.luaRef, this, argsCount);
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/ui/scripting/Script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ class Script {
this.source = null;
}

get isLuaRegistered() {
return this.luaRef !== null;
}

get wrapper() {
return `return function(${this.args.join(', ')})\n$body\nend`;
}
Expand Down
144 changes: 140 additions & 4 deletions src/ui/scripting/ScriptingContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,23 @@ import {
lua_State,
lua_atnativeerror,
lua_call,
lua_checkstack,
lua_createtable,
lua_gc,
lua_getglobal,
lua_getinfo,
lua_getlocal,
lua_getstack,
lua_gettop,
lua_insert,
lua_isstring,
lua_isuserdata,
lua_pcall,
lua_pushboolean,
lua_pushcclosure,
lua_pushnumber,
lua_pushstring,
lua_pushvalue,
lua_rawgeti,
lua_replace,
lua_setglobal,
Expand All @@ -36,6 +41,8 @@ import {
import bitLua from './vendor/bit.lua?raw'; // eslint-disable-line import/no-unresolved
import compatLua from './vendor/compat.lua?raw'; // eslint-disable-line import/no-unresolved

import EventType from './EventType';
import EventEmitter, { EventListenerNode } from './EventEmitter';
import FrameScriptObject from './FrameScriptObject';

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

constructor() {
ScriptingContext.instance = this;
Expand All @@ -73,6 +81,10 @@ class ScriptingContext {
this.recursiveTableHash = luaL_ref(L, LUA_REGISTRYINDEX);
lua_gc(L, 6, 110);

this.events = Object.assign({}, ...Object.values(EventType).map((type) => ({
[type]: new EventEmitter(type)
})));

// TODO: Is this OK, rather than lua_openbase + friends?
luaL_openlibs(L);
this.execute(bitLua, 'bit.lua');
Expand Down Expand Up @@ -140,12 +152,44 @@ class ScriptingContext {
return true;
}

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

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

// TODO: Global 'this', 'event' and 'argX'
lua_checkstack(L, givenArgsCount + 2);

if (thisArg) {
lua_getglobal(L, 'this');

if (!thisArg.isLuaRegistered) {
thisArg.register();
}

lua_rawgeti(L, LUA_REGISTRYINDEX, thisArg.luaRef!);
lua_setglobal(L, 'this');
}

if (event) {
lua_getglobal(L, 'event');
lua_pushvalue(L, stackBase);
lua_setglobal(L, 'event');
}

const firstArg = event ? 1 : 0;
let globalArgId = 0;
if (firstArg < givenArgsCount) {
for (let i = firstArg; i < givenArgsCount; ++i) {
globalArgId++;
const argName = `arg${globalArgId}`;
lua_getglobal(L, argName);
lua_pushvalue(L, stackBase + firstArg);
lua_setglobal(L, argName);
}
}

lua_checkstack(L, givenArgsCount + 3);

lua_rawgeti(L, LUA_REGISTRYINDEX, this.errorHandlerRef);
lua_rawgeti(L, LUA_REGISTRYINDEX, functionRef);
Expand All @@ -159,15 +203,28 @@ class ScriptingContext {
argsCount++;
}

// TODO: Arguments
for (let i = 0; i < givenArgsCount; ++i) {
lua_pushvalue(L, stackBase + i);
}

if (lua_pcall(L, argsCount, 0, -2 - argsCount)) {
lua_settop(L, -2);
}

lua_settop(L, -2);

// TODO: Clean-up
for (let i = globalArgId; i > 0; --i) {
const argName = `arg${globalArgId}`;
lua_setglobal(L, argName);
}

if (event) {
lua_setglobal(L, 'event');
}

if (thisArg) {
lua_setglobal(L, 'this');
}

lua_settop(L, -1 - givenArgsCount);
}
Expand Down Expand Up @@ -276,6 +333,85 @@ class ScriptingContext {
lua_pushcclosure(L, func, 0);
lua_setglobal(L, name);
}

registerScriptEvent(object: FrameScriptObject, event: EventEmitter) {
if (event.pendingSignalCount) {
let node = event.registerListeners.find((node) => node.listener === object);
if (node) {
return;
}

node = new EventListenerNode(object);
event.registerListeners.add(node);
} else {
const node = new EventListenerNode(object);
event.listeners.add(node);
}
}

signalEvent(type: EventType, format?: string, ...args: Array<string | number | boolean>) {
const L = this.state;

const event = this.events[type];
if (!event) {
return;
}

let argsCount = 1;
lua_pushstring(L, event.type);

if (format) {
for (const char of format) {
switch (char) {
case 'b':
lua_pushboolean(L, args[argsCount++ - 1] as boolean);
break;

case 'd':
lua_pushnumber(L, args[argsCount++ - 1] as number);
break;

case 'f':
lua_pushnumber(L, args[argsCount++ - 1] as number);
break;

case 's':
lua_pushstring(L, args[argsCount++ - 1] as string);
break;

case 'u':
lua_pushnumber(L, args[argsCount++ - 1] as number);
break;
}
}
}

event.signalCount++;
event.pendingSignalCount++;

lua_checkstack(L, argsCount);

for (const node of event.listeners) {
const unregisterNode = event.unregisterListeners.find((inner) => inner.listener === node.listener);
if (unregisterNode) {
break;
}

const script = node.listener.scripts.get('OnEvent');
if (script?.isLuaRegistered) {
for (let i = 0; i < argsCount; ++i) {
lua_pushvalue(L, -argsCount);
}

this.executeFunction(script.luaRef!, node.listener, argsCount, null, event);
}
}

event.pendingSignalCount--;

// TODO: Unregister listeners
// TODO: Register listeners
}
}

export default ScriptingContext;
Expand Down

0 comments on commit dac08ad

Please sign in to comment.