ET framework---ActorProxyComponent study notes (ActorProxy)

ActorProxyComponent

Please follow me on Weibo: @NormanLin_BadPixel Bad Pixel


We see that this component subscribes to the Start event. And it is an asynchronous startup method. The function is as the author commented. The expired actorproxy is scanned every 10s for recycling, and the expiration time is 1 minute . The code is also easy to understand, so I won't explain it here. But from here we can probably guess that this is actually a component that manages all ActorProxy . The following Get and Remove methods will not be discussed. It should be noted that when the Get method queries an ActorProxy that does not specify an Id in the dictionary , it will create a new storage dictionary according to the Id.

ActorProxy

Before looking at more code, we need to understand what the new thing ActorTask is,

ActorTask

We saw the familiar MessageObject , which we mentioned a little bit in the ClientFrameComponent study notes . In fact, it is a base class, depending on its derived class.

ActorRequest and ActorResponse are custom message types. But we still don't know what this Actor is used for, just treat it as a communication message type. We have to look at the specific message of the MessageObject to know what it does.

ActorResponse response = (ActorResponse)await this.proxy.RealCall(request, this.proxy.CancellationTokenSource.Token);

Send requests asynchronously via ActorProxy and wait for replies. We can go back to ActorProxy .

ActorProxy

The author's comments on several properties are detailed. Let me mention again here that RunningTasks and WaitingTasks are definitely important, and they jointly manage the sending of Actor messages. And LastSendTime determines the moment when the ActorProxy is automatically released.

Let's take a look at the Start method first.

public async void Start()
{
    int appId = await Game.Scene.GetComponent<LocationProxyComponent>().Get(this.Id);
    this.Address = Game.Scene.GetComponent<StartConfigComponent>().Get(appId).GetComponent<InnerConfig>().IPEndPoint;

    this.UpdateAsync();
}

There are too many new guys here, let's split P. LocationProxyComponent study notes

We know that when registering new data in the address server, the Id of the entity object is passed in. But ActorProxy is a Component, which is obtained here through this.Id. Don't worry, let's take a look first, how we all add ActorProxy components.

All ActorProxy are obtained through the Get method of ActorProxyComponent .

public ActorProxy Get(long id)
{
    if (this.ActorProxys.TryGetValue(id, out ActorProxy actorProxy))
    {
        return actorProxy;
    }

    actorProxy = ComponentFactory.CreateWithId<ActorProxy>(id);
    this.ActorProxys[id] = actorProxy;
    return actorProxy;
}

And this method specifies the ID, so we only need to pass in the ID of the entity when we create it.

private async void UpdateAsync()
{
    while (true)
    {
        ActorTask actorTask = await this.GetAsync();
        if (this.IsDisposed)
        {
            return;
        }
        try
        {
            this.RunTask(actorTask);
        }
        catch (Exception e)
        {
            Log.Error(e.ToString());
            return;
        }
    }
}

We see that after creating an ActorProxy , it will start asynchronously processing all ActorTask tasks for that ActorProxy .

The first is to asynchronously obtain the task of the ActorProxy , because it is possible that the ActorProxy is naturally recycled (no new messages are received within 10s), and an empty ActorProxy will be returned in Dispose to end the asynchronous operation. Therefore, at this time, it will be judged that the object has been released, and it will return directly without any operation.

If there is a task, the task is processed by RunTask .

When we want to add a task to the Actor agent, we will first store the task in the queue waiting to be processed. Attempting to get the task from the waiting queue and process it, if the previous task failed and is retrying, then do not process it.

Under what circumstances can we not get tasks from the waiting queue?
1. There is no need to acquire. this.tcs == null , this.tcs will be instantiated in GetAsync .
2. There is no data in the waiting queue.
3. The number of tasks being executed is greater than or equal to the maximum number of parallel executions. (here is 1)

Otherwise, we can get the task from the waiting queue and push it into the RunningTasks queue. Reset this.tcs and return the obtained ActorTask to complete the asynchronous wait in UpdateAsync .

ActorTask actorTask = await this.GetAsync();

After that, we have to deal with this task.

We see that the ActorTask 's Run method will be run first. In this method, the ActorRequest will be composed of the Id of the ActorProxy that sent the request and the message . The method of sending is inside ActorProxy.RealCall . Take a quick look at this method

