CodeJunkie.Repositories 1.0.0

dotnet add package CodeJunkie.Repositories --version 1.0.0
                    
NuGet\Install-Package CodeJunkie.Repositories -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CodeJunkie.Repositories" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CodeJunkie.Repositories" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="CodeJunkie.Repositories" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CodeJunkie.Repositories --version 1.0.0
                    
#r "nuget: CodeJunkie.Repositories, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CodeJunkie.Repositories@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CodeJunkie.Repositories&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=CodeJunkie.Repositories&version=1.0.0
                    
Install as a Cake Tool

CodeJunkie Messaging & Repository System

A high-performance messaging and repository system for Unity and .NET applications, providing publish-subscribe patterns, property change notifications, and thread-safe operations.

🎯 Features

  • Thread-Safe Messaging - High-performance concurrent messaging with weak reference management
  • Channel-Based Communication - Isolated message delivery through named channels
  • Repository Pattern - Base classes for data management with built-in messaging
  • Property Change Broadcasting - Automatic message broadcasting for property changes
  • Synchronization Context Support - UI thread-safe message delivery
  • Memory Efficient - Weak references prevent memory leaks from unsubscribed handlers

📦 Core Components

IMessenger Interface

Central messaging contract supporting both typed and untyped message delivery:

public interface IMessenger 
{
    ISubscription<T> Subscribe<T>(Action<T> action);
    ISubscription<T> Subscribe<T>(string channel, Action<T> action);
    void Publish<T>(T message);
    void Publish<T>(string channel, T message);
}

Messenger Class

High-performance implementation using concurrent dictionaries and weak references:

var messenger = new Messenger();

// Subscribe to messages
var subscription = messenger.Subscribe<PlayerDiedMessage>(msg => {
    Console.WriteLine($"Player {msg.PlayerId} died!");
});

// Publish messages
messenger.Publish(new PlayerDiedMessage { PlayerId = 123 });

// Clean up
subscription.Dispose();

RepositoryBase Class

Base class for repositories with built-in messaging and property change notifications:

public class PlayerRepository : RepositoryBase
{
    private int _health;
    private string _name;
    
    public int Health 
    {
        get => _health;
        set => Set(ref _health, value, nameof(Health), broadcast: true);
    }
    
    public string Name 
    {
        get => _name;
        set => Set(ref _name, value, nameof(Name), broadcast: true);
    }
}

🎮 Unity Game Development Examples

Player Health System

public class PlayerHealthSystem : MonoBehaviour
{
    private PlayerRepository _playerRepo;
    private IMessenger _messenger;
    private ISubscription<PropertyChangedMessage<int>> _healthSubscription;
    
    void Start()
    {
        _messenger = new Messenger();
        _playerRepo = new PlayerRepository(_messenger);
        
        // Subscribe to health changes
        _healthSubscription = _messenger.Subscribe<PropertyChangedMessage<int>>(msg => {
            if (msg.PropertyName == nameof(PlayerRepository.Health))
            {
                UpdateHealthUI(msg.NewValue);
                
                if (msg.NewValue <= 0)
                {
                    _messenger.Publish(new PlayerDiedMessage(_playerRepo));
                }
            }
        });
        
        // Subscribe to death events
        _messenger.Subscribe<PlayerDiedMessage>(msg => {
            ShowGameOverScreen();
        });
    }
    
    void OnDestroy()
    {
        _healthSubscription?.Dispose();
        _playerRepo?.Dispose();
    }
    
    void UpdateHealthUI(int health)
    {
        var healthBar = FindObjectOfType<Slider>();
        healthBar.value = health / 100f;
    }
}

public class PlayerDiedMessage : MessageBase
{
    public PlayerDiedMessage(object sender) : base(sender) { }
}

Multi-System Communication

public class GameManager : MonoBehaviour
{
    private IMessenger _messenger;
    private PlayerRepository _playerRepo;
    private InventoryRepository _inventoryRepo;
    private ScoreRepository _scoreRepo;
    
