Let's first define the terminology.
There are 4 separate things:
1) Engine API - Lua code calls C++ functions.
2) Engine handlers -- C++ code calls Lua functions.
3) Events -- Lua to Lua communication. One script sends, another script receives with a delay.
4) Interfaces (not implemented yet) -- Lua to Lua communication. One script uses an API, provided by another script. Both scripts should either be global scripts, or be attached to the same object. Have no delay, and unlike event handlers can return something.
To avoid ambiguity I suggest to use the word "event" only for the 3rd one. I.e. event is always a custom event.
For the same reason it is better to say either "engine handler" or "event handler" instead of just "handler". I will use just one word "handler" only if something applies both to engine handlers and to event handlers.
> 1
akortunov wrote:
1. Personally I do not like event names as string literals, they are error-prone:
Code: Select all
world.activeActors[i]:sendEvent("startJumping", {})
But I suppose than they are inevitable to support custom events. So it is important is to print an error message, which shows an exact location of instruction, which tries to register or send an invalid event.
`object:sendEvent` syntax always means Lua to Lua communication, so enums don't suit here.
If an object receives an event, but there are no handlers for this event, then a warning is logged (already implemented).
And it is quite easy to avoid string literals on the Lua side:
Code: Select all
*********** some_mod_defs.lua ***********
-- it is not attached to an object as a separate script; just a 'header' that used by other scripts as a library
return {
startJumpingEvent = "startJumping",
stopJumpingEvent = "stopJumping"
}
*********** some_mod.lua ****************
local defs = require('some_mod_defs.lua')
return {
eventHandlers = {
[defs.startJumpingEvent] = function(eventData) ... end,
[defs.stopJumpingEvent] = function(eventData) ... end,
}
}
*********** another_mod.lua *************
local some_mod = require('some_mod_defs.lua')
...
actor:sendEvent(some_mod.startJumpingEvent, {})
I'll add an example like this to the scripting documentation, and it is up to mod makers to decide, use this approach or live with literals.
> 2
akortunov wrote:
2. I see an API to register handlers, but see no "mirror" API to un-register handlers (e.g. when player changes related in-game setting). Ideally it would be nice to have similar ways to do similar things.
akortunov wrote: ↑29 Dec 2020, 13:58
urm wrote: ↑29 Dec 2020, 13:06
It is not planned to unregister event handlers. If you want to skip over a particular event, you can just do so
if skip then return end.
Does not looks like a good solution for me as well - it may make code more messy. A common API to work with events is "subscribe on event" and "unsubscribe from event", and it would be better to have a way to do a cleanup rather than do not have it.
After detaching a local script from an object, we need to cleanup automatically the handlers it registered. If handlers can be registered or deregistered at any moment, tracing the list of handlers is hard. Also, since events are interceptable, it would add a lot of mess with calling order and debugging.
akortunov wrote: ↑29 Dec 2020, 13:58
"Subscribe on non-existant event and hope that an another module will define it later" is not a good solution, IMO. In a common case client should not rely on 3d-party code, which may or may not be aware of specific client's needs.
I prefer to look at it the other way around. Every script at the moment of creation defines a list of events it supports (a kind of API), and lifetime of every handler is always the same as lifetime of the script. It is not "subscribing to an event", but "registering an event". The main subject is the receiver rather than the caller.
> 3
akortunov wrote:
3. Test scripts lack an example of writeable access to event data (except of "self" usage).
Local scripts have a writeable access ONLY to "self" (except scripts attached to a player: it will also have an access to the UI and the camera). In order to change some other object, the script can send an event to this object (or to a global script; global scripts will have write access).
There are two reasons for this limitation:
- To avoid concurrent access from several threads. Local scripts of different objects can be processed by different thread (will be implemented in the future; important for performance).
- It is essential for multiplayer, because other objects can be even processed by another client.
As for global scripts, it will have write access, but I haven't implemented such commands yet.
akortunov wrote:
For instance, MWSE-Lua has a "damage" event, which get a writeable argument (amount of damage to apply), so event handler can change an amount of damage which Morrowind will apply, and it is unclear how such case will look and work with current implementation, especially in multiplayer.
Calls of engine handlers (i.e. C++ to Lua calls) are not interceptable. Events and calls of a script interface (i.e. Lua to Lua communication) are interceptable.
To change the amount of damage we need dehardcoding. But the full dehardcoding is not necessary. On the first stage it is enough just to replace the C++ code that applies the damage with a call of an engine handler "applyDamage". The amount is still calculated in C++, but applied (and can be edited) by a Lua script. The script can also use the power of script interfaces to allow other scripts to intercept it.
urm wrote:
So likely damage logic would just be a global script listening to these events, which would calculate the damage and apply it to the object/actor/whatever. It could also generate an event for local scripts to display the stagger animation and play the hit sound.
...
Keep in mind that the above example if just one way to go about implementing the damage logic. It could also be done entirely with local scripts instead, or some other hybrid way, to reduce the latency of the hit effect and stagger animation in multiplayer.
I think it is better to calculate and apply the damage by a local script on the object that receives the damage. It looks cleaner, and it is also important for performance, since global scripts cannot be parallelized.
P.S.
Builds for Windows now work.