From 6738bf854e559618c5dd9582a4a44216fbc72c52 Mon Sep 17 00:00:00 2001 From: halgari Date: Tue, 4 Jun 2024 08:36:27 -0600 Subject: [PATCH 1/3] Struct read only models. --- .../IndexSegments/Entities.cs | 40 ++++++++++ .../IndexSegments/EntityIds.cs | 2 +- .../Models/IReadOnlyConcreteModel.cs | 20 ----- .../Models/IReadOnlyModel.cs | 34 +++++++++ .../Models/ReadOnlyModel.cs | 10 +-- .../Template.weave | 76 ++++++++++++++++++- 6 files changed, 149 insertions(+), 33 deletions(-) delete mode 100644 src/NexusMods.MnemonicDB.Abstractions/Models/IReadOnlyConcreteModel.cs create mode 100644 src/NexusMods.MnemonicDB.Abstractions/Models/IReadOnlyModel.cs diff --git a/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/Entities.cs b/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/Entities.cs index 68d346ec..57946be9 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/Entities.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/Entities.cs @@ -36,3 +36,43 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } } + + +/// +/// A wrapper around EntityIds that auto-creates the given ReadModel on-the-fly +/// +/// +public readonly struct Entities : IReadOnlyCollection +where TModel : IReadOnlyModel +{ + private readonly EntityIds _ids; + private readonly IDb _db; + + /// + /// A wrapper around EntityIds that auto-creates the given ReadModel on-the-fly + /// + /// + public Entities(EntityIds ids, IDb db) + { + _ids = ids; + _db = db; + } + + + /// + public IEnumerator GetEnumerator() + { + foreach (var id in _ids) + { + yield return TModel.Create(_db, id); + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + public int Count => _ids.Count; +} diff --git a/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/EntityIds.cs b/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/EntityIds.cs index ac79e311..bba8debb 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/EntityIds.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/EntityIds.cs @@ -8,7 +8,7 @@ namespace NexusMods.MnemonicDB.Abstractions.IndexSegments; /// A subview of an IndexSegment that returns the entity ids of the segment /// public struct EntityIds(IndexSegment segment, int start, int end) : - IEnumerable, IIndexSegment + IReadOnlyCollection, IIndexSegment { /// /// Gets the value at the given location diff --git a/src/NexusMods.MnemonicDB.Abstractions/Models/IReadOnlyConcreteModel.cs b/src/NexusMods.MnemonicDB.Abstractions/Models/IReadOnlyConcreteModel.cs deleted file mode 100644 index ab9d9e0e..00000000 --- a/src/NexusMods.MnemonicDB.Abstractions/Models/IReadOnlyConcreteModel.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace NexusMods.MnemonicDB.Abstractions.Models; - -/// -/// A interface for the concrete implementation of a readonly model. -/// -/// -/// -public interface IReadOnlyConcreteModel -where TModel : ReadOnlyModel, TInterface -{ - /// - /// Create a new instance of the ReadOnlyModel with the given db and id. - /// - public static abstract TInterface CreateReadOnly(IDb db, EntityId id); - - /// - /// List of attributes that are required for this model. - /// - public static abstract IAttribute[] RequiredAttributes { get; } -} diff --git a/src/NexusMods.MnemonicDB.Abstractions/Models/IReadOnlyModel.cs b/src/NexusMods.MnemonicDB.Abstractions/Models/IReadOnlyModel.cs new file mode 100644 index 00000000..40cc1b6e --- /dev/null +++ b/src/NexusMods.MnemonicDB.Abstractions/Models/IReadOnlyModel.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; + +namespace NexusMods.MnemonicDB.Abstractions.Models; + +/// +/// Base interface for all read-only models +/// +public interface IReadOnlyModel : IHasEntityIdAndDb, IReadOnlyCollection +{ + /// + /// Looks for the given attribute in the entity + /// + public bool Contains(IAttribute attribute) + { + foreach (var datom in this) + { + if (datom.A == attribute) + return true; + } + + return false; + } +} + +/// +/// Typed version of IReadOnlyModel, that has a static Create method +/// +public interface IReadOnlyModel : IReadOnlyModel +{ + /// + /// Creates a new instance of the model with the given id + /// + public static abstract TModel Create(IDb db, EntityId id); +} diff --git a/src/NexusMods.MnemonicDB.Abstractions/Models/ReadOnlyModel.cs b/src/NexusMods.MnemonicDB.Abstractions/Models/ReadOnlyModel.cs index 33cb80f8..33bc2745 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/Models/ReadOnlyModel.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/Models/ReadOnlyModel.cs @@ -1,6 +1,5 @@ using System.Collections; using System.Collections.Generic; -using NexusMods.MnemonicDB.Abstractions.Attributes; namespace NexusMods.MnemonicDB.Abstractions.Models; @@ -8,7 +7,7 @@ namespace NexusMods.MnemonicDB.Abstractions.Models; /// An entity is a reference to the attributes of a specific EnityId. Think of this as a hashmap /// of attributes, or a row in a database table. /// -public class ReadOnlyModel(IDb db, EntityId id) : IHasEntityIdAndDb, IEnumerable +public readonly struct ReadOnlyModel(IDb db, EntityId id) : IHasEntityIdAndDb, IEnumerable { /// public EntityId Id => id; @@ -45,14 +44,9 @@ public bool Contains(IAttribute attribute) return false; } - /// - /// Override this to provide a custom model name - /// - protected virtual string ModelName => GetType().Name; - /// public override string ToString() { - return $"{ModelName}<{Id.Value:x}>"; + return $"ReadOnlyModel<{Id.Value:x}>"; } } diff --git a/src/NexusMods.MnemonicDB.SourceGenerator/Template.weave b/src/NexusMods.MnemonicDB.SourceGenerator/Template.weave index af421e65..ca26752b 100644 --- a/src/NexusMods.MnemonicDB.SourceGenerator/Template.weave +++ b/src/NexusMods.MnemonicDB.SourceGenerator/Template.weave @@ -172,13 +172,81 @@ public partial class {{= model.Name}} { } {{= model.Comments}} - public partial class ReadOnly(__ABSTRACTIONS__.IDb db, __ABSTRACTIONS__.EntityId id) : - __MODELS__.ReadOnlyModel(db, id) { + public readonly partial struct ReadOnly : + __MODELS__.IReadOnlyModel<{{= model.Name}}.ReadOnly> { - protected override string ModelName => "{{= model.Name}}"; + private __SEGMENTS__.IndexSegment _segment; + private ReadOnly(__ABSTRACTIONS__.IDb db, __SEGMENTS__.IndexSegment segment, __ABSTRACTIONS__.EntityId id) { + Db = db; + Id = id; + _segment = segment; + } + + /// + /// Constructs a new ReadOnly model of the entity. + /// + public ReadOnly(__ABSTRACTIONS__.IDb db, __ABSTRACTIONS__.EntityId id) : + this(db, db.Get(id), id) + { + } + + /// + /// The entity id of the model. + /// + public __ABSTRACTIONS__.EntityId Id { get; } + + /// + /// The database that the entity is associated with. + /// + public __ABSTRACTIONS__.IDb Db { get; } + + /// + /// Constructs a new ReadOnly model of the entity. + /// + public static ReadOnly Create(__ABSTRACTIONS__.IDb db, __ABSTRACTIONS__.EntityId id) { + return new ReadOnly(db, db.Get(id), id); + } + + /// + public int Count => _segment.Count; + + /// + public IEnumerator GetEnumerator() + { + var segment = Db.Get(Id); + for (var i = 0; i < segment.Count; i++) + { + yield return segment[i].Resolved; + } + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Looks for the given attribute in the entity + /// + public bool Contains(IAttribute attribute) + { + foreach (var datom in this) + { + if (datom.A == attribute) + return true; + } + + return false; + } + + public override string ToString() + { + return "{{= model.Name}}<" + id + ">"; + } - public virtual bool IsValid() + public bool IsValid() { {{each attr in model.Attributes}} {{if !attr.IsMarker}} From 6830e75ed998b24e01b23eb50374dcb73aa654c1 Mon Sep 17 00:00:00 2001 From: Timothy Baldridge Date: Tue, 4 Jun 2024 10:11:29 -0600 Subject: [PATCH 2/3] WIP on compilation fixes --- .../IndexSegments/EntityIds.cs | 10 ++++++++++ .../Models/IReadOnlyModel.cs | 12 ------------ .../Template.weave | 17 +++++++++-------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/EntityIds.cs b/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/EntityIds.cs index bba8debb..a0061964 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/EntityIds.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/EntityIds.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using NexusMods.MnemonicDB.Abstractions.Models; namespace NexusMods.MnemonicDB.Abstractions.IndexSegments; @@ -46,4 +47,13 @@ IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + + /// + /// Creates a view of these Ids that auto-casts every Id into a model of the given model type + /// + Entities AsModels(IDb db) + where TModel : IReadOnlyModel + { + return new Entities(this, db); + } } diff --git a/src/NexusMods.MnemonicDB.Abstractions/Models/IReadOnlyModel.cs b/src/NexusMods.MnemonicDB.Abstractions/Models/IReadOnlyModel.cs index 40cc1b6e..2b987e72 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/Models/IReadOnlyModel.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/Models/IReadOnlyModel.cs @@ -7,19 +7,7 @@ namespace NexusMods.MnemonicDB.Abstractions.Models; /// public interface IReadOnlyModel : IHasEntityIdAndDb, IReadOnlyCollection { - /// - /// Looks for the given attribute in the entity - /// - public bool Contains(IAttribute attribute) - { - foreach (var datom in this) - { - if (datom.A == attribute) - return true; - } - return false; - } } /// diff --git a/src/NexusMods.MnemonicDB.SourceGenerator/Template.weave b/src/NexusMods.MnemonicDB.SourceGenerator/Template.weave index ca26752b..6a7fe926 100644 --- a/src/NexusMods.MnemonicDB.SourceGenerator/Template.weave +++ b/src/NexusMods.MnemonicDB.SourceGenerator/Template.weave @@ -5,6 +5,8 @@ #nullable enable using System; +using System.Collections; +using System.Collections.Generic; namespace {{= model.Namespace}}; using __ABSTRACTIONS__ = NexusMods.MnemonicDB.Abstractions; @@ -175,7 +177,7 @@ public partial class {{= model.Name}} { public readonly partial struct ReadOnly : __MODELS__.IReadOnlyModel<{{= model.Name}}.ReadOnly> { - private __SEGMENTS__.IndexSegment _segment; + private readonly __SEGMENTS__.IndexSegment _segment; private ReadOnly(__ABSTRACTIONS__.IDb db, __SEGMENTS__.IndexSegment segment, __ABSTRACTIONS__.EntityId id) { Db = db; @@ -211,22 +213,22 @@ public partial class {{= model.Name}} { /// public int Count => _segment.Count; + /// public IEnumerator GetEnumerator() { - var segment = Db.Get(Id); - for (var i = 0; i < segment.Count; i++) + for (var i = 0; i < _segment.Count; i++) { - yield return segment[i].Resolved; + yield return _segment[i].Resolved; } } - /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } + /// /// Looks for the given attribute in the entity /// @@ -243,7 +245,7 @@ public partial class {{= model.Name}} { public override string ToString() { - return "{{= model.Name}}<" + id + ">"; + return "{{= model.Name}}<" + Id + ">"; } public bool IsValid() @@ -279,8 +281,7 @@ public partial class {{= model.Name}} { {{each backref in model.BackReferences}} - public IEnumerable<{{= backref.OtherModel.ToDisplayString()}}.ReadOnly> {{= backref.Name}} => Db.GetBackRefs({{= backref.OtherAttribute.ToDisplayString()}}, this.Id) - .Select(otherId => new {{= backref.OtherModel.ToDisplayString()}}.ReadOnly(Db, otherId)); + public __SEGMENTS__.Entities<{{= backref.OtherModel.ToDisplayString()}}.ReadOnly> {{= backref.Name}} => Db.GetBackRefs({{= backref.OtherAttribute.ToDisplayString()}}, this.Id).AsModels<{{= backRef.OtherModel.ToDisplayString()}}.ReadOnly>(Db); {{/each}} /// From 76460d14c911a98cdc1665572e7cf4e2dc8a6f72 Mon Sep 17 00:00:00 2001 From: halgari Date: Tue, 4 Jun 2024 11:55:46 -0600 Subject: [PATCH 3/3] Fix up tests and compile issues --- .../IndexSegments/EntityIds.cs | 2 +- .../IndexSegments/ValueEntities.cs | 46 +++++++++++++++++++ .../IndexSegments/ValuesExtensions.cs | 9 ++++ .../Template.weave | 4 +- tests/NexusMods.MnemonicDB.Tests/DbTests.cs | 2 +- 5 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 src/NexusMods.MnemonicDB.Abstractions/IndexSegments/ValueEntities.cs diff --git a/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/EntityIds.cs b/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/EntityIds.cs index a0061964..fcbd0df7 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/EntityIds.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/EntityIds.cs @@ -51,7 +51,7 @@ IEnumerator IEnumerable.GetEnumerator() /// /// Creates a view of these Ids that auto-casts every Id into a model of the given model type /// - Entities AsModels(IDb db) + public Entities AsModels(IDb db) where TModel : IReadOnlyModel { return new Entities(this, db); diff --git a/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/ValueEntities.cs b/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/ValueEntities.cs new file mode 100644 index 00000000..c25c858c --- /dev/null +++ b/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/ValueEntities.cs @@ -0,0 +1,46 @@ +using System.Collections; +using System.Collections.Generic; +using NexusMods.MnemonicDB.Abstractions.Models; + +namespace NexusMods.MnemonicDB.Abstractions.IndexSegments; + +/// +/// A wrapper around Values that auto-creates the given ReadModel on-the-fly +/// +public readonly struct ValueEntities : IReadOnlyCollection + where TModel : IReadOnlyModel +{ + private readonly Values _values; + + /// + /// The database the models are read from + /// + private IDb Db { get; } + + /// + /// Creates a new ValueEntities, from the given values, database, and entity id + /// + public ValueEntities(Values values, IDb db) + { + _values = values; + Db = db; + } + + /// + public IEnumerator GetEnumerator() + { + foreach (var value in _values) + { + yield return TModel.Create(Db, value); + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + + /// + public int Count => _values.Count; +} diff --git a/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/ValuesExtensions.cs b/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/ValuesExtensions.cs index 37b43a41..d7d48eb4 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/ValuesExtensions.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/IndexSegments/ValuesExtensions.cs @@ -15,4 +15,13 @@ public static Entities, TModel> As(this Values, TModel>(ids, db); } + + /// + /// Returns a view of the values as models whose ids are the given id + /// + public static ValueEntities AsModels(this Values values, IDb db) + where TModel : IReadOnlyModel + { + return new ValueEntities(values, db); + } } diff --git a/src/NexusMods.MnemonicDB.SourceGenerator/Template.weave b/src/NexusMods.MnemonicDB.SourceGenerator/Template.weave index 6a7fe926..800f3c4b 100644 --- a/src/NexusMods.MnemonicDB.SourceGenerator/Template.weave +++ b/src/NexusMods.MnemonicDB.SourceGenerator/Template.weave @@ -265,7 +265,7 @@ public partial class {{= model.Name}} { {{each attr in model.Attributes}} {{if attr.IsCollection && attr.IsReference && !attr.IsMarker}} public __SEGMENTS__.Values<__ABSTRACTIONS__.EntityId, ulong> {{= attr.ContextualName}} => {{= attr.FieldName}}.Get(this); - public IEnumerable<{{= attr.ReferenceType.ToDisplayString()}}.ReadOnly> {{= attr.Name}} => {{= attr.FieldName}}.Get(this).Select(id => new {{= attr.ReferenceType.ToDisplayString()}}.ReadOnly(Db, id)); + public __SEGMENTS__.ValueEntities<{{= attr.ReferenceType.ToDisplayString()}}.ReadOnly> {{= attr.Name}} => __SEGMENTS__.ValuesExtensions.AsModels<{{= attr.ReferenceType.ToDisplayString()}}.ReadOnly>({{= attr.FieldName}}.Get(this), Db); {{elif attr.IsMarker}} public bool Is{{= attr.ContextualName}} => {{= attr.FieldName}}.Contains(this); {{else}} @@ -281,7 +281,7 @@ public partial class {{= model.Name}} { {{each backref in model.BackReferences}} - public __SEGMENTS__.Entities<{{= backref.OtherModel.ToDisplayString()}}.ReadOnly> {{= backref.Name}} => Db.GetBackRefs({{= backref.OtherAttribute.ToDisplayString()}}, this.Id).AsModels<{{= backRef.OtherModel.ToDisplayString()}}.ReadOnly>(Db); + public __SEGMENTS__.Entities<{{= backref.OtherModel.ToDisplayString()}}.ReadOnly> {{= backref.Name}} => Db.GetBackRefs({{= backref.OtherAttribute.ToDisplayString()}}, this.Id).AsModels<{{= backref.OtherModel.ToDisplayString()}}.ReadOnly>(Db); {{/each}} /// diff --git a/tests/NexusMods.MnemonicDB.Tests/DbTests.cs b/tests/NexusMods.MnemonicDB.Tests/DbTests.cs index e9d9ad97..608890d8 100644 --- a/tests/NexusMods.MnemonicDB.Tests/DbTests.cs +++ b/tests/NexusMods.MnemonicDB.Tests/DbTests.cs @@ -281,7 +281,7 @@ public async Task CanGetDatomsFromEntity() mod.Contains(Mod.Source).Should().BeTrue(); mod.Contains(Loadout.Name).Should().BeFalse(); - mod.ToString().Should().Be("Mod<200000000000002>"); + mod.ToString().Should().Be("Mod"); await VerifyTable(mod); }