    void Start()
    {
        _messenger = new Messenger();
        
        // Create repositories with shared messenger
        _playerRepo = new PlayerRepository(_messenger);
        _inventoryRepo = new InventoryRepository(_messenger);
        _scoreRepo = new ScoreRepository(_messenger);
        
        // Set up cross-system communication
        SetupEnemyDefeatedHandling();
        SetupItemCollectionHandling();
        SetupLevelUpHandling();
    }
    
    void SetupEnemyDefeatedHandling()
    {
        _messenger.Subscribe<EnemyDefeatedMessage>(msg => {
            _scoreRepo.AddScore(msg.ScoreValue);
            _playerRepo.AddExperience(msg.ExpValue);
        });
    }
    
    void SetupItemCollectionHandling()
    {
        _messenger.Subscribe<ItemCollectedMessage>(msg => {
            _inventoryRepo.AddItem(msg.Item);
            
            if (msg.Item.Type == ItemType.HealthPotion)
            {
                _playerRepo.Heal(msg.Item.Value);
            }
        });
    }
    
    void SetupLevelUpHandling()
    {
        _messenger.Subscribe<PropertyChangedMessage<int>>(msg => {
            if (msg.PropertyName == nameof(PlayerRepository.Level))
            {
                _messenger.Publish(new LevelUpMessage(msg.NewValue));
            }
        });
    }
}

Channel-Based Communication

public class UIManager : MonoBehaviour
{
    private IMessenger _messenger;
    
    void Start()
    {
        _messenger = new Messenger();
        
        // Subscribe to UI-specific messages
        _messenger.Subscribe<ShowDialogMessage>("UI", msg => {
            ShowDialog(msg.Title, msg.Content);
        });
        
        _messenger.Subscribe<UpdateHUDMessage>("UI", msg => {
            UpdateHUD(msg.Data);
        });
        
        // Subscribe to game events on different channel
        _messenger.Subscribe<GameStateChangedMessage>("Game", msg => {
            UpdateGameStateUI(msg.NewState);
        });
    }
    
    public void ShowDialog(string title, string content)
    {
        // Show dialog logic
        var dialog = Instantiate(dialogPrefab);
        dialog.Setup(title, content);
    }
}

// Publish to specific channels
_messenger.Publish("UI", new ShowDialogMessage("Warning", "Low Health!"));
_messenger.Publish("Game", new GameStateChangedMessage(GameState.Paused));

Thread-Safe UI Updates

public class NetworkManager : MonoBehaviour
{
    private IMessenger _messenger;
    private SynchronizationContext _mainThreadContext;
    
    void Start()
    {
        _messenger = new Messenger();
        _mainThreadContext = SynchronizationContext.Current;
        
        // Subscribe with UI thread context
        _messenger.Subscribe<NetworkDataMessage>(msg => {
            UpdateUI(msg.Data); // This will run on main thread
        }).ObserveOn(_mainThreadContext);
        
        // Start background network operations
        Task.Run(async () => await ProcessNetworkData());
    }
    
    async Task ProcessNetworkData()
    {
        while (true)
        {
            var data = await ReceiveNetworkData();
            
            // Publish from background thread - will be marshaled to main thread
            _messenger.Publish(new NetworkDataMessage(data));
            
            await Task.Delay(100);
        }
    }
}

🔧 Advanced Usage

Custom Repository with Complex Logic

public class InventoryRepository : RepositoryBase
{
    private ObservableList<Item> _items = new ObservableList<Item>();
    private int _maxCapacity = 50;
    
    public ObservableList<Item> Items => _items;
    
    public int MaxCapacity 
    {
        get => _maxCapacity;
        set => Set(ref _maxCapacity, value, nameof(MaxCapacity), broadcast: true);
    }
    
    public bool IsFull => _items.Count >= _maxCapacity;
    
    public bool TryAddItem(Item item)
    {
        if (IsFull)
        {
            Messenger?.Publish(new InventoryFullMessage(this));
            return false;
        }
        
        _items.Add(item);
        Messenger?.Publish(new ItemAddedMessage(this, item));
        return true;
    }
    
