import std.stdio;
import std.traits;
import std.typecons;
bool hasTemplateUDA(alias SYM, alias UDA)() { // std.traits.hasUDA doesn't normally work with Templates as UDAs
static foreach (attr; __traits(getAttributes, SYM)) {
static if (__traits(isSame, attr, TemplateOf!UDA))
return true;
}
return false;
}
abstract class ProxyableBase {}
abstract class Proxyable(Destination, string IDField, string RetrieverMethod) : ProxyableBase {
static if (isFunction!(__traits(getMember, Destination, IDField))) {
alias ProxyIdentifier = ReturnType!(__traits(getMember, Destination, IDField));
} else {
alias ProxyIdentifier = typeof(__traits(getMember, Destination, IDField));
}
alias ProxyGetter = __traits(getMember, Destination, RetrieverMethod);
auto _proxyID() => __traits(getMember, cast(Destination) this, IDField);
}
struct Proxy(Destination : ProxyableBase) {
alias Identifier = Destination.ProxyIdentifier;
Identifier ident;
this(Identifier ident) {
this.ident = ident;
}
this(Destination dest) {
this.ident = dest ? dest._proxyID : Identifier.init;
}
auto opDispatch(string s, SA...)(SA sargs) @trusted {
alias FUNC = __traits(getMember, Destination, s);
static if (!hasTemplateUDA!(FUNC, Proxy)) {
// Assert fails or compilation errors are hard to see inside opDispatch
pragma(msg, format("Cannot call function %s without @Proxy UDA", fullyQualifiedName!FUNC));
static assert(false);
}
static struct Outbound {
this() @disable;
this(this) @disable;
~this() {
if (!fired) go();
}
alias RT = ReturnType!FUNC;
static if (is(RT : ProxyableBase)) {
alias SUCCESS = void delegate(Proxy!RT);
} else static if (is(RT == void)) {
alias SUCCESS = void delegate();
} else {
alias SUCCESS = void delegate(RT);
}
alias PARAMS = Parameters!FUNC;
private Identifier ident;
private bool fired;
Tuple!PARAMS tup;
SUCCESS successDG;
this(Identifier ident, SA sargs) {
this.ident = ident;
static foreach (i; 0 .. SA.length) { tup[i] = sargs[i]; }
}
alias then = onSuccess;
auto onSuccess(SUCCESS dg) {
this.successDG = dg;
}
void go() {
if (fired) return;
fired = true;
/* **************** */
// MAGIC GOES HERE: Instead of calling the function directly, here's
// where you'd serialize the arguments into a message, send to remote thread/process/server,
// which would then reconstruct, retrieve the remote object via ID, process the method,
// serialize the result, and return to sender. The calling process would need to stay
// resident and manage a message handler to return the results to the supplied delegates
// or handle error codes, timeouts, etc.
/* **************** */
auto dest = Destination.ProxyGetter(ident);
if (!dest) {
writeln(i"No object[ident='$(ident)'] found for method '$(s)'");
return;
}
static if (is(RT == void)) {
__traits(child, dest, FUNC)(tup.expand);
if (successDG !is null) {
successDG();
}
} else {
auto ret = __traits(child, dest, FUNC)(tup.expand);
if (successDG !is null) {
static if (is(RT : ProxyableBase)) {
successDG(Proxy(ret));
} else {
successDG(ret);
}
}
}
}
}
return Outbound(ident, sargs);
}
}
class Person : Proxyable!(Person, "name", "getPerson") {
static Person[string] allPeople;
static auto getPerson(string s){
if (auto p = s in allPeople)
return *p;
return null;
}
string name;
Person friend;
this(string str) {
this.name = str;
allPeople[name] = this;
}
@Proxy:
Person getFriend() => friend;
void bark() {
writeln(i"My name is $(name).");
}
int doubleMyInt(int x) {
return x * 2;
}
}
void main() {
{
// Server-side stuff
new Person("bob").friend = new Person("joe");
}
auto p = Proxy!Person("bob");
p.doubleMyInt(4).then((n) {
writefln("Doubled Result: %s", n);
});
p.bark();
p.getFriend().then((f) {
writeln("I'm the friend, and my name...");
f.bark();
});
}