Resolve C# subscription filters by schema alias

This commit is contained in:
Meta Vens 2026-06-27 21:53:28 +01:00
commit 1b9140da3d
2 changed files with 117 additions and 5 deletions

View file

@ -344,7 +344,10 @@ namespace GameCult.Networking
var changeType = change.GetType();
var key = (CultRecordKey)(changeType.GetProperty("Key")?.GetValue(change) ?? new CultRecordKey(string.Empty));
var schemaId = (string?)changeType.GetProperty("SchemaId")?.GetValue(change) ?? string.Empty;
if (!Matches(request, schemaId, key))
var documentType = changeType.IsGenericType ? changeType.GetGenericArguments()[0] : null;
var descriptor = documentType == null ? null : _database.Cache.Registry.GetRequired(documentType);
var binding = documentType == null ? null : _database.Documents.GetByDocumentType(documentType);
if (!Matches(request, schemaId, key, descriptor, binding))
{
return null;
}
@ -387,17 +390,75 @@ namespace GameCult.Networking
return ((CultNetDocumentPutRawMessage)message!).Document;
}
private static bool Matches(CultNetDatabaseSubscribeMessage request, string schemaId, CultRecordKey key)
private static bool Matches(
CultNetDatabaseSubscribeMessage request,
string schemaId,
CultRecordKey key,
CultDocumentDescriptor? descriptor,
CultNetDocumentBinding? binding)
{
var schemaMatches = request.SchemaIds == null ||
request.SchemaIds.Length == 0 ||
request.SchemaIds.Contains(schemaId, StringComparer.Ordinal);
var schemaMatches = MatchesSchema(request.SchemaIds, schemaId, descriptor, binding);
var keyMatches = request.RecordKeys == null ||
request.RecordKeys.Length == 0 ||
request.RecordKeys.Contains(key.Value, StringComparer.Ordinal);
return schemaMatches && keyMatches;
}
private static bool MatchesSchema(
string[]? requestedSchemaIds,
string schemaId,
CultDocumentDescriptor? descriptor,
CultNetDocumentBinding? binding)
{
if (requestedSchemaIds == null || requestedSchemaIds.Length == 0)
{
return true;
}
var requested = requestedSchemaIds.ToHashSet(StringComparer.Ordinal);
if (requested.Contains(schemaId))
{
return true;
}
if (descriptor == null)
{
return false;
}
if (requested.Contains(descriptor.SchemaId) ||
requested.Contains(descriptor.SchemaName) ||
requested.Contains(descriptor.SchemaVersion) ||
(binding != null && requested.Contains(binding.SchemaId)))
{
return true;
}
if (descriptor.ToCatalogEntry().CompatibleSchemaIds.Any(requested.Contains))
{
return true;
}
return requested
.Select(InferSchemaName)
.Where(schemaName => !string.IsNullOrWhiteSpace(schemaName))
.Any(schemaName => string.Equals(schemaName, descriptor.SchemaName, StringComparison.Ordinal));
}
private static string? InferSchemaName(string schemaVersion)
{
var marker = schemaVersion.LastIndexOf(".v", StringComparison.Ordinal);
if (marker <= 0 || marker + 2 >= schemaVersion.Length)
{
return null;
}
var version = schemaVersion.Substring(marker + 2);
return version.All(char.IsDigit)
? schemaVersion.Substring(0, marker)
: null;
}
private static string SubscriptionKey(NetPeer peer, string subscriptionId)
{
return $"{peer.Id}:{subscriptionId}";

View file

@ -4039,6 +4039,57 @@ namespace GameCult.Networking.Tests
Assert.That(message.Document!.Payload, Is.EqualTo(SerializePlayerDataPayload(player)));
}
[Test]
public void CultNetDatabaseServer_Creates_Filtered_SubscriptionChange_ForSchemaAlias()
{
var cache = new CultCache();
var registry = new CultNetDocumentRegistry(cache.Registry)
.Register(CultNetDocumentBinding.ForDocument<NetworkSchemaNote>(cache.Registry));
var database = new CultNetDatabase(cache, new CultNetDatabaseOptions
{
DocumentRegistry = registry
});
using var server = new Server(cache, DevelopmentServerSecurity);
using var databaseServer = new CultNetDatabaseServer(server, database);
var key = new CultRecordKey("network-note:alias-change");
var note = new NetworkSchemaNote
{
Schema = "tests.networking_note.v1",
Text = "alias change",
Revision = 11
};
var descriptor = cache.Registry.GetRequired<NetworkSchemaNote>();
var change = new CultNetDatabaseChange<NetworkSchemaNote>(
CultNetDatabaseChangeKind.Added,
key,
descriptor.SchemaId,
database.Shards[0],
note,
previousDocument: null);
var method = typeof(CultNetDatabaseServer).GetMethod(
"CreateChangeMessage",
BindingFlags.Instance | BindingFlags.NonPublic)!;
var message = (CultNetDatabaseChangeRawMessage?)method.Invoke(databaseServer, new object[]
{
change,
"sub-alias",
new CultNetDatabaseSubscribeMessage
{
SubscriptionId = "sub-alias",
SchemaIds = ["tests.networking_note.v1"],
RecordKeys = [key.Value]
}
});
Assert.That(message, Is.Not.Null);
Assert.That(message!.SubscriptionId, Is.EqualTo("sub-alias"));
Assert.That(message.ChangeKind, Is.EqualTo("added"));
Assert.That(message.Document, Is.Not.Null);
Assert.That(message.Document!.SchemaId, Is.EqualTo(descriptor.SchemaId));
Assert.That(message.Document.Payload, Is.EqualTo(CultDocumentMessagePackSerialization.Serialize(note)));
}
[Test]
public void CultNetSchemaRegistry_BuiltInCatalog_AdvertisesRawLane_AndSharedGhostlightContract()
{