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

Event handler implementation. #3

Closed
NouberNou opened this Issue Dec 3, 2015 · 10 comments

Comments

Projects
None yet
4 participants
@NouberNou
Copy link
Contributor

NouberNou commented Dec 3, 2015

Framework for adding per-object event handlers. This will be an SQF/callExtension bridge most likely (at least how I am envisioning it) where Intercept will implement a special C++ side call to addEventhandler that will add a specific SQF side callback as well as listing the object internally in the client adding the event. That call back will then be raised when the specific event is handled, assigning _this to a global variable (or someplace we can find it) and notifying all attached Intercept clients of this event and the object.

This is just what I have in my head right now, if there is a better way I am totally for it because this sounds kind of convoluted.

@NouberNou NouberNou added the task label Dec 3, 2015

@NouberNou NouberNou self-assigned this Dec 3, 2015

@NouberNou NouberNou added this to the 1.0 milestone Dec 3, 2015

@thojkooi

This comment has been minimized.

Copy link
Contributor

thojkooi commented Dec 3, 2015

  • Add listeners for all events in sqf
  • listeners will assign _this to a global var in missionnamespace
  • upon event raised, listeners will call an extension with the eventname.
  • extension will get the assigned data in the global var, and raise the event within C++

Might be risky - because we are using global variables for transporting the data. This is something we would have to test.

Unless we can hook into the RV event system from within C++, the only other option I see if using serialization to transfer the data, but that brings a whole train lot of other problems and perf issues.

Perhaps an alternative might be using some kind of queue (sqf array), and monitor that internally?

@jaynus

This comment has been minimized.

Copy link
Collaborator

jaynus commented Dec 4, 2015

I'd prefer to avoid SQF interaction for event hooking. We can hook into the event system Glowbal, and any ghetto SQF workarounds really need to be avoided.

These should be intercepted instead, as the function calls are the same and we already know how to do function hooking and deceleration.

We should hook and catch the function, handle, then pass on to the continuation of the event chain in regular SQF.

@NouberNou

This comment has been minimized.

Copy link
Contributor

NouberNou commented Dec 4, 2015

Anything that requires more memory patching is out, as it would not be BE compliant. BE does memory image checking to see if there are hooks and flags if so.

If BE was not an issue then I'd say yes, but ultimately we do need to support BE for multiple reasons.

@dedmen

This comment has been minimized.

Copy link
Member

dedmen commented Nov 18, 2016

could use #109 for the callback. That could also directly pass _this in the function call.
a unary function intercept_event

target addEventHandler ["killed", "intercept_event _this"];

could also format the command string like intercept_event [_this, eventUUID] or use a binary function. which will make it easier to decide who should receive the event

@dedmen

This comment has been minimized.

Copy link
Member

dedmen commented May 7, 2017

#109 is implemented now.. How about
target addEventHandler ["killed", "<UUID> interceptEvent _this"];

sqf::add_event_handler(target, "killed", "intercept_event ['killed',_this]");

We just have to provide some way to route that eventhandler to a specific place. Maybe a lambda function... But we also have to make sure the eventhandler get's unregistered when the plugin is unloaded. We shoudn't trust on the user doing that.

