diff --git a/Directory.Build.props b/Directory.Build.props index 45263613..114bea2a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -14,7 +14,7 @@ MIT © Meta Vens and contributors true - 1.0.22 + 1.0.23 true diff --git a/src/GameCult.Mesh/CultMesh.cs b/src/GameCult.Mesh/CultMesh.cs index 4ec7940f..d19063ef 100644 --- a/src/GameCult.Mesh/CultMesh.cs +++ b/src/GameCult.Mesh/CultMesh.cs @@ -2852,6 +2852,40 @@ namespace GameCult.Mesh return new CultMeshRudpEndpoint(host, uri.Port, $"rudp://{uriHost}:{uri.Port}"); } + /// + /// Opens one exact hot-body subscription and advertises every body plane this runtime can + /// actually open. The provider derives locality and publishes only the selected plane. + /// + public static Task> SubscribeHotBodyAsync( + CultNetDatabaseSubscriptionClient subscriptions, + IEnumerable supportedBodyTransports, + CultMeshHotBodySubscription subscription) + { + if (subscriptions == null) throw new ArgumentNullException(nameof(subscriptions)); + if (supportedBodyTransports == null) throw new ArgumentNullException(nameof(supportedBodyTransports)); + if (subscription == null) throw new ArgumentNullException(nameof(subscription)); + var transports = supportedBodyTransports.Distinct().ToArray(); + if (transports.Length == 0) + throw new ArgumentException("At least one readable body transport is required.", nameof(supportedBodyTransports)); + return subscriptions.SubscribeAsync( + subscription.SubscriptionId, + recordKeys: subscription.RecordKeys, + schemaIds: subscription.SchemaIds, + consumerRuntimeId: subscription.ConsumerRuntimeId, + bodyIds: new[] { subscription.BodyId }, + supportedBodyTransports: transports.Select(value => value.ToString())); + } + + /// Opens an exact hot-body subscription using a resolver's readable planes. + public static Task> SubscribeHotBodyAsync( + CultNetDatabaseSubscriptionClient subscriptions, + CultMeshBodyPublicationResolver bodyResolver, + CultMeshHotBodySubscription subscription) + { + if (bodyResolver == null) throw new ArgumentNullException(nameof(bodyResolver)); + return SubscribeHotBodyAsync(subscriptions, bodyResolver.SupportedTransports, subscription); + } + /// Resolves a CultMesh URI or RUDP endpoint into a concrete RUDP endpoint. public static CultMeshRudpEndpoint ResolveRudpEndpoint(string endpointOrUri) { diff --git a/src/GameCult.Mesh/CultMeshBodies.cs b/src/GameCult.Mesh/CultMeshBodies.cs index 4f2b3514..ff12e3b9 100644 --- a/src/GameCult.Mesh/CultMeshBodies.cs +++ b/src/GameCult.Mesh/CultMeshBodies.cs @@ -150,6 +150,11 @@ namespace GameCult.Mesh private readonly Func _authorizeProducer; private readonly Func? _authorizeNamedProducer; + /// Gets locally available body planes in fastest-local-first order. + public IReadOnlyList SupportedTransports => _adapters.Keys + .OrderByDescending(TransportPreference) + .ToArray(); + public CultMeshBodyTransportService( IEnumerable adapters, Func authorizeProducer) @@ -264,6 +269,13 @@ namespace GameCult.Mesh left.AccessMode == right.AccessMode && left.Synchronization == right.Synchronization && string.Equals(left.SemanticHash, right.SemanticHash, StringComparison.Ordinal); + + private static int TransportPreference(CultMeshBodyTransportKind transport) => transport switch + { + CultMeshBodyTransportKind.SharedMemory => 3, + CultMeshBodyTransportKind.SharedFileMapping => 2, + _ => 1 + }; } public sealed class CultMeshMappedBodyPublisher diff --git a/src/GameCult.Mesh/CultMeshBodyDemand.cs b/src/GameCult.Mesh/CultMeshBodyDemand.cs index 049d762a..7bfc230f 100644 --- a/src/GameCult.Mesh/CultMeshBodyDemand.cs +++ b/src/GameCult.Mesh/CultMeshBodyDemand.cs @@ -7,6 +7,57 @@ using GameCult.Networking; namespace GameCult.Mesh { + /// + /// One exact hot-body subscription. The view record, latest-publication record, consumer body + /// demand, and locally supported planes are one contract so callers cannot accidentally stream + /// body bytes through retained document snapshots. + /// + public sealed class CultMeshHotBodySubscription + { + public CultMeshHotBodySubscription( + string subscriptionId, + string consumerRuntimeId, + string viewRecordKey, + string viewSchemaId, + string bodyId, + IEnumerable? additionalRecordKeys = null, + IEnumerable? additionalSchemaIds = null) + { + SubscriptionId = Require(subscriptionId, nameof(subscriptionId)); + ConsumerRuntimeId = Require(consumerRuntimeId, nameof(consumerRuntimeId)); + BodyId = Require(bodyId, nameof(bodyId)); + RecordKeys = new[] + { + Require(viewRecordKey, nameof(viewRecordKey)), + CultMeshBodyPublicationDocument.CreateLatestRecordKey(bodyId).Value + } + .Concat(additionalRecordKeys ?? Array.Empty()) + .Where(value => !string.IsNullOrWhiteSpace(value)) + .Distinct(StringComparer.Ordinal) + .ToArray(); + SchemaIds = new[] + { + Require(viewSchemaId, nameof(viewSchemaId)), + CultMeshBodyPublicationSchemaVersions.Publication + } + .Concat(additionalSchemaIds ?? Array.Empty()) + .Where(value => !string.IsNullOrWhiteSpace(value)) + .Distinct(StringComparer.Ordinal) + .ToArray(); + } + + public string SubscriptionId { get; } + public string ConsumerRuntimeId { get; } + public string BodyId { get; } + public IReadOnlyList RecordKeys { get; } + public IReadOnlyList SchemaIds { get; } + + private static string Require(string value, string parameterName) => + string.IsNullOrWhiteSpace(value) + ? throw new ArgumentException("Value is required.", parameterName) + : value; + } + /// Derived transport work for one logical hot body. public sealed class CultMeshBodyDemandPlan { diff --git a/src/GameCult.Mesh/CultMeshBodyPublications.cs b/src/GameCult.Mesh/CultMeshBodyPublications.cs index 37f2955f..673465cc 100644 --- a/src/GameCult.Mesh/CultMeshBodyPublications.cs +++ b/src/GameCult.Mesh/CultMeshBodyPublications.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; @@ -361,6 +362,9 @@ namespace GameCult.Mesh public CultMeshBodyPublicationResolver(CultMeshBodyTransportService transport) => _transport = transport ?? throw new ArgumentNullException(nameof(transport)); + /// Gets the body planes this resolver can actually open. + public IReadOnlyList SupportedTransports => _transport.SupportedTransports; + public ICultMeshBodyReadLease ResolveReadOnly( CultMeshBodyPublicationDocument publication, CultMeshBodyValidationRequest request) => diff --git a/src/GameCult.Mesh/docs/reliability-migration-plan.md b/src/GameCult.Mesh/docs/reliability-migration-plan.md index c52ea4dd..8b183dc6 100644 --- a/src/GameCult.Mesh/docs/reliability-migration-plan.md +++ b/src/GameCult.Mesh/docs/reliability-migration-plan.md @@ -918,6 +918,14 @@ stream demand merely by existing. and server disposal all update the same typed demand projection. Stable peer identity, not per-message wrapper identity, keys subscription lifetime. +`CultMesh.SubscribeHotBodyAsync(...)` is the public construction path. Its +`CultMeshHotBodySubscription` keeps the exact view record, the body's stable +latest-publication record, consumer identity, and logical body demand in one +contract. The local `CultMeshBodyTransportService` contributes only adapters it +can actually open, ordered shared-memory first; the server combines those +capabilities with observed peer locality. Applications do not hand-assemble +transport-name arrays or request one snapshot per frame. + **Cut line:** local frame publication writes directly into a mapped write lease and commits its descriptor. The compatibility byte-array publisher is copy-in only. The current remote frame representation may still use the verified CDN @@ -925,6 +933,12 @@ body path when remote demand exists; Phase 6 must replace that expiry-sensitive traffic with a stream/datagram body plane rather than enlarging snapshot or CDN timeouts. +Reliable control messages larger than the RUDP receive mask explicitly +acknowledge the packet just received. A retransmit older than the rolling +32-packet summary can therefore always leave the sender queue; the rolling mask +remains useful for piggyback acknowledgements but is not allowed to strand a +fragment indefinitely. + ### Gate - Two local consumers map one verified CDN artifact without duplicate process diff --git a/src/GameCult.Networking/CultNetDatabaseSubscriptionServer.cs b/src/GameCult.Networking/CultNetDatabaseSubscriptionServer.cs index 0d8e4ff2..618f64df 100644 --- a/src/GameCult.Networking/CultNetDatabaseSubscriptionServer.cs +++ b/src/GameCult.Networking/CultNetDatabaseSubscriptionServer.cs @@ -163,16 +163,17 @@ namespace GameCult.Networking { var changeType = change.GetType(); var key = (CultRecordKey)(changeType.GetProperty("Key")?.GetValue(change) ?? new CultRecordKey("")); - var schemaId = (string?)changeType.GetProperty("SchemaId")?.GetValue(change) ?? ""; if (request.RecordKeys is { Length: > 0 } && !request.RecordKeys.Contains(key.Value, StringComparer.Ordinal)) return null; - if (request.SchemaIds is { Length: > 0 } && !request.SchemaIds.Contains(schemaId, StringComparer.Ordinal)) - return null; var kind = (CultNetDatabaseChangeKind)(changeType.GetProperty("Kind")?.GetValue(change) ?? CultNetDatabaseChangeKind.Updated); var document = changeType.GetProperty("Document")?.GetValue(change); if (kind == CultNetDatabaseChangeKind.Removed || document == null) { + var previous = changeType.GetProperty("PreviousDocument")?.GetValue(change); + var schemaId = ResolveWireSchemaId(previous, (string?)changeType.GetProperty("SchemaId")?.GetValue(change) ?? ""); + if (!MatchesRequestedSchema(request.SchemaIds, previous, schemaId)) + return null; return new CultNetDatabaseChangeRawMessage { MessageId = Guid.NewGuid().ToString("N"), @@ -183,15 +184,33 @@ namespace GameCult.Networking }; } + var raw = CreateRawRecord(key, document); + if (!MatchesRequestedSchema(request.SchemaIds, document, raw.SchemaId)) + return null; return new CultNetDatabaseChangeRawMessage { MessageId = Guid.NewGuid().ToString("N"), SubscriptionId = subscriptionId, ChangeKind = kind == CultNetDatabaseChangeKind.Added ? "added" : "updated", - Document = CreateRawRecord(key, document) + Document = raw }; } + private string ResolveWireSchemaId(object? document, string fallback) + { + if (document == null) return fallback; + return _database.Documents.GetByDocumentType(document.GetType())?.SchemaId ?? fallback; + } + + private bool MatchesRequestedSchema(string[]? requestedSchemaIds, object? document, string wireSchemaId) + { + if (requestedSchemaIds is not { Length: > 0 }) return true; + if (requestedSchemaIds.Contains(wireSchemaId, StringComparer.Ordinal)) return true; + return document != null && CultNetSchemaAliasMatching.MatchesAny( + requestedSchemaIds, + _database.Cache.Registry.GetRequired(document.GetType())); + } + private CultNetRawDocumentRecord CreateRawRecord(CultRecordKey key, object document) { var method = typeof(CultNetDocumentRegistry) diff --git a/src/GameCult.Networking/CultNetTransport.cs b/src/GameCult.Networking/CultNetTransport.cs index 06c6cf1a..8bdf020a 100644 --- a/src/GameCult.Networking/CultNetTransport.cs +++ b/src/GameCult.Networking/CultNetTransport.cs @@ -1141,9 +1141,14 @@ namespace GameCult.Networking /// /// Creates a packet carrying the current acknowledgement state. /// - public CultNetRudpPacket CreateAck() + public CultNetRudpPacket CreateAck(uint? acknowledgedSequence = null) { var (ack, ackMask) = AckState(); + if (acknowledgedSequence.HasValue) + { + ack = acknowledgedSequence.Value; + ackMask = 0; + } return new CultNetRudpPacket { PacketType = CultNetRudpPacketType.Ack, @@ -1955,10 +1960,9 @@ namespace GameCult.Networking delivered = _deliveredFrames.Count > 0 ? _deliveredFrames.Dequeue() : null; if (packet.PacketType == CultNetRudpPacketType.Accept || - packet.PacketType == CultNetRudpPacketType.Data || - delivered != null) + packet.PacketType == CultNetRudpPacketType.Data) { - SendPacket(_session.CreateAck()); + SendPacket(_session.CreateAck(packet.Sequence)); } return true; @@ -2108,6 +2112,10 @@ namespace GameCult.Networking /// public bool Connected => Session.Connected; /// + /// Gets the number of reliable packets still awaiting acknowledgement. + /// + public int PendingReliablePacketCount => Session.PendingReliableSequences.Count; + /// /// Gets the last transport-level remote disconnect reason, if one was received. /// public byte[]? DisconnectReason { get; internal set; } @@ -2349,9 +2357,9 @@ namespace GameCult.Networking } delivered = _deliveredFrames.Count > 0 ? _deliveredFrames.Dequeue() : null; - if (packet.PacketType == CultNetRudpPacketType.Data || delivered != null) + if (packet.PacketType == CultNetRudpPacketType.Data) { - SendPacket(existingPeer.RemoteEndPoint, existingPeer.Session.CreateAck()); + SendPacket(existingPeer.RemoteEndPoint, existingPeer.Session.CreateAck(packet.Sequence)); } return true; diff --git a/tests/GameCult.Mesh.Tests/CultMeshBodyTransportTests.cs b/tests/GameCult.Mesh.Tests/CultMeshBodyTransportTests.cs index ca3e3045..154ee587 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshBodyTransportTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshBodyTransportTests.cs @@ -357,6 +357,30 @@ public sealed class CultMeshBodyTransportTests plan.Consumers.Should().ContainSingle().Which.ConsumerRuntimeId.Should().Be("unknown"); } + [Test] + public void HotBodySubscriptionKeepsViewPublicationAndDemandInOneExactContract() + { + var subscription = new CultMeshHotBodySubscription( + "pilot-world", + "eve-unity", + "eve:view:pilot", + "gamecult.eve.entity_soa_view.v2", + "eve:body:pilot", + new[] { "eve:field:gravity" }, + new[] { "gamecult.eve.fields_splats.v1" }); + + subscription.RecordKeys.Should().Equal( + "eve:view:pilot", + CultMeshBodyPublicationDocument.CreateLatestRecordKey("eve:body:pilot").Value, + "eve:field:gravity"); + subscription.SchemaIds.Should().Equal( + "gamecult.eve.entity_soa_view.v2", + CultMeshBodyPublicationSchemaVersions.Publication, + "gamecult.eve.fields_splats.v1"); + subscription.BodyId.Should().Be("eve:body:pilot"); + subscription.ConsumerRuntimeId.Should().Be("eve-unity"); + } + private static CultMeshBodyValidationRequest Request(CultMeshBodyDescriptor descriptor, DateTimeOffset now) => new() { BodyId = descriptor.BodyId, diff --git a/tests/GameCult.Networking.Tests/NetworkingTests.cs b/tests/GameCult.Networking.Tests/NetworkingTests.cs index 8ec4e8d4..30162445 100644 --- a/tests/GameCult.Networking.Tests/NetworkingTests.cs +++ b/tests/GameCult.Networking.Tests/NetworkingTests.cs @@ -771,6 +771,43 @@ namespace GameCult.Networking.Tests Assert.That(session.DueResends(250), Is.Empty); } + [Test] + public void RudpSession_ExplicitlyAcknowledgesRetransmitsOlderThanTheReceiveMask() + { + var sender = new CultNetRudpSession(new CultNetRudpSessionOptions + { + ConnectionId = 991, + InitialSequence = 1, + ResendDelayMs = 10 + }); + var receiver = new CultNetRudpSession(new CultNetRudpSessionOptions + { + ConnectionId = 991, + InitialSequence = 500 + }); + sender.Receive(new CultNetRudpPacket + { PacketType = CultNetRudpPacketType.Accept, ConnectionId = 991, Sequence = 400, ChannelId = "control" }); + receiver.Receive(new CultNetRudpPacket + { PacketType = CultNetRudpPacketType.Accept, ConnectionId = 991, Sequence = 0, ChannelId = "control" }); + + var packets = sender.SendMany( + "snapshot", + Enumerable.Range(0, 80).Select(index => (byte)index).ToArray(), + new CultNetRudpSendOptions { Reliable = true, Ordered = true, NowMs = 0 }, + maxFragmentBytes: 1); + foreach (var packet in packets) + receiver.Receive(packet); + + sender.Receive(receiver.CreateAck()); + Assert.That(sender.PendingReliableSequences.First(), Is.EqualTo(1u)); + + var oldRetransmit = sender.DueResends(10).Single(packet => packet.Sequence == 1); + receiver.Receive(oldRetransmit); + sender.Receive(receiver.CreateAck(oldRetransmit.Sequence)); + + Assert.That(sender.PendingReliableSequences, Does.Not.Contain(1u)); + } + [Test] public async Task RudpSession_ResendsAndAcknowledgementsCanRunConcurrently() { @@ -1544,6 +1581,104 @@ namespace GameCult.Networking.Tests Assert.That(((NetworkSchemaNote)targetCache.Get(new CultRecordKey(recordKey))!).Text, Is.EqualTo("live")); } + [Test] + public async Task DatabaseSubscription_FiltersLiveChangesByWireSchemaBinding() + { + const string recordKey = "tests:subscription-client:wire-note"; + const string wireSchema = "tests.wire_note.v1"; + var sourceCache = new CultCache(); + var sourceDocuments = new CultNetDocumentRegistry(sourceCache.Registry) + .Register(CultNetDocumentBinding.ForDocument(sourceCache.Registry, wireSchema)); + using var sourceDatabase = new CultNetDatabase(sourceCache, new CultNetDatabaseOptions + { + DocumentRegistry = sourceDocuments + }); + using var server = new RudpCultNetSchemaServer(new RudpCultNetSchemaServerOptions + { + RuntimeId = "database-wire-schema-subscription-server", + Socket = BindUdpSocket() + }); + using var subscriptions = new CultNetDatabaseSubscriptionServer(server, sourceDatabase); + using var cancellation = new CancellationTokenSource(); + var serverThread = new Thread(() => + { + while (!cancellation.IsCancellationRequested) + { + _ = server.PollOnceAsync().GetAwaiter().GetResult(); + Thread.Sleep(1); + } + }) { IsBackground = true }; + serverThread.Start(); + + var targetCache = new CultCache(); + var targetDocuments = new CultNetDocumentRegistry(targetCache.Registry) + .Register(CultNetDocumentBinding.ForDocument(targetCache.Registry, wireSchema)); + var transport = CultNetSchemaClients.CreateRudp("database-wire-schema-subscription-client"); + using var client = new CultNetDatabaseSubscriptionClient(transport, targetCache, targetDocuments); + transport.Connect("127.0.0.1", server.LocalEndPoint.Port); + await WaitUntilAsync(() => transport.Connected, TimeSpan.FromSeconds(2)); + await AwaitWithTimeout( + client.SubscribeAsync("wire-notes", recordKeys: [recordKey], schemaIds: [wireSchema]), + TimeSpan.FromSeconds(2)); + var changed = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + client.Changed += change => changed.TrySetResult(change); + + await sourceDatabase.PutAsync(new CultRecordKey(recordKey), new NetworkSchemaNote + { + Schema = "tests.networking_note.v1", + Text = "wire-live" + }); + var update = await AwaitWithTimeout(changed.Task, TimeSpan.FromSeconds(2)); + + cancellation.Cancel(); + Assert.That(update.SchemaId, Is.EqualTo(wireSchema)); + Assert.That(((NetworkSchemaNote)update.Document!).Text, Is.EqualTo("wire-live")); + } + + [Test] + public void DatabaseSubscription_FiltersCanonicalWireChangesByRequestedSchemaAlias() + { + var cache = new CultCache(); + var documents = new CultNetDocumentRegistry(cache.Registry) + .Register(CultNetDocumentBinding.ForDocument(cache.Registry)); + using var database = new CultNetDatabase(cache, new CultNetDatabaseOptions + { + DocumentRegistry = documents + }); + using var server = new RudpCultNetSchemaServer(new RudpCultNetSchemaServerOptions + { + RuntimeId = "database-schema-alias-subscription-server", + Socket = BindUdpSocket() + }); + using var subscriptions = new CultNetDatabaseSubscriptionServer(server, database); + var descriptor = cache.Registry.GetRequired(); + var change = new CultNetDatabaseChange( + CultNetDatabaseChangeKind.Added, + new CultRecordKey("tests:subscription-client:alias-note"), + descriptor.SchemaId, + database.Shards[0], + new NetworkSchemaNote { Schema = "tests.networking_note.v1", Text = "alias-live" }, + previousDocument: null); + var method = typeof(CultNetDatabaseSubscriptionServer).GetMethod( + "CreateChange", + BindingFlags.Instance | BindingFlags.NonPublic)!; + + var message = (CultNetDatabaseChangeRawMessage?)method.Invoke(subscriptions, new object[] + { + change, + new CultNetDatabaseSubscribeMessage + { + SubscriptionId = "alias-notes", + SchemaIds = ["tests.networking_note.v1"], + RecordKeys = ["tests:subscription-client:alias-note"] + }, + "alias-notes" + }); + + Assert.That(message, Is.Not.Null); + Assert.That(message!.Document!.SchemaId, Is.EqualTo(descriptor.SchemaId)); + } + [Test] public async Task RudpCultNetSchemaServer_DeliversLargeSnapshotResponse() { @@ -1607,9 +1742,14 @@ namespace GameCult.Networking.Tests if (completed != responseCompletion.Task) Assert.Fail($"Large snapshot timed out; server sent {server.Stats.BytesSent} bytes, received {server.Stats.BytesReceived} bytes, transports {server.Profile.Transports.Count()}."); var response = await AwaitWithTimeout(responseCompletion.Task, TimeSpan.FromMilliseconds(1)); + await WaitUntilAsync( + () => server.Peers.Count == 1 && server.Peers.Single().PendingReliablePacketCount == 0, + TimeSpan.FromSeconds(2)); cancellation.Cancel(); Assert.That(response.Documents, Has.Length.EqualTo(1)); Assert.That(response.Documents[0].Payload, Has.Length.EqualTo(128 * 1024)); + Assert.That(server.Stats.BytesSent, Is.LessThan(512 * 1024), + "A lossless large response must not remain trapped in the reliable resend queue."); } [Test] diff --git a/unity/org.gamecult.cultlib/README.md b/unity/org.gamecult.cultlib/README.md index be09e204..28c73612 100644 --- a/unity/org.gamecult.cultlib/README.md +++ b/unity/org.gamecult.cultlib/README.md @@ -7,8 +7,8 @@ CultCache, CultNet, and CultMesh assemblies consumed through NuGet by normal Build it with `scripts/build-unity-package.ps1`. Do not edit generated plugin assemblies in the package output. -Install version `1.0.22` through Unity Package Manager with: +Install version `1.0.23` through Unity Package Manager with: ```text -https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.22 +https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.23 ``` diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.dll index d13a4ef8..79bf48c5 100644 Binary files a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.dll and b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.dll differ diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.pdb index a55bee74..17d72db2 100644 Binary files a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.pdb and b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.MessagePack.pdb differ diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.dll index caa426d0..a06016f8 100644 Binary files a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.dll and b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.dll differ diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.pdb index fb55549d..cdeae45c 100644 Binary files a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.pdb and b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Caching.pdb differ diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Logging.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Logging.dll index 75d4d505..2a2596b7 100644 Binary files a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Logging.dll and b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Logging.dll differ diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Logging.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Logging.pdb index 96c3418c..dcf27116 100644 Binary files a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Logging.pdb and b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Logging.pdb differ diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.dll index 85a1216f..adb73216 100644 Binary files a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.dll and b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.dll differ diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.pdb index 6faa3ece..9fbfc4f4 100644 Binary files a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.pdb and b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Mesh.pdb differ diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.dll b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.dll index 919bf200..7ff275e1 100644 Binary files a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.dll and b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.dll differ diff --git a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.pdb b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.pdb index db888d0e..3d6c1c90 100644 Binary files a/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.pdb and b/unity/org.gamecult.cultlib/Runtime/Plugins/GameCult.Networking.pdb differ diff --git a/unity/org.gamecult.cultlib/package.json b/unity/org.gamecult.cultlib/package.json index 3633a675..3e6da006 100644 --- a/unity/org.gamecult.cultlib/package.json +++ b/unity/org.gamecult.cultlib/package.json @@ -1,7 +1,7 @@ { "name": "org.gamecult.cultlib", "displayName": "CultLib", - "version": "1.0.22", + "version": "1.0.23", "unity": "2021.3", "description": "CultCache, CultNet, and CultMesh managed runtime assemblies for Unity.", "license": "MIT",