public async Task<IResponse> RealCall(ActorRequest request, CancellationToken cancellationToken)
{
    try
    {
        //Log.Debug($"realcall {MongoHelper.ToJson(request)} {this.Address}");
        request.Id = this.Id;
        Session session = Game.Scene.GetComponent<NetInnerComponent>().Get(this.Address);
        IResponse response = await session.Call(request, cancellationToken);
        return response;
    }
    catch (RpcException e)
    {
        Log.Error($"{this.Address} {e}");
        throw;
    }
}

We see that the session with the server where the ActorProxy is located is obtained through the NetInnerComponent component , and the request is sent. Returns a result regardless of success or failure.

If the request is successful, update the time and some data of the ActorProxy's last call.

// 发送成功
this.LastSendTime = TimeHelper.Now();
this.failTimes = 0;
if (this.WindowSize < MaxWindowSize)
{
    ++this.WindowSize;
}

this.RunningTasks.Dequeue();
this.AllowGet();

And will try to get the task processing again. If the waiting queue is still there and the conditions that can be processed are met, the above logic will be performed again.

If the request fails , try to resend the request.

this.CancellationTokenSource.Cancel();

The need to resend the request reflects the role of this.CancellationTokenSource . I don't know if you still remember, when we were learning Session . Every time we call an Rpc request, the RPCId is unique (incremented). We resend the request, it is already a new request, and the RPCId of the two is different.

And we will register a callback method after receiving the result according to the RPCId. This method is registered when it is sent, but we don't want it to call this callback if our request fails. Therefore, when we call Seesion.Call , we pass in a CancellationTokenSource and register the callback for task cancellation. That is, remove the callback method we registered before after receiving the result.

Seesion.Call
public Task<IResponse> Call(IRequest request, CancellationToken cancellationToken)
{
    uint rpcId = ++RpcId;
    ...
    this.requestCallback[rpcId] = (packetInfo) =>{...}
    ...
    cancellationToken.Register(()=>this.requestCallback.Remove(rpcId));
    ...
}

After that, I will make detailed comments on the author's code.

IResponse response = await task.Run();
// 如果没找到Actor,发送窗口减少为1,重试
if (response.Error == ErrorCode.ERR_NotFoundActor)
{
    //移除之前注册的收到回复的回调。
    this.CancellationTokenSource.Cancel();
    //发送窗口减少为1
    this.WindowSize = 1;
    //标记失败次数
    ++this.failTimes;
    //讲等待队列里的任务全部压入RunningTasks队列,为后面做准备。
    while (this.WaitingTasks.Count > 0)
    {
        ActorTask actorTask = this.WaitingTasks.Dequeue();
        this.RunningTasks.Enqueue(actorTask);
    }
    //这是一个交换数据的方法,因为我们的等待队列和执行队列的数据结构是Quene
    //先进先出,所以这样对调不会对顺序造成改变。
    ObjectHelper.Swap(ref this.RunningTasks, ref this.WaitingTasks);

    // 失败3次则清空actor发送队列,返回失败
    if (this.failTimes > 3)
    {
        while (this.WaitingTasks.Count > 0)
        {
            ActorTask actorTask = this.WaitingTasks.Dequeue();
            actorTask.RunFail(response.Error);
        }

        // 失败直接删除actorproxy
        Game.Scene.GetComponent<ActorProxyComponent>().Remove(this.Id);
        return;
    }
    // 等待一会再发送
    await Game.Scene.GetComponent<TimerComponent>().WaitAsync(this.failTimes * 500);
    //重新获取定位信息
    int appId = await Game.Scene.GetComponent<LocationProxyComponent>().Get(this.Id);
    this.Address = Game.Scene.GetComponent<StartConfigComponent>().Get(appId).GetComponent<InnerConfig>().IPEndPoint;
    this.CancellationTokenSource = new CancellationTokenSource();
    //重新发送
    this.AllowGet();
    return;
}   

In addition to this, we see that there are two more methods

public void Send(IMessage message)
{
    ActorTask task = new ActorTask();
    task.message = (MessageObject)message;
    task.proxy = this;
    this.Add(task);
}

public Task<IResponse> Call(IRequest request)
{
    ActorTask task = new ActorTask();
    task.message = (MessageObject)request;
    task.proxy = this;
    task.Tcs = new TaskCompletionSource<IResponse>();
    this.Add(task);
    return task.Tcs.Task;
}

These two methods do not send messages or requests immediately, but store these actions in the waiting queue in the form of to-do tasks.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325818608&siteId=291194637