Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add event machinery and global script arguments handling #57

Merged
merged 1 commit into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading