Skip to content

Listening Patterns

Targeted across all targets

  • Accept every targeted message of a given type regardless of who it’s for.
C#
using DxMessaging.Core;   // InstanceId
using DxMessaging.Core.Messages;

// Observe all Heal messages and their intended targets
_ = token.RegisterTargetedWithoutTargeting<Heal>(OnAnyHeal);
void OnAnyHeal(InstanceId target, Heal m) => Audit(target, m);

// Post‑process all targeted of type
_ = token.RegisterTargetedWithoutTargetingPostProcessor<Heal>(OnAnyHealPost);
void OnAnyHealPost(InstanceId target, Heal m) => Log(target, m);

Broadcast across all sources

  • Accept every broadcast message of a given type regardless of who emitted it.
C#
using DxMessaging.Core;   // InstanceId
using DxMessaging.Core.Messages;

// Observe all TookDamage messages and their sources
_ = token.RegisterBroadcastWithoutSource<TookDamage>(OnAnyTookDamage);
void OnAnyTookDamage(InstanceId source, TookDamage m) => Track(source, m);

// Post‑process all broadcast of type
_ = token.RegisterBroadcastWithoutSourcePostProcessor<TookDamage>(OnAnyTookDamagePost);
void OnAnyTookDamagePost(InstanceId source, TookDamage m) => Log(source, m);

Global accept‑all (debug/inspection)

  • Receive every message of every type on a handler; useful for tooling.
C#
using DxMessaging.Core;

var bus = MessageHandler.MessageBus;
var handler = new MessageHandler(new InstanceId(1)) { active = true };
var dereg = bus.RegisterGlobalAcceptAll(handler);
// implement handler callbacks for generic categories on your MessageHandler

Real‑World Use Cases

Development Debug Dump

Capture all messages during development for debugging and diagnostics:

C#
using DxMessaging.Core;
using DxMessaging.Core.Messages;
using UnityEngine;

public class DebugMessageLogger : MessageHandler
{
    public DebugMessageLogger() : base(new InstanceId(999)) { }

    public override void Handle(ref IUntargetedMessage message)
    {
        Debug.Log($"[Untargeted] {message.GetType().Name}: {message}");
    }

    public override void Handle(ref InstanceId target, ref ITargetedMessage message)
    {
        Debug.Log($"[Targeted → {target}] {message.GetType().Name}: {message}");
    }

    public override void Handle(ref InstanceId source, ref IBroadcastMessage message)
    {
        Debug.Log($"[Broadcast ← {source}] {message.GetType().Name}: {message}");
    }
}

// Register in development builds only
#if DEVELOPMENT_BUILD || UNITY_EDITOR
var logger = new DebugMessageLogger { active = true };
_ = MessageHandler.MessageBus.RegisterGlobalAcceptAll(logger);
#endif

Attribute‑Based Network Replication

Automatically replicate messages marked with custom attributes across the network:

C#
using System;
using System.Reflection;
using System.Collections.Generic;
using DxMessaging.Core;
using DxMessaging.Core.Messages;

// Mark messages that should be replicated
[AttributeUsage(AttributeTargets.Struct)]
public class NetworkedAttribute : Attribute { }

[Networked]
[DxUntargetedMessage]
[DxAutoConstructor]
public readonly partial struct PlayerMoved
{
    public readonly Vector3 position;
}

[Networked]
[DxTargetedMessage]
[DxAutoConstructor]
public readonly partial struct DealDamage
{
    public readonly float amount;
}

// Network replication handler
public class NetworkReplicator : MessageHandler
{
    private readonly INetworkManager _network;
    private readonly HashSet<Type> _networkedTypes = new();

    public NetworkReplicator(INetworkManager network) : base(new InstanceId(1000))
    {
        _network = network;
        CacheNetworkedTypes();
    }

    private void CacheNetworkedTypes()
    {
        // Find all message types with [Networked] attribute
        foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
        {
            foreach (var type in assembly.GetTypes())
            {
                if (type.GetCustomAttribute<NetworkedAttribute>() != null)
                {
                    _networkedTypes.Add(type);
                }
            }
        }
    }

    public override void Handle(ref IUntargetedMessage message)
    {
        if (_networkedTypes.Contains(message.GetType()))
        {
            _network.Send(message);  // Serialize and send
        }
    }

    public override void Handle(ref InstanceId target, ref ITargetedMessage message)
    {
        if (_networkedTypes.Contains(message.GetType()))
        {
            _network.Send(target, message);
        }
    }

    public override void Handle(ref InstanceId source, ref IBroadcastMessage message)
    {
        if (_networkedTypes.Contains(message.GetType()))
        {
            _network.Send(source, message);
        }
    }
}

// Usage: any message with [Networked] automatically replicates
var replicator = new NetworkReplicator(networkManager) { active = true };
_ = MessageHandler.MessageBus.RegisterGlobalAcceptAll(replicator);

// These messages are now replicated across the network
var playerMoved = new PlayerMoved(playerPos);
playerMoved.Emit();
var dealDamage = new DealDamage(50f);
dealDamage.EmitTargeted(enemyId);

Message Analytics and Metrics

Track message frequency and performance across your entire game:

C#
using System;
using System.Collections.Generic;
using System.Diagnostics;
using DxMessaging.Core;
using DxMessaging.Core.Messages;

public class MessageAnalytics : MessageHandler
{
    private readonly Dictionary<Type, (int count, long totalMs)> _stats = new();
    private readonly Stopwatch _stopwatch = new();

    public MessageAnalytics() : base(new InstanceId(1001)) { }

    public override void Handle(ref IUntargetedMessage message)
    {
        TrackMessage(message.GetType());
    }

    public override void Handle(ref InstanceId target, ref ITargetedMessage message)
    {
        TrackMessage(message.GetType());
    }

    public override void Handle(ref InstanceId source, ref IBroadcastMessage message)
    {
        TrackMessage(message.GetType());
    }

    private void TrackMessage(Type messageType)
    {
        _stopwatch.Restart();
        // Message processing happens here
        _stopwatch.Stop();

        if (!_stats.TryGetValue(messageType, out var stat))
        {
            stat = (0, 0);
        }
        _stats[messageType] = (stat.count + 1, stat.totalMs + _stopwatch.ElapsedMilliseconds);
    }

    public void PrintStats()
    {
        foreach (var kvp in _stats)
        {
            var avg = kvp.Value.totalMs / (double)kvp.Value.count;
            UnityEngine.Debug.Log($"{kvp.Key.Name}: {kvp.Value.count} messages, avg {avg:F2}ms");
        }
    }
}

When to Use Global Accept‑All

Good use cases:

  • Development‑time debugging and logging
  • Cross‑cutting concerns (analytics, telemetry, metrics)
  • Attribute‑based systems (networking, serialization, persistence)
  • Testing and diagnostics tools
  • Message replay/recording systems

⚠️ Performance consideration: Global Accept-All handlers are invoked for every message of every type. For performance-sensitive gameplay logic, prefer type-specific registrations which use O(1) lookup instead of O(N) iteration.

Avoid for:

  • Core gameplay logic that only needs specific message types
  • Hot paths with thousands of messages per frame
  • Production code that can use specific type registrations instead

Tips

  • Use across‑all listeners for diagnostics, analytics, or cross‑cutting observers.
  • Prefer specific (target/source) registrations for gameplay logic.

Related