原文
如果想要更强大示例,下面是使用UDA
来轻松定义无缝回复
是否成功
回调的调用函数
的RPC
模块的概念证明.
proxy.requestName().onSuccess((string str) {
writefln("We got the name reply back from the server: %s", str);
}).onFailure((string errMsg) {
writefln("Something went wrong! Error: %s", errMsg);
});
此例省略
了系统的实际网络部分
,而只是在目标对象
上直接调用
函数,并立即
回复.在真实世界示例中,会序化
函数请求及其参数,及指定要调用对象
的唯一标识
,通过网络或线程
传递,并在目标进程
上解码并运行,然后同样序化
并返回
回复.
可按调用者等待
直到收到回复或失败
的同步
,或定期
处理回调的异步
来调整系统.如,假设把简单的.async
插入调用链,并指定超时
或其他选项
.
除此
外,该程序应完全编译
并说明系统
如何工作.
import std.stdio;
import std.format;
import std.traits;
import std.exception;
import util.extratraits;
class Person {
mixin ProxyReceiver;
string name;
int age;
this(string name, int age) {
this.name = name;
this.age = age;
}
@Proxy
@Reply!(string)
void requestName() {
writefln("SERVER# User requested name");
reply(name);
}
@Proxy
@Reply!(bool)
void setName(string s) {
writefln("SERVER# Changed name to: %s", s);
this.name = s;
reply(true);
}
@Proxy
@Reply!(int)
@Failure!(string)
void doubleEvenNumber(int x) {
writefln("SERVER# User wants to double number: %s", x);
if (!(x % 2)) reply(x * 2, x);
else failure("Supplied number is not even.");
}
}
struct Reply(RA...) {
}
struct Failure(RA...) {
}
mixin template ProxyReceiver() {
Proxy!(typeof(this)) _proxy;
void reply(string funcstr = __FUNCTION__, RA...)(RA rargs) {
_proxy.reply!(funcstr)(rargs);
}
void failure(string funcstr = __FUNCTION__, RA...)(RA rargs) {
_proxy.failure!(funcstr)(rargs);
}
}
class Proxy(Destination) {
Destination dest;
private static abstract class Callback {
MsgNum msgNum;
double startTime, timeout;
}
private static final class CallbackT(alias FUNC) : Callback {
static if (hasUDA!(FUNC, Reply)) {
alias SUCCESS = void delegate(TemplateArgsOf!(getUDAs!(FUNC, Reply)[0]));
SUCCESS successDG;
}
static if (hasUDA!(FUNC, Failure)) {
alias FAILURE = void delegate(TemplateArgsOf!(getUDAs!(FUNC, Failure)[0]));
FAILURE failureDG;
}
}
alias MsgNum = uint;
MsgNum nextMsgNum = 1;
Callback[MsgNum] callbacks;
MsgNum lastMessageNum;
alias FIRE = void delegate();
FIRE[MsgNum] pendingCalls; //在此处使用闭包来模拟`网络延迟`,以留出时间添加回调
this(Destination dest) {
this.dest = dest;
dest._proxy = this;
}
void reply(string funcstr = __FUNCTION__, RA...)(RA rargs) {
mixin(`alias FUNC = `~funcstr~`;`);
alias FQN = fullyQualifiedName!FUNC;
static assert(hasUDA!(FUNC, Reply), "No reply allowed for func: "~FQN);
alias UDA = getUDAs!(FUNC, Reply)[0];
alias RFUNC = void delegate(TemplateArgsOf!UDA);
static assert(canCallFuncWithParameters!(RFUNC, RA), format("Invalid parameters for reply: %s (expected %s)", RA.
stringof, Parameters!RFUNC.stringof));
auto msgNum = lastMessageNum;
auto p = msgNum in callbacks;
enforce(p, format("Callback not found: #%s", msgNum));
auto cbase = *p;
callbacks.remove(msgNum);
auto cb = cast(CallbackT!FUNC) cbase;
enforce(cb, "Callback mismatch: could not cast to %s", CallbackT!FUNC.stringof);
//writefln("准备用参数:%s调用cb(%s)闭包",rargs,cb);
if (cb.successDG !is null) {
cb.successDG(rargs);
cb.successDG = null;
}
}
void failure(string funcstr = __FUNCTION__, RA...)(RA rargs) {
mixin(`alias FUNC = `~funcstr~`;`);
alias FQN = fullyQualifiedName!FUNC;
static assert(hasUDA!(FUNC, Failure), "No failure allowed for func: "~FQN);
alias UDA = getUDAs!(FUNC, Failure)[0];
alias RFUNC = void delegate(TemplateArgsOf!UDA);
static assert(canCallFuncWithParameters!(RFUNC, RA), format("Invalid parameters for failure: %s (expected %s)", RA.
stringof, Parameters!RFUNC.stringof));
auto msgNum = lastMessageNum;
auto p = msgNum in callbacks;
enforce(p, format("Callback not found: #%s", msgNum));
auto cbase = *p;
callbacks.remove(msgNum);
auto cb = cast(CallbackT!FUNC) cbase;
enforce(cb, "Callback mismatch: could not cast to %s", CallbackT!FUNC.stringof);
if (cb.failureDG !is null) {
cb.failureDG(rargs);
cb.failureDG = null;
}
}
struct Outbound(alias FUNC) {
this() @disable;
this(this) @disable;
~this() {
//writefln("~Outbound!%s [%s:%s]", FUNCNAME!FUNC, msgNum, fired);
if (!fired)
go();
}
alias FQN = fullyQualifiedName!FUNC;
static if (hasUDA!(FUNC, Reply)) {
alias SUCCESS = void delegate(TemplateArgsOf!(getUDAs!(FUNC, Reply)[0]));
}
static if (hasUDA!(FUNC, Failure)) {
alias FAILURE = void delegate(TemplateArgsOf!(getUDAs!(FUNC, Failure)[0]));
}
private Proxy proxy;
private MsgNum msgNum;
private bool fired;
private this(Proxy proxy, MsgNum n) {
this.proxy = proxy;
this.msgNum = n;
}
static if (is(SUCCESS))
auto onSuccess(SUCCESS dg) scope return {
static assert(is(SUCCESS), ("Reply not allowed for %s", FQN));
CallbackT!FUNC cb;
if (auto p = msgNum in proxy.callbacks) {
cb = cast(CallbackT!FUNC) *p;
assert(cb, "Callback type mismatch");
assert(cb.successDG is null, "Success callback already set");
} else {
cb = new CallbackT!FUNC;
cb.msgNum = msgNum;
proxy.callbacks[cb.msgNum] = cb;
}
cb.successDG = dg;
fired = true; //返回新结构,因此现已失效
return Outbound(proxy, msgNum);
}
static if (is(FAILURE))
auto onFailure(FAILURE dg) scope return {
static assert(is(FAILURE), ("Failure not allowed for %s", FQN));
CallbackT!FUNC cb;
if (auto p = msgNum in proxy.callbacks) {
cb = cast(CallbackT!FUNC) *p;
assert(cb, "Callback type mismatch");
assert(cb.failureDG is null, "Failure callback already set");
} else {
cb = new CallbackT!FUNC;
cb.msgNum = msgNum;
proxy.callbacks[cb.msgNum] = cb;
}
cb.failureDG = dg;
fired = true; //返回新结构,因此现已失效
return Outbound(proxy, msgNum);
}
void go() {
if (fired) return;
fired = true;
if (auto fireDG = msgNum in proxy.pendingCalls) {
proxy.pendingCalls.remove(msgNum);
(*fireDG)();
}
}
}
auto opDispatch(string s, SA...)(SA sargs) {
//writefln("opDispatch:<%s>%s",s,SA.stringof);
alias FUNC = __traits(getMember, dest, s);
alias FN = FUNCNAME!FUNC;
alias FQN = fullyQualifiedName!FUNC;
static if (!hasTemplateUDA!(FUNC, Proxy)) {
pragma(msg, format("Cannot call function %s without @Proxy UDA", FQN)); //在`opDispatch`中难以取编译错误消息
static assert(false);
}
alias PARAMS = Parameters!FUNC;
static if (!canCallFuncWithParameters!(FUNC, SA)) {
pragma(msg, format("Invalid parameters for proxy %s: expected %s, got %s", FQN, PARAMS.stringof, SA.stringof));
static assert(false, "Invalid parameters");
} else {
auto msgNum = nextMsgNum++;
auto outbound = Outbound!FUNC(this, msgNum);
pendingCalls[msgNum] = {
//此闭包直接调用接收方对象.实际上,按发射器/接收器对拆分代理,并跨要重构`网络`序化消息,并在`目标对象`上远程调用接收器.
lastMessageNum = msgNum;
__traits(getMember, dest, s)(sargs);
};
return outbound;
}
}
}
void main() {
auto person = new Person("bob", 34);
auto proxy = new Proxy!Person(person);
proxy.requestName().onSuccess((string str) {
writefln("Client| Received name: %s", str);
});
proxy.doubleEvenNumber(4).onSuccess((int r, int orig) {
writefln("Client| Received doubled number: %s (Original number was: %s)", r, orig);
}).onFailure((string str) {
writefln("Client| Error: %s", str);
});
proxy.doubleEvenNumber(3).onSuccess((int r, int orig) {
writefln("Client| Received doubled number: %s (Original number was: %s)", r, orig);
}).onFailure((string str) {
writefln("Client| Error: %s", str);
});
assert(proxy.callbacks.length == 0, format("Unhandled callbacks: %s", proxy.callbacks));
}
.
My util/extratraits.d for some additional helper templates:
module util.extratraits;
import std.traits;
template FUNCNAME(F...) if (F.length == 1) {
import std.string;
enum FUNCNAME = fullyQualifiedName!(F[0])[ fullyQualifiedName!(F[0]).lastIndexOf('.')+1 .. $ ];
}
bool canCallFuncWithParameters(alias FUNC, SA...)() pure @safe nothrow {
static if (SA.length > Parameters!FUNC.length || SA.length < numRequiredArguments!FUNC) {
return false;
}
static foreach (idx, P; Parameters!FUNC) {
static if (idx < SA.length && !isImplicitlyConvertible!(SA[idx], P)) {
return false;
}
}
return true;
}
size_t numRequiredArguments(alias FUNC)() pure @safe nothrow {
size_t numReq = 0;
static foreach (argi, PD; ParameterDefaults!FUNC) {
static if (is(PD == void))
numReq++;
else
return numReq;
}
return numReq;
}
string shortname()(string str) {
import std.string;
auto excl = str.indexOf('!');
if (excl > 0) str = str[0 .. excl];
return str[str.lastIndexOf('.')+1 .. $];
}
bool hasTemplateUDA(alias SYM, alias UDA)() {
static foreach (idx, attr; __traits(getAttributes, SYM)) {
static if (__traits(isTemplate, TemplateOf!UDA)) {
static if (__traits(isSame, attr, TemplateOf!UDA))
return true;
} else {
static if (__traits(isSame, attr, UDA))
return true;
}
}
return false;
}
template isMemberVariable(alias THIS, alias SYM) if (isAggregateType!THIS) {
enum bool isMemberVariable = !(isFunction!SYM || isType!SYM || __traits(isTemplate, SYM)
//|| hasStaticMember!(THIS, SYM.stringof)
);
}