    public bool TryRemoveItem(Item item)
    {
        if (_items.Remove(item))
        {
            Messenger?.Publish(new ItemRemovedMessage(this, item));
            return true;
        }
        return false;
    }
    
    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _items?.Clear();
        }
        base.Dispose(disposing);
    }
}

Message Inheritance and Polymorphism

public abstract class GameEventMessage : MessageBase
{
    public DateTime Timestamp { get; }
    
    protected GameEventMessage(object sender) : base(sender)
    {
        Timestamp = DateTime.UtcNow;
    }
}

public class PlayerActionMessage : GameEventMessage
{
    public string Action { get; }
    
    public PlayerActionMessage(object sender, string action) : base(sender)
    {
        Action = action;
    }
}

public class CombatMessage : GameEventMessage
{
    public int Damage { get; }
    public string Target { get; }
    
    public CombatMessage(object sender, int damage, string target) : base(sender)
    {
        Damage = damage;
        Target = target;
    }
}

// Subscribe to base class to receive all derived messages
_messenger.Subscribe<GameEventMessage>(msg => {
    LogGameEvent(msg.Timestamp, msg.GetType().Name);
});

Performance Monitoring

public class PerformanceMonitor
{
    private readonly IMessenger _messenger;
    private readonly Dictionary<Type, int> _messageStats = new Dictionary<Type, int>();
    
    public PerformanceMonitor(IMessenger messenger)
    {
        _messenger = messenger;
        
        // Monitor all messages using base type
        _messenger.Subscribe<object>(msg => {
            var type = msg.GetType();
            _messageStats[type] = _messageStats.GetValueOrDefault(type) + 1;
        });
    }
    
    public void LogStatistics()
    {
        foreach (var kvp in _messageStats)
        {
            Debug.Log($"Message Type: {kvp.Key.Name}, Count: {kvp.Value}");
        }
    }
}

🛠️ Installation

Unity Package Manager

Add the following to your manifest.json:

{
  "dependencies": {
    "com.codejunkie.messaging": "https://github.com/yourrepo/CodeJunkie.Messaging.git"
  }
}

Manual Installation

  1. Download the source files
  2. Copy to your Unity project's Assets/Scripts/CodeJunkie/ folder
  3. Add the namespace: using CodeJunkie.Observables; and using CodeJunkie.Repositories;

📋 Platform Support

  • Unity 2019.4+ (All platforms including WebGL)
  • .NET Framework 4.6+
  • .NET Core 2.0+
  • .NET 5/6/7/8

🔄 Thread Safety

All messaging operations are thread-safe using concurrent collections:

// Safe to call from any thread
Parallel.For(0, 1000, i => {
    messenger.Publish(new TestMessage(i));
});

// Safe concurrent subscriptions
Parallel.For(0, 100, i => {
    messenger.Subscribe<TestMessage>(msg => ProcessMessage(msg));
});

⚡ Performance Characteristics

  • O(1) message publishing for direct type matches
  • O(n) for inheritance-based message matching (where n = number of subscribed types)
  • Memory Efficient - Weak references automatically clean up dead subscriptions
  • Lock-Free Reads - Message publishing uses lock-free concurrent data structures
  • Minimal Allocations - Reuses internal structures where possible

🏗️ Architecture Patterns

Repository Pattern Implementation

public interface IPlayerRepository : IRepository
{
    int Health { get; set; }
    string Name { get; set; }
    void Heal(int amount);
    void TakeDamage(int damage);
}

public class PlayerRepository : RepositoryBase, IPlayerRepository
{
    // Implementation with automatic messaging
}

Event Sourcing Support

public class EventSourcedRepository : RepositoryBase
{
    private readonly List<GameEventMessage> _events = new List<GameEventMessage>();
    
    protected void ApplyEvent(GameEventMessage evt)
    {
        _events.Add(evt);
        Messenger?.Publish(evt);
    }
    
    public IReadOnlyList<GameEventMessage> GetEvents() => _events.AsReadOnly();
}

🤝 Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

  • Inspired by ReactiveX patterns and modern messaging frameworks
  • Optimized for Unity game development workflows
  • Performance improvements based on community feedback
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 174 6/7/2025