We are assigning the function pointers to the client functions. Instead of directing the client directly to the addEventHandler function we could redirect him into a lambda function. That registers the eventhandler together the the Modules UUID (I know something like that doesn't yet exist). That way it's quite easy to have a table with which plugin registered which eventhandler. Which also makes it easy for us to remove them when the Plugin is unloaded.
I just had the Idea for that.. I might implement that for RSQF (register SQF Function) because they also have to be removed at Plugin unload to keep them from calling into empty memory. Maybe that's already implemented and working and can be used as a basis for this when this issue will be processed.

@dedmen

This comment has been minimized.

Copy link
Member

dedmen commented May 17, 2017

Additionally to the addEventhandler CODE wrapper we could add
addEventhandler that takes std::function<void(EH args)>.
People can pass their lambda or whatevers in there and in the backend intercept assigns a ID to the eventhandler and calls addEH like in my last comment.

RZSenfo added a commit to Molaron/intercept that referenced this issue Jun 2, 2017

@dedmen

This comment has been minimized.

Copy link
Member

dedmen commented Jun 6, 2017

Okey easy to implement.
On Pluginside we have
holder addEventhandler(object obj_, std::string_view ehname_, std::function<void(game_value)> func_)
(Notice the naming difference from sqf::add_event_handler. In the end it will also be called add_event_handler but I use the camelCase variant here to make it clear what is meant)
This will allow people to pass in lambdas and other crazy stuff.
The holder acts like the RSQF registered_sqf_function When it's refcount goes to 0 the eventhandler will be removed. Removing the need for Plugins to call removeEventhandler and also makes sure every eventhandler is removed when a Plugin unloads and thus preventing any possible user error in that realm.

Plugin side:
In addEventhandler we will generate a Unique ID for the eventhandler and then do a
sqf::add_event_handler(object, ehname_, "[module,UID] interceptEvent _this;
where UID is said Unique ID and module is the hash of the module name. Just because numbers(hash) are faster to handle.
And we will also store the UID connected with the function in a std::map<uint32_t,std::function<...>>

Core side:
interceptEvent will then associate that hash to a module and call it's static function
void event_trigger(uint32_t uid, game_value arguments)
This function will not be accessible to the client and will just be a hidden function just like our assign_functions function.

Plugin side:
That function will take the uid and map it to the function that we stored previously and then just call the function with the arguments.

This can also be extended to every other eventhandler (Not only events of objects) and thus we might also want to have a return value for some eventhandlers.

I think a good solution would be to make the addEventHandler function be a template. And define traits for each possible eventhandler. In these traits we can define the eventHandler name, whether it can return anything, and even how many arguments and what argument types it has.

We would have a enum of eventhandlers and the client would call
addEventHandler<object_fired>(...)
Depending on the template argument addEventHandler will take different arguments.
In this case it would take (https://community.bistudio.com/wiki/Arma_3:_Event_Handlers#Fired)
(object obj_, std::function<void(object,r_string,r_string,r_string,r_string,object,object)>)
or another example addEventhandler<object_handle_rating>(...)
(https://community.bistudio.com/wiki/Arma_3:_Event_Handlers#HandleRating)
which would take
(object obj_, std::function<int(object,int)>)
Assuming rating will only ever be rounded to .0. Otherwise it would be float instead of int.
This could potentially also return std::optional to really conform with the wiki statement in that it CAN return a Number but doesn't have to.

I'm not sure if we can have addEventhandler be a template function. We probably have to go for a class template with operator()
Which we can then easilly pull from the traits by just using operator() = Traits::operator()

I'm not quite sure how we would go about mapping the std::function to the UID then as we have many different types of std::function. Having one map per eventhandler type (Can be stored as static inline variable in the traits. Though we don't have static inline member variables in MSVC yet.) is a possibility but I don't really like cluddering memory with that.

We can also just store the std::function as a pair of <eventhandler type, std::shared_ptr>
And then just switch on the eventhandler type and convert the std::function pointer to the correct type and then call it. Which will be much nicer.

Appendix about UID
The UID only needs to be unique per module. Meaning we can always start at 0 and thus use a std::vector to store our associated functions which gives us much faster lookups which is good because performance in our eventhandler code is hugely important.
We would however need to write a manager that takes note of which places in the vector are free to reuse them when registering new eventhandlers. We could use a std::vector as a fast and size efficient container for this.
The problem in this solution is that when a plugin registers 101 eventhandlers and then removes the first 100 we will still need to have a vector of size 101 because the handlers UID points to it. Meaning we will have some wasted space. I however think this won't be a big concern.

@dedmen

This comment has been minimized.

Copy link
Member

dedmen commented Jun 14, 2017

First part of implementation done.
It looks like this for the client

void myOnDraw() {
    sqf::system_chat("onDraw");
}

void __cdecl intercept::post_init() {
    static auto drawEH = client::addMissionEventHandler<client::eventhandlers_mission::Draw3D>(myOnDraw);
}

Or

void __cdecl intercept::post_init() {
    static auto drawEH = client::addMissionEventHandler<client::eventhandlers_mission::Draw3D>(
        [](){ sqf::system_chat("onDraw"); }
    );
}

Or even Class Member functions.
you need to store the return Value because once the last reference to that get's destructed the Eventhandler will automatically be removed.

For now I'm using pair<uint32,float> as UID for the handlers. The uin32 is randomly generated. and the float is the handle returned by addMissionEventHandler so we have it handy when we want to remove it again.
All handlers are stored in a unordered_map using the UID as index.
there will be one function map for each eventhandler type (generic(addEventHandler), mission (addMissionEventHandler), display (addDisplayEventHandler), ctrl ...)

@dedmen

This comment has been minimized.

Copy link
Member

dedmen commented Jun 16, 2017

Implementation has just moved to develop branch.
Examples:

static auto drawEH = client::addMissionEventHandler<client::eventhandlers_mission::Draw3D>(myOnDraw);
    static auto firedEH = client::addEventHandler<client::eventhandlers_object::Fired>(sqf::player(),[](object unit, r_string weapon, r_string muzzle, r_string mode, r_string ammo, r_string magazine, object projectile, object gunner) {
        sqf::system_chat(sqf::format({ "%1 fired %2 with %3 resulting in %4",unit,weapon,ammo,projectile }));
    });

static because return value needs to stay alive. As it going out of scope deletes the eventhandler.

All Mission/Object eventhandlers implemented.
Some ctrl eventhandlers implemented.
Missing are dialog,object(curator), some ctrl handlers.

Can be implemented later on depending on need.

@dedmen

This comment has been minimized.

Copy link
Member

dedmen commented Oct 31, 2017

Implementation is already on master branch. Besides addMPEventhandler which is included in #169
Also added a easy way to bind a lambda function to a SQF call.
generate_custom_callback() uses the same code as the eventhandlers but returns the SQF code that can be used to call the Function.

@dedmen dedmen closed this Oct 31, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment