dennis / slipstream

ISC License
5 stars 0 forks source link

Restructure Lua integration with Slipstream #90

Closed dennis closed 3 years ago

dennis commented 3 years ago

This PR re-architects how C# functionality is exposed in Lua.

Replacing plugins is: LuaLibrary, LuaInstances and LuaReferences.

LuaLibrary

This thread-safe object will be the main entry point for interacting with c# code from lua.

LuaLibraries needs to implement ILuaLibraryAutoRegistration and by doing so they will be picked up by AutoFac, when in turns results in a ILuaLibraryRepository that contains all the available LuaLibraries.

LuaLibrary needs to be threadsafe!

When needing to use a LuaLibrary, you can in lua do

let audio = require("api/audio")

Slipstream changes require() and will check if there is a LuaLibrary matching the requested name. If so, the LuaLibrary is returned. LuaLibraries can expose themselves given any name, but by convention we'll use api/ prefix. The thought is, that you require a module. And these happens to be Slipstream functionality, but otherwise not really special.

Note: Multiple Lua scripts will get the same LuaLibrary instance. So it needs to be thread-safe!

All LuaLibraries exposes a instance(cfg) function, where cfg is a lua table containing settings to setup the instance. As minimum cfg needs to contain a id key, that identifies exactly which instance is required. If such instance doesn't exist, a new instance is created, otherwise the existing is used.

instance(cfg) will return a reference to the instance.

Lua Instances

A lua instance is started on request. And stopped again, if nothing refers to it. Lua scripts never interact directly with it, but via references. For really simple LuaLibraries, there might not even be a Instance, but for most it will create a InstanceThread that provides the features for the LuaLibrary.

For some LuaLibraries there is a singleton instance, meaning that the id of that instance is ignored, and you'll always get the one instance. For these, the instance id forced to be singleton. IRacingLuaLibrary is an example using a singleton instance.

Lua References

As mentioned earlier these are exposed directly in Lua. These are normally pretty might in code, as they would for most part, just send Events, which the Instance can process and act on. All references are discarded when the script is stopped (e.g lua file is deleted or changed). Once all references to a instance is dropped, the instance might shut down.

Plugins?

Are no more. A lot of code was removed as the new implementation is simpler (I hope) and doesn't need as much management from slipstream.

init.lua

Is changed a lot. Engnie still loads init.lua, but it differs greatly. For one, the syntax changed (instead of register_plugins, we do require("api/..."), but also the hot reloading of lua scripts is move from LuaManagerPlugin to init.lua - and simplified in the process.

This means that you can implement you own logic if you need to. And it also means that lua scripts can start other lua scripts.

Removed plugnis

Migrating scripts

Look at LuaScripts to get an idea on how to migrate lua scripts.

We recommend that you create a config.lua that contains the arguments used for the instance(cfg) call. This means that order of scripts doesn't matter and only the features needed is activated.

Documentation

Documentation is still lacking. Also, now I've remove the Events from the documentation, as https://github.com/dennis/slipstream/pull/91 will offer a view of the events via UI. As this is based on code, these will not be outdated as the previous documentation were.

Example

Let's look at the following line:

local audio = require("api/audio"):instance({ id = "audio"})
audio:say("Hello world")

First require("api/audio") will return AudioLuaLibrary, which provides instance(...). For AudioLuaLIbrary the only required argument is id, which we set to audio. This means, that any other script refering to this id, will share the same resource.

instance({ id = "audio"}) will start up the AudioInstanceThread which takes care of TTS and playing audio. It also creates a AudioLuaReference to it, that is returned. We store this as audio variable.

audio:say("Hello world") invokes say on AudioLuaReference. This will publish an event, asking the instance to play audio.

After these lines is executed, and since there is no handle(event) function, nor any pending functions (via wait or debounce), the script will stop.

Lua scripts and their execution is also done by same approach. The script is evaluated by a Instance and you can get references to it - and start/stop scripts as you please. This can be seen in init.lua.