Scripting your system is still hard. Let's contemplate on how it should be done in case 'your system' happens to be an unholy mess of c++, scheme and self-implemented lispy macro language instead of an unholy mess of JVM and somewhat lispy semantics provided by Clojure.
History
Back when my tile engine project was running inside a Clojure REPL, I had an "immutable" registry that held every relevant game entity inside it. That was then put inside Clojure's mutable atom-container, and every mutation to that registry was ran by calling (swap! registry-atom update id mutating-fn). That setup brought me free events on mutation on a key, because Clojure's atoms are observable. This made it rather trivial to build cause and effect -relationships inside the engine's editor. Then I routed Swing's kbd events to a buffer that knew which keys had been pressed after the engine last polled the kbd state. Last part of the flow was the reagi stream which polled the keydown - buffer every hundreth millisecond, searched the events associated to each of the keys present in the buffer, and called each of those. Mouse worked similarly. Swing frame updated current-mouse-location - atom on :mouse-moved - event, and registry events could read mouse from there. Then there was a reagi stream to which map-view and tileset-view posted mouse-events with coordinates, source and event type as arguments. The final event hashmap looked like this.
Tracking events
This time around my system isn't as nice. Trying to track assignment and other state mutation in a c++ hell isn't easy, and making an event system is going to take somewhat more serious effort. Aaand because c++'s value semantics are funny and I'm too useless to use references or "smart" pointers (protip: anything but full runtime available at compile time isn't smart), stuff is being mostly pass around as pointers.
Okay, that isn't entirely correct. The actual reason is that all my domain objects are being compiled from a lispy macro language (which I call propertier because I suck at branding) that can't do anything but definitions and thus compiles everything to abstract classes that you have to fill the actual methods in by deriving. Thus, every collection using these types is of type std::collection<domain_class*>, which dictates how these are being passed around the system.
A word about definition macro compiler
Anyway, tracking pointer assignment isn't nice. Fortunately, even though this moronic-sounding macro language brings problems, it should also bring solutions. Originally I made this system because I had no desire to hard code an editor GUI for each of the domain classes. Instead I designed and hacked a system that reads definitions of classes' backing fields, the public and private method interface, and each class's properties. For every property the macro compiler outputs a corresponding T getProp() and void setProp(T value); - methods. Compiler also outputs methods that let the user query object's type, list of object's defined properties and their types. The last relevant bit that the compiler outputs are the generic T get(std::string propname) and void set(std::string propname, T value); - methods that let you emulate Clojure's assoc and get in an inferior way. This made it somewhat easier to make a Qt window that reads in a Propertierbase* pointer (which every class generated with this macro compiler inherits), and by reading the object's props generates a gui that lets the end user edit its props as is the easiest for the type. That means a textbox for string props, currently a \d+ - delimited textbox for numbers (which should be changed to a spinner I think), dropdown for enums or other trivially definable groups. This is documented much better here in cpp.
This compiler also outputs methods that try to prin1 object's properties to json and read them from a json string. They're not perfect, but they shooould work on primitive C types, std::string and anything that has functions void to_json(json& j, const T c); and void from_json(const json& j, T c); defined. This compiler defines these for the classes it outputs and assumes they the above holds. These will make saving & reading engine's state easy when it's time the write those. However we're not there yet.
Why is the macro compiler relevant?
Similarly how I've achieved a hacky reflection and an even more hacky symmetrical reader/printer, I could make the propertier-generated code to shout events into an engine-wide message bus. This message bus might be an FRP stream, using a library like Boost Phoenix, but that's relevant only after the user of this engine is able to subscribe to simpler assignment-to-object/-object's-id events. That requires a mapping between object's property names and a list of lambdas to call per assignment. This final call should be done on property's setter, even though I know that's going to fuck up someone trying to override a prop setter.
Maybe I should add a protected method like void handleEvents(std::string propname) to ease the act of overriding trivial stuff. My god this would be so much easier in a language with fucking macros so that the end user hacking my engine could just write a block like
def_prop_override OVERRIDING_CLASS PROP{
// assume code here
}
and the g++ would macroexpand that to
void OVERRIDING_CLASS::setProp(T val)
{
//assume code here
handleEvents("Prop");
}
Meh, I'm ranting tangentially. The prop setter was calling every lambda in the map<propname, list<lambda>>. These lambdas will get this pointer as parameter. This makes it easier to use simple functions instead of trying to screw with classes and inheritance chain. The scripter should know which event they have subscribed a particular lambda to, so it shouldn't need the propname as parameter.
The fun part?
There shouldn't be any distinction between a c++ lambda and a scheme lambda in the callee!
In fact this shouldn't be too hard. Creating an union of std::function<void(Propertierbase*)> and SCM - lambda, and maybe stuffing it inside a struct with a bool field representing the state of which lambda the engine should call. I'll need a c++ event subscribing api for testing purposes and a guile api for actual end users, and these should as similar as possible. In fact, guile api should be just a small layer that unwraps the SCM:s to whatever types c++ api requires and then return something useful & relevant.
Let's write some code examples? You want to update an object's position as another's changes?
leader->addEvent("X", [=](Propertierbase *l) {
bool dummy;
follower->setX(l->get("X", 0, &dummy));
follower->setY(l->get("Y", 0, &dummy));
});
Or the same in scheme
(add-event! leader (lambda (l) (set-prop follower :x (get l :x)) (set-prop follower :y (get l :y))))
What about keyboard and mouse tracking?
I don't know enough yet about doing FRP in C++, so I'm probably going to make this as simple and inelegant as possible. I'll make an int-pair to track mouse coordinates in the editorController - class. For keyboard, I'm going to wire the game window's qt events to update keycode - isDown? - mapping. FRP might make it possible to make events poll this buffer automatically every Nth millisecond. If that is too much work with Boost Phoenix, I'll crap out a thread that continuosly polls this buffer and calls relevant keyboard events when arbitrary keycodes have been marked as DOWN in that buffer.
Maybe I'll publish this, create the relevant issues to github and start hacking once I'm back to my linux workstation.