egametang/ET · error · Exception

actor id is 0: {iRequest}

Error message

actor id is 0: {iRequest}

What it means

ProcessOuterSender.Call (the RPC variant) rejects a default/zero ActorId before setting up the rpcId callback and sending. Same precondition as the Send path but on the request/response flow — a zero id means there is no remote process to receive the RPC.

Source

Thrown at Packages/cn.etetet.netinner/Scripts/Hotfix/Server/ProcessOuterSenderSystem.cs:220

            // 如果发向同一个进程,则报错
            if (actorId.Address == self.GetSingleton<AddressSingleton>().InnerAddress)
            {
                throw new Exception($"actor is the same process: {actorId}");
            }
            Session session = self.Get(actorId.Address);
            session.Send(actorId.FiberInstanceId, message);
        }

        private static int GetRpcId(this ProcessOuterSender self)
        {
            return ++self.RpcId;
        }

        public static async ETTask<IResponse> Call(this ProcessOuterSender self, ActorId actorId, IRequest iRequest, bool needException = true)
        {
            if (actorId == default)
            {
                throw new Exception($"actor id is 0: {iRequest}");
            }
            Fiber fiber = self.Fiber();
            
            int rpcId = self.GetRpcId();

            iRequest.RpcId = rpcId;

            Type requestType = iRequest.GetType();
            MessageSenderStruct messageSenderStruct = new(actorId, requestType, needException);
            self.requestCallback.Add(rpcId, messageSenderStruct);
            self.SendInner(actorId, iRequest as MessageObject);
            EntityRef<ProcessOuterSender> selfRef = self;

            async ETTask Timeout()
            {
                await fiber.Root.TimerComponent.WaitAsync(ProcessOuterSender.TIMEOUT_TIME);
                self = selfRef;
                if (!self.requestCallback.Remove(rpcId, out MessageSenderStruct action))

View on GitHub (pinned to 5cab01f7a8)

Solutions

  1. Resolve and validate the target ActorId (non-zero, address registered) before calling Call.
  2. Ensure the destination process is in the start config and its outer address is known.
  3. Guard with a default check and degrade gracefully (return a failure response) instead of throwing.

Example fix

// before
var resp = await outerSender.Call(actorId, request);

// after
if (actorId == default)
{
    return new SomeResponse { Error = ErrorCode.ERR_ActorIdInvalid };
}
var resp = await outerSender.Call(actorId, request);
Defensive patterns

Strategy: validation

Validate before calling

if (actorId == default) { return new SomeResponse { Error = ErrorCode.ERR_ActorIdInvalid }; }

Prevention

When it happens

Trigger: Calling ProcessOuterSender.Call(actorId, iRequest) where actorId is default; the address look-up for the target process returned nothing.

Common situations: The target process is not configured or not yet started; a player/entity ActorId was not initialized before an RPC; single-process dev mode calling outer RPC with no remote process configured.

Related errors


AI-assisted analysis of egametang/ET@5cab01f7a8 (2026-08-13). Data as JSON: /api/errors/829ef57d57e1953a. Report an issue: GitHub.