-
Notifications
You must be signed in to change notification settings - Fork 0
[RFC] SLua event handling proposal #3
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
base: main
Are you sure you want to change the base?
Conversation
The proposal looks good on an initial skim! If I'm reading it right, there's support multiple timers in one script (awesome). What about |
It might, but we don't have plans for it at present. The implementation for We'll have follow-on proposals for other things like sensors and async task management. |
Edit: I'll add these comments inline on the PR snipJust a check, the concept implementation makes use of If the actual implementation makes use of this, does that mean that Afaik all the beta grid SLua sims are running on one machine, so it's not possible for me to test if it remains consistent. The luau documentation states that For my own slua multi timer implementation I used |
-- Event handlers which stack multiple events together in LSL via an integer `num_detected` parameter | ||
-- TODO: Pass in a special wrapper object for these functions and loop through `num_detected` rather | ||
-- than actually pass in `num_detected`? Seems like almost nobody uses these functions as | ||
-- intended since it's annoying to have to write the loop, everyone just does `llDetectedWhatever(0)` | ||
-- and silently drops the rest of the queued events of that type on the floor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be elaborated on some?
In general I agree that the grouping of some events, is an unintuitive pattern, that is likely the result of resource management from the past.
But
Does this TODO: Pass in a special wrapper object for these functions
mean that it would ALWAYS be done for the scripter, or is it more of a TODO: implement supporting this
?
If this were to be implemented as the new standard it would need careful documenting else we will end up with people still doing ll.DetectedKey(0)
but now having it run 10 times or something.
It would also need to pass in the current "event index" to allow scripters to use the correct data via ll.Detected...
Or a rework of how the ll.Detected...
functions work, so as to not need an index, and pretend that that system doesn't exist. Where in reality something equivalent to the code below is executing, but the scripter only sees the bit after USER CODE
themselves.
local LL = ll;
ll = setmetatable({}, {__index=ll});
(function()
local detectedIndex = nil
ll.SetDetectedIndex = function(index:number) detectedIndex = index end
ll.DetectedKey = function(num:number)
return LL.DetectedKey(num or detectedIndex)
end
end)()
function touch_start(events : number)
for i=0,events-1 do
ll.SetDetectedIndex(i)
touch_handler()
end
end
-- USER CODE
touch_handler = function()
ll.OwnerSay(`Touched By: {ll.DetectedKey()}`)
end
A "clearer" alternative might be to add new "pseudo" events, named something like each_touch_start
or each_touch
etc but that smells in other ways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it more of a TODO: implement supporting this?
Sorry about that, yeah, all TODOs are things that need to be fleshed out (or decided against) for this RFC.
Or a rework of how the ll.Detected... functions work, so as to not need an index, and pretend that that system doesn't exist. [...]
Yeah, we definitely want to get rid of ll.Detected*()
if we can, since the usability story isn't great there.
I guess we're worried about the usability angle. It's not obvious that ll.DetectedKey()
and friends are functions that you should think about calling in that context, or that something like ll.DetectedVel()
is something that will work in some contexts but not others, and that their output is going to be nonsense if you try to use them in a coroutine
once execution is no longer happening under the touch_start()
handler.
Then you have events like sensor
where you want want to handle all those detected items as a single batch, since you might want to select the closest object matching certain criteria or something.
My thinking was that there could be wrapper object that store an index internally, and that have methods like event:getKey()
or event:getVel()
and friends that get marked as invalid when execution leaves the relevant event handler.
These event objects would be inexpensive memory-wise (is_valid
bool + event number index) so no resource worries there, just not sure what makes sense. Passing in a table
of these event objects?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One idea could be returning results in tables for events like sensor
and collision
where there are routinely going to be multiple results.
The various Detected types could be stored in the list as a way to get rid of the Detected
functions. Then llDetectedKey(0)
becomes something like result[1].key
.
This would not be as memory-efficient per event object though, so it has its drawbacks. Ease of use versus resource consumption.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Beware, some scripts call llDetected*(0)
without a proper loop when they really should be looped (discarding incidents that built up during a sleep or forced delay), while some scripts deliberately accept only the first input and then discard the rest. If touch*
, collision*
, sensor
and other events that provide values for the llDetected*
functions are restructured to call a user-defined function (handler) once for every incident, then SLua must not pay additional forced delay time for each time the UDF is called within what would be a batch in LSL. The script should drop further UDF calls if removed from the event before a batch is finished.
Scripters can always implement their own wrappers in SLua. Time will tell whether this is actually necessary. Some will do it out of habit, but I think most will take the path of least resistance. I have never considered it annoying to wrap llDetected*
functions in a loop; batching reduces the number of events in the queue, and fewer events in the queue means a more responsive script.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeh the more I think about this, the more it feels like this should be a new event, and the old events should be marked as "deprecated".
I also like Kristy's idea of "clicked"
Maybe we can have something like this instead
local handler = function(event: TouchedEvent)
ll.OwnerSay(lljson.encode(event)) -- {"type":"start","key":"<uuid>","link":1,"uv":"<0.5,0.5,0.5>",...}
end
LLEvents:on("touched", handler)
type should probably be a number/enum, but I used a string for clarity
This could later be expanded to allow for filtering...
LLEvents:on("touched", handler, {type="end", key=ll.GetOwner()})
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated with an example of how wrappers for the num_detected
type event handlers could work
is adding a In my scripts I use Having a simple Though, that only covers the touch part of things, collisions are different beast... Side note: maybe SLua having few extra handy events, compared to LSL, would encourage more creators to update their scripts or adopt new stuff faster to take advantage of the new features. |
Some ideas... Could we have states?
"num_detected" events
LLEvents:off / LLTimers:off
LLEvents:once and LLEvents:off
LLEvents:listeners
And two questions to know how to teach the changes to the students:
Depending on it i will teach the changes to the students before LLEvents arrives (so the students know what to do when their scripts stop working) or after LLEvents arrives (if the scripts will go on working for some time, better for explaining and testing). Thanks Harold, great project! |
We're not planning to provide this ourselves during the first effort. We're trying to only build out the things that people can't efficiently write themselves in SLua first, then adding wrappers around common usage patterns as we see them crop up in real code. Mostly because there's only a handful of folks working on SLua, and we still have a bunch of "must-haves" to implement :P States could be pretty easily done in user code by having states represented as a table of
Yeah, that seems sensible. It seems like the existing event batching is something people want, but I think we can do it with slightly better usability. We're going to look at writing a wrapper around the existing
This was caused by me not paying attention, I'll fix it :)
It's mainly for debugging. With LSL you always know what handlers are active since all handlers are grouped by state and you can't dynamically add or remove them, but that's not the case in SLua.
They'll be removed immediately, it'll be a breaking API change once it's implemented
Likely not. The |
To script states in LSL-style, could we have functions like:
Is this related to this project? Or do I write a canny request for new LL functions? |
I believe you'll get much more targeted event dropping under the new scheme. Removing all event handlers for a particular event clears the queue of all events of that type once this is actually implemented in C++. Is there another usecase that
We're going to follow up with an |
@HaroldCindy I'm not sure, but I think @Suzanna-Linn's A means to drop/cleanup all registered event handlers at once, could be useful. |
In one of my scripts I use state swapping to clear all the listener handles because very very rarely, my script ends up running out of listeners, and I can't figure out how to reproduce that issue, as far as I can see all my code paths create and remove listeners just fine. I have since rewritten the script in SLua, so hopefully I won't run into that issue. But something like |
Makes sense to me! Once the event API is finalized / merged we can look at more utilities for clearing the sim-side script event queue if they're still necessary, I don't think any of this work would block that. |
Good idea, clearing the pending events of a specific type when all its handlers have been removed is perfect.
Yes, that would be sufficient if it includes a function to remove all listeners without needing to know their individual handles. This would be useful for simulating a state change, as it would avoid the need to wrap ll.Listen() to store the handles. Although a ll.ListenRemoveAll() would have a more general use, also in LSL, so lazy scripters (like me) wouldn't leave listeners active so often. |
Actually, I was referring to removing all the listeners activated with ll.Listen(), which would allow for simulating a state change in an LSL-style. Although having a parameterless LLEvents:off() to remove all events would be good too, and the same goes for LLTimers:off(). |
We don't want to do too much special logic with missing arguments (since they're just filled with for i, event_name in LLEvents:eventNames() do
LLEvents:off(event_name)
end |
@HaroldCindy does this system allow for custom event handlers or is this out of the scope of the proposal |
Custom event handlers are out of scope for this since the underlying event system SL is using doesn't support them |
Instead of Also the idea for a "click" event is pretty good as this could provide a standard confirmed click. (This should work like click on the web to meet existing and expected UX, where if you press and hold down on a button but then move and let go outside the button does not result in a click) |
I think that adding a |
Pushed an update that changes how |
The new num_detected events are elegant and Lua-ish, but I wonder whether the improvement is really worth it, given that LSL and SLua will coexist for years. Having two different ways to do the same thing might be confusing, especially for less advanced scripters. Anyway, I think that we can still use the LSL-style by adding a line at the beginning of the event handler: |
The problem with keeping things as they are (just calling With coroutines, it's much more likely that someone will store an event index and try to use it with I can make an example of what I mean by that if it'd be helpful to illustrate the issue |
@HaroldCindy I assume this is the issue with local last = nil
function touch_start(touches: integer | number)
if last then
last()
last = nil
end
ll.OwnerSay(`Clicked on face {ll.DetectedTouchFace(0)}`)
last = coroutine.wrap(function()
ll.OwnerSay(`Last clicked on face {ll.DetectedTouchFace(0)}`)
end)
ll.OwnerSay("-------------------------------------------------------")
end The code in the coroutine "feels" like it should print the detected face of when it was created, but will always print the value for the "latest" touch. Another example would possibly be local coro = nil
function touch_start(touches: integer | number)
coro = coroutine.wrap(function()
ll.OwnerSay(`Clicked By: {ll.DetectedName(0)}`)
end)
ll.SetTimerEvent(0.01);
end
function timer()
coro()
ll.SetTimerEvent(0.0)
end Where it prints, |
@WolfGangS Yep, exactly |
end | ||
|
||
-- Do the actual event handling | ||
for i, handler in handlers do | ||
-- We explicitly want errors to bubble up to the global error handler, no pcall(). | ||
handler(event_args) | ||
handler(table.unpack(event_args)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could something like this be useful to avoid calling the next handlers when one returns true
, indicating it has processed the event?
if handler(table.unpack(event_args)) then break end
For example, when multiple dataserver
handlers are waiting for their Id, or multiple listen
handlers are waiting for their channel.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it from the side of optimization, if we aren't going to get more advanced event filtering yet.
But then dislike that code somewhere completely different can prevent a handler from running, and it can change depending on the order handlers register. Though that would be a bad implementation on the scripters part really.
In general I would like @Suzanna-Linn's suggested behaviour.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But then dislike that code somewhere completely different can prevent a handler from running, and it can change depending on the order handlers register.
That's mainly what I'm concerned about, I'm not aware of functionality like this in any event handling APIs in other languages I've looked at. Just because the function you're calling is done with the message, doesn't mean there are no handlers following yours that might be interested in handling that message, and this is especially true in cases where people start using libraries for writing SLua that they didn't write.
I'll have to think on this a little bit, but my gut feeling is that multiple event handlers like this for dataserver()
and friends would be better handled through a single event handler that does dispatch itself (probably through some wrapper API that we'll write eventually.)
This contains the proposed API for event handling and timer management in SLua. Please add any suggestions inline on the code, or as a comment on this PR!
Particularly interested in how people think events like
touch_start
that pass anum_detected
forll.Detected*()
function calls. Right now those events continue to receive anum_detected
, but is there something better we should be doing there?