protected async Task OnReceiveMessageAsync(EntityActorMessage message) { if (message == null) { throw new ArgumentNullException(nameof(message)); } if (!isInitialized) { //Only 1 thread ever will call OnInternalReceiveMessageAsync but it prevents //any external initialization from happening. lock (SyncObj) { if (!isInitialized) { if (ExtractPotentialStateMessage(message, out var initMessage)) { InitializeState(initMessage.State); //Send successful initialization message to the entity, immediately. //Some entities may not care. OnInitialized(new EntityActorInitializationSuccessMessage()); } else { if (Logger.IsWarnEnabled) { Logger.Warn($"{GetType().Name} encountered MessageType: {message.GetType().Name} before INITIALIZATION."); } } //Even if we're initialized now, it's an init message we shouldn't continue with. return; } } } //TODO: Is it safe to capture the Context message ref forever?? //TODO: Pool or cache somehow. EntityActorMessageContext context = new EntityActorMessageContext(Context); try { if (!await HandleMessageAsync(message, context)) { if (Logger.IsWarnEnabled) { Logger.Warn($"EntityActor encountered unhandled MessageType: {message.GetType().Name}"); } } } catch (Exception e) { if (Logger.IsErrorEnabled) { Logger.Error($"Actor: {Self.Path.Address} failed to handle MessageType: {message.GetType().Name} without Exception: {e.Message}\n\nStack: {e.StackTrace}"); } throw; } }
/// <summary> /// Implements must override this and implement domain-specific message handling logic. /// </summary> /// <param name="message">The message to handle.</param> /// <param name="context">The actor message context.</param> /// <returns>True if the message was successfully handled.</returns> protected Task <bool> HandleMessageAsync(EntityActorMessage message, EntityActorMessageContext context) { if (message == null) { throw new ArgumentNullException(nameof(message)); } if (context == null) { throw new ArgumentNullException(nameof(context)); } return(MessageHandlerService.HandleMessageAsync(context, message, CancellationToken.None)); }
/// <inheritdoc /> public async Task <bool> HandleMessageAsync(EntityActorMessageContext context, EntityActorMessage message, CancellationToken token = default) { //We don't lock here even though dictionary is publicly mutable //But we discourage calling it. if (!MessageHandlerMap.ContainsKey(message.GetType())) { return(false); } foreach (var handler in MessageHandlerMap[message.GetType()]) { await handler.HandleMessageAsync(context, message, token); } return(true); }
/// <inheritdoc /> public override async Task HandleMessageAsync(EntityActorMessageContext context, TMessageRequestType message, CancellationToken token = default) { //Concept here is to dispatch to the request handler and get a response to send. TMessageResponseType response = await HandleRequestAsync(context, message, token); //TODO: Validate performance of ordering. //Support returning nothing, but ONLY when it's a reference type. //Value type responses like 0 or Enum0 or string empty should be considered valid message types. if (EqualityComparer <TMessageResponseType> .Default.Equals(response, default) && !typeof(TMessageResponseType).IsValueType) { return; } try { context.Sender.Tell(response, context.Entity); } finally { await OnResponseMessageSendAsync(context, message, response); } }
/// <inheritdoc /> public override Task HandleMessageAsync(EntityActorMessageContext context, InitializeStateMessage <T> message, CancellationToken token = default) { //Just initialize the state container. MutableStateContainer.Data = message.State; return(Task.CompletedTask); }
/// <summary> /// Implementer can override this method as a callback/event for when the <see cref="response"/> has been sent to the session. /// Called after <see cref="HandleMessageAsync"/>. /// /// Implementers should not await directly within this message as it blocks the request pipeline unless they are absolutely sure they want this to happen. /// </summary> /// <param name="context">The message context.</param> /// <param name="request">The original request message.</param> /// <param name="response">The response message sent.</param> /// <returns></returns> protected virtual Task OnResponseMessageSendAsync(EntityActorMessageContext context, TMessageRequestType request, TMessageResponseType response) { return(Task.CompletedTask); }
/// <summary> /// Similar to <see cref="HandleMessageAsync"/> but requires the implementer return an instance of the specified <typeparamref name="TMessageResponseType"/>. /// Which will be sent over the network. /// </summary> /// <param name="context">Message context.</param> /// <param name="message">Incoming message.</param> /// <param name="token">Cancel token.</param> /// <returns></returns> protected abstract Task <TMessageResponseType> HandleRequestAsync(EntityActorMessageContext context, TMessageRequestType message, CancellationToken token = default);