diff --git a/Libraries/Esiur/Proxy/ResourceGenerator.cs b/Libraries/Esiur/Proxy/ResourceGenerator.cs index c401ed1..6875d46 100644 --- a/Libraries/Esiur/Proxy/ResourceGenerator.cs +++ b/Libraries/Esiur/Proxy/ResourceGenerator.cs @@ -10,6 +10,7 @@ using Esiur.Resource; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Org.BouncyCastle.Asn1.Cms; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -75,30 +76,71 @@ namespace Esiur.Proxy { try { - var code = @$"using Esiur.Resource; -using Esiur.Core; + var code = new StringBuilder(); -#nullable enable + var ns = ci.ClassSymbol.ContainingNamespace.ToDisplayString(); + if (ns == null || ns == "") + { -namespace {ci.ClassSymbol.ContainingNamespace.ToDisplayString()} {{ -"; + } + + code.AppendLine("using Esiur.Resource;"); + code.AppendLine("using Esiur.Core;"); + code.AppendLine("#nullable enable"); + + if (!(ns == null || ns == "")) + { + code.AppendLine($"namespace {ns};"); + } + + code.AppendLine(); + code.AppendLine($"// DO NOT EDIT THIS FILE! "); + code.AppendLine($"// This file was generated by {nameof(ResourceGenerator)} based on the presence of [Resource] and [Export] attributes in {ci.ClassSymbol.Name}.cs"); if (IsInterfaceImplemented(ci, classes)) - code += $"public partial class {ci.Name} {{\r\n"; + { + code.AppendLine($"public partial class {ci.Name}"); + code.AppendLine("{"); + + } else { - code += -$@" public partial class {ci.Name} : IResource {{ - public virtual Instance? Instance {{ get; set; }} - public virtual event DestroyedEvent? OnDestroy; - - public virtual void Destroy() {{ OnDestroy?.Invoke(this); }} -"; + code.AppendLine($"public partial class {ci.Name} : IResource"); + code.AppendLine("{"); + code.AppendLine("public virtual Instance? Instance { get; set; }"); + code.AppendLine("public virtual event DestroyedEvent? OnDestroy;"); + code.AppendLine("public virtual void Destroy() { OnDestroy?.Invoke(this); }"); if (!ci.HasTrigger) - code += "\tpublic virtual AsyncReply Trigger(ResourceTrigger trigger) => new AsyncReply(true);\r\n\r\n"; + { + code.AppendLine("public virtual AsyncReply Trigger(ResourceTrigger trigger) => new AsyncReply(true);"); + } } +// var code = @$"using Esiur.Resource; +//using Esiur.Core; + +//#nullable enable + +//namespace {ci.ClassSymbol.ContainingNamespace.ToDisplayString()} {{ +//"; + +// if (IsInterfaceImplemented(ci, classes)) +// code += $"public partial class {ci.Name} {{\r\n"; +// else +// { +// code += +//$@" public partial class {ci.Name} : IResource {{ +// public virtual Instance? Instance {{ get; set; }} +// public virtual event DestroyedEvent? OnDestroy; + +// public virtual void Destroy() {{ OnDestroy?.Invoke(this); }} +//"; + +// if (!ci.HasTrigger) +// code += "\tpublic virtual AsyncReply Trigger(ResourceTrigger trigger) => new AsyncReply(true);\r\n\r\n"; +// } + foreach (var f in ci.Fields) { var givenName = f.GetAttributes().FirstOrDefault(x => x.AttributeClass?.Name == "ExportAttribute")?.ConstructorArguments.FirstOrDefault().Value as string; @@ -106,21 +148,26 @@ $@" public partial class {ci.Name} : IResource {{ var fn = f.Name; var pn = string.IsNullOrEmpty(givenName) ? SuggestExportName(fn) : givenName; - var attrs = string.Join("\r\n\t", f.GetAttributes().Select(x => FormatAttribute(x))); + var attrs = f.GetAttributes().Select(x => FormatAttribute(x)); + foreach(var attr in attrs) + code.AppendLine($"\t{attr}"); + + if (f.Type.Name.StartsWith("ResourceEventHandler") || f.Type.Name.StartsWith("CustomResourceEventHandler")) { - code += $"\t{attrs}\r\n\t public event {f.Type} {pn};\r\n"; + code.AppendLine($"public event {f.Type} {pn};"); } else { - code += $"\t{attrs}\r\n\t public {f.Type} {pn} {{ \r\n\t\t get => {fn}; \r\n\t\t set {{ \r\n\t\t this.{fn} = value; \r\n\t\t Instance?.Modified(); \r\n\t\t}}\r\n\t}}\r\n"; + code.AppendLine($"\t{attrs}\r\n\t public {f.Type} {pn} {{ \r\n\t\t get => {fn}; \r\n\t\t set {{ \r\n\t\t this.{fn} = value; \r\n\t\t Instance?.Modified(); \r\n\t\t}}\r\n\t}}\r\n"); + } } - code += "}}\r\n"; + code.AppendLine("}\r\n"); - spc.AddSource(ci.Name + ".g.cs", code); + spc.AddSource(ci.Name + ".g.cs", code.ToString()); } catch (Exception ex) { diff --git a/Tests/Distribution/ConcurrentAttach/Client/Esiur.Tests.ConcurrentAttach.Client.csproj b/Tests/Distribution/ConcurrentAttach/Client/Esiur.Tests.ConcurrentAttach.Client.csproj index ed9781c..44e8917 100644 --- a/Tests/Distribution/ConcurrentAttach/Client/Esiur.Tests.ConcurrentAttach.Client.csproj +++ b/Tests/Distribution/ConcurrentAttach/Client/Esiur.Tests.ConcurrentAttach.Client.csproj @@ -7,4 +7,8 @@ enable + + + + diff --git a/Tests/Distribution/ConcurrentAttach/Client/Program.cs b/Tests/Distribution/ConcurrentAttach/Client/Program.cs index 48b699a..5a946a9 100644 --- a/Tests/Distribution/ConcurrentAttach/Client/Program.cs +++ b/Tests/Distribution/ConcurrentAttach/Client/Program.cs @@ -19,7 +19,7 @@ using Esiur.Resource; using Esiur.Stores; -using Esiur.Net.IIP; +using Esiur.Protocol; using System.Diagnostics; var mode = GetArg(args, "--mode", "both"); @@ -29,28 +29,30 @@ var concurrent = int.Parse(GetArg(args, "--concurrent", "50")); var resources = int.Parse(GetArg(args, "--resources", "200")); var timeoutMs = int.Parse(GetArg(args, "--timeout", "10000")); var rounds = int.Parse(GetArg(args, "--rounds", "5")); +var wh = new Warehouse(); // ---------------------------------------------------------------- // SERVER SIDE // ---------------------------------------------------------------- if (mode == "server" || mode == "both") { - await Warehouse.Put("sys", new MemoryStore()); - await Warehouse.Put("sys/server", new DistributedServer() { Port = (ushort)port }); + + await wh.Put("sys", new MemoryStore()); + await wh.Put("sys/server", new EpServer() { Port = (ushort)port }); for (int i = 0; i < resources; i++) { - await Warehouse.Put($"sys/sensor_{i}", new SensorResource { SensorId = i, Value = i }); + await wh.Put($"sys/sensor_{i}", new SensorResource { SensorId = i, Value = i }); } - await Warehouse.Open(); + await wh.Open(); Console.WriteLine($"[Server-T3] Ready: {resources} resources on port {port}"); if (mode == "server") { Console.WriteLine("Press ENTER to stop."); Console.ReadLine(); - await Warehouse.Close(); + await wh.Close(); return; } @@ -88,7 +90,7 @@ for (int round = 0; round < rounds; round++) using var cts = new CancellationTokenSource(timeoutMs); try { - var proxy = await Warehouse.Get( + var proxy = await wh.Get( $"iip://{host}:{port}/sys/sensor_{resourceIdx}"); sw.Stop(); @@ -160,7 +162,7 @@ await File.WriteAllTextAsync("test3_concurrent_attach.csv", csv); Console.WriteLine("\n[Client-T3] Results written to test3_concurrent_attach.csv"); if (mode == "both") - await Warehouse.Close(); + await wh.Close(); // ---------------------------------------------------------------- diff --git a/Tests/Distribution/ConcurrentAttach/Client/SensorResource.cs b/Tests/Distribution/ConcurrentAttach/Client/SensorResource.cs new file mode 100644 index 0000000..8dd3474 --- /dev/null +++ b/Tests/Distribution/ConcurrentAttach/Client/SensorResource.cs @@ -0,0 +1,15 @@ +using Esiur.Resource; + +/// +/// Shared observable sensor resource used across all scalability tests. +/// Property changes via Value setter are automatically propagated +/// to all attached remote peers by the Esiur runtime. +/// +[Resource] +public partial class SensorResource : Resource +{ + public int SensorId { get; set; } + + [Export] + public double value; +} diff --git a/Tests/Distribution/ConcurrentAttach/Server/Esiur.Tests.ConcurrentAttach.Server.csproj b/Tests/Distribution/ConcurrentAttach/Server/Esiur.Tests.ConcurrentAttach.Server.csproj index ed9781c..44e8917 100644 --- a/Tests/Distribution/ConcurrentAttach/Server/Esiur.Tests.ConcurrentAttach.Server.csproj +++ b/Tests/Distribution/ConcurrentAttach/Server/Esiur.Tests.ConcurrentAttach.Server.csproj @@ -7,4 +7,8 @@ enable + + + + diff --git a/Tests/Distribution/ConcurrentAttach/Server/Program.cs b/Tests/Distribution/ConcurrentAttach/Server/Program.cs index 48b699a..ef092d2 100644 --- a/Tests/Distribution/ConcurrentAttach/Server/Program.cs +++ b/Tests/Distribution/ConcurrentAttach/Server/Program.cs @@ -17,9 +17,9 @@ // Usage (client only): dotnet run -- --mode client --host 127.0.0.1 --concurrent 50 --resources 200 // ============================================================ +using Esiur.Protocol; using Esiur.Resource; using Esiur.Stores; -using Esiur.Net.IIP; using System.Diagnostics; var mode = GetArg(args, "--mode", "both"); @@ -30,27 +30,28 @@ var resources = int.Parse(GetArg(args, "--resources", "200")); var timeoutMs = int.Parse(GetArg(args, "--timeout", "10000")); var rounds = int.Parse(GetArg(args, "--rounds", "5")); +var wh = new Warehouse(); // ---------------------------------------------------------------- // SERVER SIDE // ---------------------------------------------------------------- if (mode == "server" || mode == "both") { - await Warehouse.Put("sys", new MemoryStore()); - await Warehouse.Put("sys/server", new DistributedServer() { Port = (ushort)port }); + await wh.Put("sys", new MemoryStore()); + await wh.Put("sys/server", new EpServer() { Port = (ushort)port }); for (int i = 0; i < resources; i++) { - await Warehouse.Put($"sys/sensor_{i}", new SensorResource { SensorId = i, Value = i }); + await wh.Put($"sys/sensor_{i}", new SensorResource { SensorId = i, Value = i }); } - await Warehouse.Open(); + await wh.Open(); Console.WriteLine($"[Server-T3] Ready: {resources} resources on port {port}"); if (mode == "server") { Console.WriteLine("Press ENTER to stop."); Console.ReadLine(); - await Warehouse.Close(); + await wh.Close(); return; } @@ -88,7 +89,7 @@ for (int round = 0; round < rounds; round++) using var cts = new CancellationTokenSource(timeoutMs); try { - var proxy = await Warehouse.Get( + var proxy = await wh.Get( $"iip://{host}:{port}/sys/sensor_{resourceIdx}"); sw.Stop(); @@ -160,7 +161,7 @@ await File.WriteAllTextAsync("test3_concurrent_attach.csv", csv); Console.WriteLine("\n[Client-T3] Results written to test3_concurrent_attach.csv"); if (mode == "both") - await Warehouse.Close(); + await wh.Close(); // ---------------------------------------------------------------- diff --git a/Tests/Distribution/ConcurrentAttach/Server/SensorResource.cs b/Tests/Distribution/ConcurrentAttach/Server/SensorResource.cs new file mode 100644 index 0000000..8dd3474 --- /dev/null +++ b/Tests/Distribution/ConcurrentAttach/Server/SensorResource.cs @@ -0,0 +1,15 @@ +using Esiur.Resource; + +/// +/// Shared observable sensor resource used across all scalability tests. +/// Property changes via Value setter are automatically propagated +/// to all attached remote peers by the Esiur runtime. +/// +[Resource] +public partial class SensorResource : Resource +{ + public int SensorId { get; set; } + + [Export] + public double value; +} diff --git a/Tests/Distribution/NodeFanout/Client/Esiur.Tests.NodeFanout.Client.csproj b/Tests/Distribution/NodeFanout/Client/Esiur.Tests.NodeFanout.Client.csproj index ed9781c..0efb788 100644 --- a/Tests/Distribution/NodeFanout/Client/Esiur.Tests.NodeFanout.Client.csproj +++ b/Tests/Distribution/NodeFanout/Client/Esiur.Tests.NodeFanout.Client.csproj @@ -7,4 +7,8 @@ enable + + + + diff --git a/Tests/Distribution/NodeFanout/Client/Program.cs b/Tests/Distribution/NodeFanout/Client/Program.cs index b774940..0108a4b 100644 --- a/Tests/Distribution/NodeFanout/Client/Program.cs +++ b/Tests/Distribution/NodeFanout/Client/Program.cs @@ -32,11 +32,13 @@ var latencyLock = new object(); var proxies = new dynamic[resourceCount]; var sw = Stopwatch.StartNew(); +var wh = new Warehouse(); + try { for (int i = 0; i < resourceCount; i++) { - proxies[i] = await Warehouse.Get($"iip://{host}:{port}/sys/sensor_{i}"); + proxies[i] = await wh.Get($"iip://{host}:{port}/sys/sensor_{i}"); // Subscribe to property change notifications via the Esiur event model double lastValue = (double)proxies[i].Value; diff --git a/Tests/Distribution/NodeFanout/Server/Esiur.Tests.NodeFanout.Server.csproj b/Tests/Distribution/NodeFanout/Server/Esiur.Tests.NodeFanout.Server.csproj index ed9781c..0efb788 100644 --- a/Tests/Distribution/NodeFanout/Server/Esiur.Tests.NodeFanout.Server.csproj +++ b/Tests/Distribution/NodeFanout/Server/Esiur.Tests.NodeFanout.Server.csproj @@ -7,4 +7,8 @@ enable + + + + diff --git a/Tests/Distribution/NodeFanout/Server/Program.cs b/Tests/Distribution/NodeFanout/Server/Program.cs index 05d0304..ce9fe38 100644 --- a/Tests/Distribution/NodeFanout/Server/Program.cs +++ b/Tests/Distribution/NodeFanout/Server/Program.cs @@ -9,8 +9,8 @@ using Esiur.Resource; using Esiur.Stores; -using Esiur.Net.IIP; -using System.Diagnostics; + using System.Diagnostics; +using Esiur.Protocol; var resourceCount = int.Parse(GetArg(args, "--resources", "100")); var intervalMs = int.Parse(GetArg(args, "--interval", "50")); @@ -18,19 +18,20 @@ var port = int.Parse(GetArg(args, "--port", "10900")); Console.WriteLine($"[Server] resources={resourceCount} interval={intervalMs}ms port={port}"); +var wh = new Warehouse(); // --- Warehouse setup ------------------------------------------------- -await Warehouse.Put("sys", new MemoryStore()); -await Warehouse.Put("sys/server", new DistributedServer() { Port = (ushort)port }); +await wh.Put("sys", new MemoryStore()); +await wh.Put("sys/server", new EpServer() { Port = (ushort)port }); // Create and register all sensor resources var sensors = new SensorResource[resourceCount]; for (int i = 0; i < resourceCount; i++) { sensors[i] = new SensorResource { SensorId = i }; - await Warehouse.Put($"sys/sensor_{i}", sensors[i]); + await wh.Put($"sys/sensor_{i}", sensors[i]); } -await Warehouse.Open(); +await wh.Open(); Console.WriteLine($"[Server] Listening on port {port} with {resourceCount} resources."); // --- Emit loop ------------------------------------------------------- @@ -68,7 +69,7 @@ _ = Task.Run(async () => Console.WriteLine("Press ENTER to stop."); Console.ReadLine(); -await Warehouse.Close(); +await wh.Close(); // --- Helpers --------------------------------------------------------- diff --git a/Tests/Distribution/NodeFanout/Server/SensorResource.cs b/Tests/Distribution/NodeFanout/Server/SensorResource.cs index de25dae..8dd3474 100644 --- a/Tests/Distribution/NodeFanout/Server/SensorResource.cs +++ b/Tests/Distribution/NodeFanout/Server/SensorResource.cs @@ -1,24 +1,15 @@ using Esiur.Resource; /// -/// A simple observable sensor resource. -/// Property changes are automatically propagated to all attached peers. +/// Shared observable sensor resource used across all scalability tests. +/// Property changes via Value setter are automatically propagated +/// to all attached remote peers by the Esiur runtime. /// [Resource] -public class SensorResource : Resource +public partial class SensorResource : Resource { public int SensorId { get; set; } - private double _value; - - [ResourceProperty] - public double Value - { - get => _value; - set - { - _value = value; - PropertyModified("Value"); // notifies Esiur runtime to propagate - } - } + [Export] + public double value; } diff --git a/Tests/Distribution/ResourceCount/Client/Esiur.Tests.ResourceCount.Client.csproj b/Tests/Distribution/ResourceCount/Client/Esiur.Tests.ResourceCount.Client.csproj index ed9781c..634bddd 100644 --- a/Tests/Distribution/ResourceCount/Client/Esiur.Tests.ResourceCount.Client.csproj +++ b/Tests/Distribution/ResourceCount/Client/Esiur.Tests.ResourceCount.Client.csproj @@ -7,4 +7,8 @@ enable + + + + diff --git a/Tests/Distribution/ResourceCount/Client/Program.cs b/Tests/Distribution/ResourceCount/Client/Program.cs index 30c5239..890b6f6 100644 --- a/Tests/Distribution/ResourceCount/Client/Program.cs +++ b/Tests/Distribution/ResourceCount/Client/Program.cs @@ -23,7 +23,7 @@ var proxies = new dynamic[resourceCount]; // --- Attach in batches to avoid overwhelming the runtime ------------- var totalSw = Stopwatch.StartNew(); - +var wh = new Warehouse(); for (int batch = 0; batch < resourceCount; batch += batchSize) { int end = Math.Min(batch + batchSize, resourceCount); @@ -35,7 +35,7 @@ for (int batch = 0; batch < resourceCount; batch += batchSize) batchTasks[i - batch] = Task.Run(async () => { var sw = Stopwatch.StartNew(); - proxies[capturedI] = await Warehouse.Get( + proxies[capturedI] = await wh.Get( $"iip://{host}:{port}/sys/sensor_{capturedI}"); sw.Stop(); diff --git a/Tests/Distribution/ResourceCount/Server/Esiur.Tests.ResourceCount.Server.csproj b/Tests/Distribution/ResourceCount/Server/Esiur.Tests.ResourceCount.Server.csproj index ed9781c..0efb788 100644 --- a/Tests/Distribution/ResourceCount/Server/Esiur.Tests.ResourceCount.Server.csproj +++ b/Tests/Distribution/ResourceCount/Server/Esiur.Tests.ResourceCount.Server.csproj @@ -7,4 +7,8 @@ enable + + + + diff --git a/Tests/Distribution/ResourceCount/Server/Program.cs b/Tests/Distribution/ResourceCount/Server/Program.cs index 08d2cba..32f1860 100644 --- a/Tests/Distribution/ResourceCount/Server/Program.cs +++ b/Tests/Distribution/ResourceCount/Server/Program.cs @@ -8,25 +8,27 @@ using Esiur.Resource; using Esiur.Stores; -using Esiur.Net.IIP; +using Esiur.Protocol; var resourceCount = int.Parse(GetArg(args, "--resources", "10000")); var port = int.Parse(GetArg(args, "--port", "10901")); Console.WriteLine($"[Server-T2] Creating {resourceCount} resources on port {port}"); -await Warehouse.Put("sys", new MemoryStore()); -await Warehouse.Put("sys/server", new DistributedServer() { Port = (ushort)port }); +var wh = new Warehouse(); + +await wh.Put("sys", new MemoryStore()); +await wh.Put("sys/server", new EpServer() { Port = (ushort)port }); long memBefore = GC.GetTotalMemory(forceFullCollection: true); for (int i = 0; i < resourceCount; i++) { var s = new SensorResource { SensorId = i, Value = i * 0.1 }; - await Warehouse.Put($"sys/sensor_{i}", s); + await wh.Put($"sys/sensor_{i}", s); } -await Warehouse.Open(); +await wh.Open(); long memAfter = GC.GetTotalMemory(forceFullCollection: true); double memMB = (memAfter - memBefore) / (1024.0 * 1024.0); @@ -35,7 +37,7 @@ Console.WriteLine($"[Server-T2] Ready. Resources={resourceCount} MemoryUsed={me Console.WriteLine($"[Server-T2] Per-resource ≈ {(memAfter - memBefore) / (double)resourceCount:F0} bytes"); Console.WriteLine("Press ENTER to stop."); Console.ReadLine(); -await Warehouse.Close(); +await wh.Close(); static string GetArg(string[] args, string key, string def) diff --git a/Tests/Distribution/ResourceCount/Server/SensorResource.cs b/Tests/Distribution/ResourceCount/Server/SensorResource.cs new file mode 100644 index 0000000..8dd3474 --- /dev/null +++ b/Tests/Distribution/ResourceCount/Server/SensorResource.cs @@ -0,0 +1,15 @@ +using Esiur.Resource; + +/// +/// Shared observable sensor resource used across all scalability tests. +/// Property changes via Value setter are automatically propagated +/// to all attached remote peers by the Esiur runtime. +/// +[Resource] +public partial class SensorResource : Resource +{ + public int SensorId { get; set; } + + [Export] + public double value; +}