lune-org/lune

Support Roblox-like requires, using a `script` global.

Closed this issue · 3 comments

Already been discussed in Discord, but creating an issue for formality.

Lune could be an excellent option for unit-testing Roblox scripts outside a Roblox environment. If scripts are decoupled from engine APIs, there's no reason they shouldn't be able to run in an environment like Lune. Lune could support Roblox-like requires as an opt-in feature, using a Rojo source map to resolve file paths.

In the script environment, this would require a few things:

  • A new Instance userdata which allows for indexing children and getting the parent. Specific instance types shouldn't be necessary; there just needs to be a way to traverse the datamodel.
  • A script global, which is simply the current script's Instance.
  • A game global, an Instance which represents the root of the datamodel.
    • How much functionality should be provided here? To maintain compatibility with most Roblox scripts, GetService should be supported so that datamodel services can be indexed in an idiomatic way.
  • An alternative require implementation which supports referencing Instances. Much of the existing async require logic can be reused.

In addition, under the existing roblox global, Lune could support scripts implementing functionality for Roblox services. Lune can't reimplement everything (and it shouldn't aim to), but allowing users to reimplement what they need would be very powerful. Something like this:

roblox.implementService("HttpService", function(service)
    service:AddMethod("JSONEncode", ...)
end)

One thing to consider is how this pairs with the existing DataModel implementation for reading place/model files. How much functionality there can be reused?

This is probably something that will take some time to get right - but now that the general Lune runtime and its built-in libraries are starting to stabilize I'm definitely more open to working towards helping more specific usecases such as this one. Some general thoughts I have right now are, in no particular order:

Roblox globals

We could add a function like injectRobloxGlobals in the roblox library which would make the script and game globals available after the point in time that function is called. Maybe we could provide a general solution for this - injecting something like require("@lune/task") as a task global would suddenly make Lune work with a lot of signal & promise libraries out there

Roblox-style requires

The script and game globals for require-purposes could probably share most if not all of the existing require functionality, we could use a trait such as IntoRequirePath that anything require should support implements. Instance on the new roblox lib is also completely independent of reading/writing actual roblox files, you can use it without, so we might be able to share some stuff here too

Implementing service / class methods

I really like the idea of letting users implement class methods if they choose to, there are quite a few methods such as Model::PivotTo that are super useful in the context of Lune (imagine a script for a tycoon-like game that duplicates and places the tycoon plot at build-time instead of at runtime or manually) but that are definitely out of scope for the runtime itself. I also think this point specifically may be a lot more straightforward to design for & implement compared to the others, we could do this soon-ish

Did some work to start supporting this in 9fe3b02 - the plan goes something like this:

  1. Use the luau built-in and luau.load with custom environment and require function set, to override default behavior

  2. This custom require function (implemented by the user, in lua) can support whatever they need, be it sourcemaps, mixing instance and string-based requires, ...

  3. Properties and methods can be implemented in lua and added using:

    • roblox.implementProperty(className: string, propertyName: string, getter: function, setter: function?)
    • roblox.implementMethod(className: string, methodName: string, callback: function)

    where all of the functions take the instance as the first argument, and values as the rest. These will also respect instance class hierarchies and try to call a method for BasePart when one for Part is not available, etc.

I think this is really all we need to make workflows such as unit testing viable, and that is the major goal for 0.8.0, so I'll add back the milestone and close this issue once the functions on the roblox built-in are available.

The abovementioned APIs on the roblox built-in are now available as of version 0.7.9!

https://github.com/filiptibell/lune/releases/tag/v0.7.9