From 5bbeed631ec48a2437fc97f5ff705d5481455cd7 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 16 May 2026 10:35:38 +0100 Subject: [PATCH] Add CultCache interop peer --- .../GameCult.Caching.InteropPeer.csproj | 16 +++ tests/GameCult.Caching.InteropPeer/Program.cs | 134 ++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 tests/GameCult.Caching.InteropPeer/GameCult.Caching.InteropPeer.csproj create mode 100644 tests/GameCult.Caching.InteropPeer/Program.cs diff --git a/tests/GameCult.Caching.InteropPeer/GameCult.Caching.InteropPeer.csproj b/tests/GameCult.Caching.InteropPeer/GameCult.Caching.InteropPeer.csproj new file mode 100644 index 00000000..dc982453 --- /dev/null +++ b/tests/GameCult.Caching.InteropPeer/GameCult.Caching.InteropPeer.csproj @@ -0,0 +1,16 @@ + + + Exe + net10.0 + enable + GameCult.Caching.InteropPeer + + + + + + + + diff --git a/tests/GameCult.Caching.InteropPeer/Program.cs b/tests/GameCult.Caching.InteropPeer/Program.cs new file mode 100644 index 00000000..5de07335 --- /dev/null +++ b/tests/GameCult.Caching.InteropPeer/Program.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text.Json; +using System.Threading.Tasks; +using GameCult.Caching; +using GameCult.Caching.MessagePack; +using MessagePack; + +return await ProgramMainAsync(args); + +static async Task ProgramMainAsync(string[] args) +{ + try + { + if (args.Length == 0) + { + throw new InvalidOperationException("Expected mode: write | read"); + } + + var mode = args[0]; + var options = ParseArgs(args.Skip(1).ToArray()); + var file = RequireArg(options, "file"); + switch (mode) + { + case "write": + await WriteAsync(file, RequireArg(options, "runtime-id")); + return 0; + case "read": + await ReadAsync(file); + return 0; + default: + throw new InvalidOperationException($"Unknown mode {mode}."); + } + } + catch (Exception error) + { + Console.Error.WriteLine(error); + return 1; + } +} + +static async Task WriteAsync(string file, string runtimeId) +{ + var cache = BuildCache(file); + await cache.PullAllBackingStoresAsync(); + var note = new CultCacheInteropNote + { + DocumentId = $"note:{runtimeId}", + AuthorRuntimeId = runtimeId, + Title = $"{runtimeId} wrote a CultCache note", + Body = "The v1 store format is the contract.", + Tags = [runtimeId, "csharp", "interop"] + }; + await cache.AddAsync(note, new CultRecordHandle(new CultRecordKey(note.DocumentId))); + cache.FlushAllBackingStores(); + WriteJsonLine(note); +} + +static async Task ReadAsync(string file) +{ + var cache = BuildCache(file); + await cache.PullAllBackingStoresAsync(); + var note = cache.AllEntries + .OfType() + .FirstOrDefault() + ?? throw new InvalidOperationException("No cultcache.interop-note records found."); + WriteJsonLine(note); +} + +static CultCache BuildCache(string file) +{ + var cache = new CultCache(); + cache.AddBackingStore(new SingleFileMessagePackBackingStore(file)); + return cache; +} + +static Dictionary ParseArgs(string[] args) +{ + var parsed = new Dictionary(StringComparer.OrdinalIgnoreCase); + for (var index = 0; index < args.Length; index += 2) + { + var token = args[index]; + if (!token.StartsWith("--", StringComparison.Ordinal)) + { + continue; + } + + if (index + 1 >= args.Length) + { + throw new InvalidOperationException($"Missing value for {token}."); + } + + parsed[token[2..]] = args[index + 1]; + } + + return parsed; +} + +static string RequireArg(Dictionary options, string name) +{ + if (!options.TryGetValue(name, out var value) || string.IsNullOrWhiteSpace(value)) + { + throw new InvalidOperationException($"Missing required argument --{name}."); + } + + return value; +} + +static void WriteJsonLine(CultCacheInteropNote note) +{ + Console.Out.WriteLine(JsonSerializer.Serialize(new + { + schemaVersion = note.SchemaVersion, + documentId = note.DocumentId, + authorRuntimeId = note.AuthorRuntimeId, + title = note.Title, + body = note.Body, + tags = note.Tags + })); +} + +[CultDocument("cultcache.interop-note", "cultcache.interop_note.v1")] +[MessagePackObject] +public sealed class CultCacheInteropNote +{ + [Key(0)] public string SchemaVersion { get; set; } = "cultcache.interop_note.v1"; + [Key(1)] [CultName] public string DocumentId { get; set; } = string.Empty; + [Key(2)] public string AuthorRuntimeId { get; set; } = string.Empty; + [Key(3)] public string Title { get; set; } = string.Empty; + [Key(4)] public string Body { get; set; } = string.Empty; + [Key(5)] public string[] Tags { get; set; } = Array.Empty(); +}