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();
    });
}
Edit
Pub: 27 Jun 2024 15:38 UTC
Edit: 27 Jun 2024 16:20 UTC
Views: 128