The goal is to be able to call an event_<name>
method on an object if it implements it, or otherwise do nothing, without needing to explicitly define and implement an interface for every event. The object may also forward the event to any sub-objects it has.
Here is my current code:
module eventobject;
import std.typetuple;
import std.traits;
import std.conv;
/**
* Iterface template that specifies that the class implements
* event_<name>(<args>).
*/
interface Event(string Name, Args...) {
mixin("public void event_"~Name~"(Args);");
}
/**
* Generates an event dispatcher based on which event methods the
* class implements.
*
* This should be used inside of the class whose event dispatcher
* is being generated, and the class should be passed in.
*/
mixin template CreateEventDispatcher(alias cls) {
mixin(EventDispatcherImplGen!cls);
static if(!__traits(hasMember, cls, "_eventDispatcher")) {
protected EventDispatcherBase _eventDispatcher;
}
protected override EventDispatcherBase eventDispatcher() @property {
if(!_eventDispatcher)
_eventDispatcher = this.new EventDispatcher();
return _eventDispatcher;
}
}
/**
* Base object for objects with events.
* Events are functions that return void and are of the form "event_<name>". Event
* methods should not be overloaded.
*/
abstract class EventObject {
/**
* Returns an array of objects to forward events to, or null to not
* do forwarding.
*
* (Overriding `event` to forward events would be better design, but template functions
* are non-virtual and can't be overwritten)
*/
protected EventObject[] getObjectsToForward() { return null; }
/**
* Triggers an event. If the object implements the event, it will call the event method.
* Otherwise, the event is ignored.
*
* If the object returns non-null from getObjectsToForward, also calls the event method
* on all of the returned objects.
*/
public final void event(string name, Args...)(Args a) {
this.eventDispatcher.callEvent!(name, Args)(a);
auto forward = this.getObjectsToForward();
if(forward)
foreach(obj; forward)
obj.event!(name, Args)(a);
}
/**
* Do not use directly; use `event` instead. Returns the event dispatcher,
* which implements the event interfaces and calls the EventObject's methods.
*/
protected abstract EventDispatcherBase eventDispatcher() @property;
}
// //////////////////////////////////////////////////////////////////////////////
// Private stuff
/**
* Do not use directly. Base class for event dispatchers.
*/
abstract class EventDispatcherBase {
public void callEvent(string Name, Args...)(Args args) {
if(cast(Event!(Name, Args)) this)
mixin("(cast(Event!(Name, Args)) this).event_"~Name~"(args);");
}
}
/**
* Returns comma-separated string list of types.
* Ex. "string,int,bool"
*/
private template TypeStrings(bool first, T...) {
static if(T.length == 0) {
enum TypeStrings = "";
} else static if(first) {
enum TypeStrings = T[0].stringof ~ TypeStrings!(false, T[1..$]);
} else {
enum TypeStrings = "," ~ T[0].stringof ~ TypeStrings!(false, T[1..$]);
}
}
/**
* Returns arguments for types.
* Ex. "string arg0, int arg1, bool arg2"
*/
private template TypeStringsWithParams(int n, T...) {
static if(T.length == 0) {
enum TypeStringsWithParams = "";
} else static if(n == 0) {
enum TypeStringsWithParams = T[0].stringof ~ " arg" ~ to!string(n) ~
TypeStringsWithParams!(n+1, T[1..$]);
} else {
enum TypeStringsWithParams = "," ~ T[0].stringof ~ " arg" ~ to!string(n) ~
TypeStringsWithParams!(n+1, T[1..$]);
}
}
/**
* Wrapper around T.stringof for use with CommaSeparatedConcat
*/
private template TypeString(int n, T) {
enum TypeString = T.stringof;
}
/**
* Outputs typed arguments, for use with CommaSeparatedConcat.
* ex. "string arg1", "bool arg5"
*/
private template TypedArg(int n, T) {
enum TypedArg = T.stringof ~ " arg" ~ to!string(n);
}
/**
* Outputs argument references, for use with CommaSeparatedConcat.
* ex. "arg1", "arg5"
*/
private template UntypedArg(int n, T) {
enum UntypedArg = "arg" ~ to!string(n);
}
/**
* Reduces F!(n, T) by concatenating the results with a comma separating each result.
*/
private template CommaSeparatedConcat(int n, alias F, T...) {
static if(T.length == 0) {
enum CommaSeparatedConcat = "";
} else static if(n == 0) {
enum CommaSeparatedConcat = F!(n, T[0]) ~ CommaSeparatedConcat!(n+1, F, T[1..$]);
} else {
enum CommaSeparatedConcat = "," ~ F!(n, T[0]) ~ CommaSeparatedConcat!(n+1, F, T[1..$]);
}
}
/**
* Given an event name and parameters, constructs a method for EventDispatcher.
*/
private template EventDispatcherMethodGen(string name, T...) {
enum EventDispatcherMethodGen = "public void "~name~"("~CommaSeparatedConcat!(0,TypedArg,T) ~
") { this.outer."~name~"("~CommaSeparatedConcat!(0,UntypedArg,T)~"); }";
}
/**
* Given an EventObject class and its members, construts each method for EventDispatcher.
*/
private template EventDispatcherAllMethodsGen(alias cls, string[] S) {
static if(S.length == 0)
enum EventDispatcherAllMethodsGen = "";
else static if(S[0].length <= 6 || S[0][0..6] != "event_")
enum EventDispatcherAllMethodsGen = EventDispatcherAllMethodsGen!(cls, S[1..$]);
else
enum EventDispatcherAllMethodsGen = EventDispatcherMethodGen!(S[0], ParameterTypeTuple!(__traits(getMember, cls, S[0]))) ~
" " ~ EventDispatcherAllMethodsGen!(cls, S[1..$]);
}
/**
* Given an event name and parameters, constructs an Event interface to inherit from.
*/
private template EventDispatcherInterfaceGen(string name, T...) {
enum EventDispatcherInterfaceGen = "Event!(\""~name[6..$]~"\"," ~ CommaSeparatedConcat!(0,TypeString,T) ~")";
}
/**
* Given an EventObject class and its members, constructs a list of Event interfaces for EventDispatcher to inherit.
* The list includes an extra leading comma.
*/
private template EventDispatcherAllInterfacesGen(alias cls, string[] S) {
static if(S.length == 0)
enum EventDispatcherAllInterfacesGen = "";
else static if(S[0].length <= 6 || S[0][0..6] != "event_")
enum EventDispatcherAllInterfacesGen = EventDispatcherAllInterfacesGen!(cls, S[1..$]);
else
enum EventDispatcherAllInterfacesGen = "," ~
EventDispatcherInterfaceGen!(S[0], ParameterTypeTuple!(__traits(getMember, cls, S[0]))) ~
EventDispatcherAllInterfacesGen!(cls, S[1..$]);
}
/**
* Do not use directly. Generates an EventDispatcher implementation
* based on the event functions in the passed members.
*/
template EventDispatcherImplGen(alias cls) {
enum EventDispatcherImplGen = "private final class EventDispatcher : EventDispatcherBase" ~
EventDispatcherAllInterfacesGen!(cls, [__traits(allMembers, cls)]) ~
"{" ~
EventDispatcherAllMethodsGen!(cls, [__traits(allMembers, cls)]) ~
"};";
}
The EventObject
class contains an eventDelegate
object. The eventDelegate
's class is dynamically created for each EventObject
's subclass, using the CreateEventDispatcher
mixin. The mixin scans the class's members for event_<name>
methods, and for each one, a) implements an interface Event!("<name>", Args...)
and b) implements the event function, which simply calls the outer class's event function.
When EventObject.event
is called, it gets the eventDelegate
, and checks if it implements the corresponding Event
interface. If it does, it casts the object to the interface and calls the eventDelegate
's event method, which calls the EventObject
subclass's event method. It also goes through the array of EventObject
s to forward events to, and calls their event
method.
An example class that uses it:
class Foo : EventObject {
private EventObject[] subobjects;
public this(EventObject[] subobjects) {
this.subobjects = subobjects;
}
protected override EventObject[] getObjectsToForward() { return subobjects; }
public void event_test() {
writeln("test");
}
public void event_anotherTest(int i, string j) {
writeln("anotherTest ", i, " ", j);
}
mixin CreateEventDispatcher!Foo;
}
void main() {
EventObject obj = new Foo([]);
obj.event!"test"(); // Prints "test"
obj.event!"anotherTest"(123, "bar"); // Prints "anotherTest 123 bar"
obj.event!"nonexistant"(); // Prints nothing
}
Is there anything I can improve? In particular:
- Is there a way to ditch the
eventDelegate
object and add the interfaces to theEventObject
subclass itself? - Is there a way to call
CreateEventDispatcher
automatically when subclassing?
class Foo
,obj.event!"test"();
, etc. Could you post your actual code? \$\endgroup\$ – Phrancis Dec 4 '14 at 15:54