Skip to content

Commit

Permalink
Merge pull request #61 from Nexus-Mods/struct-readonly-model
Browse files Browse the repository at this point in the history
Swap ReadModels over to readonly structs
  • Loading branch information
halgari authored Jun 4, 2024
2 parents 815fe57 + 76460d1 commit d366e34
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 37 deletions.
40 changes: 40 additions & 0 deletions src/NexusMods.MnemonicDB.Abstractions/IndexSegments/Entities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,43 @@ IEnumerator IEnumerable.GetEnumerator()
return GetEnumerator();
}
}


/// <summary>
/// A wrapper around EntityIds that auto-creates the given ReadModel on-the-fly
/// </summary>
/// <typeparam name="TModel"></typeparam>
public readonly struct Entities<TModel> : IReadOnlyCollection<TModel>
where TModel : IReadOnlyModel<TModel>
{
private readonly EntityIds _ids;
private readonly IDb _db;

/// <summary>
/// A wrapper around EntityIds that auto-creates the given ReadModel on-the-fly
/// </summary>
/// <typeparam name="TModel"></typeparam>
public Entities(EntityIds ids, IDb db)
{
_ids = ids;
_db = db;
}


/// <inheritdoc />
public IEnumerator<TModel> GetEnumerator()
{
foreach (var id in _ids)
{
yield return TModel.Create(_db, id);
}
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

/// <inheritdoc />
public int Count => _ids.Count;
}
12 changes: 11 additions & 1 deletion src/NexusMods.MnemonicDB.Abstractions/IndexSegments/EntityIds.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
using System;
using System.Collections;
using System.Collections.Generic;
using NexusMods.MnemonicDB.Abstractions.Models;

namespace NexusMods.MnemonicDB.Abstractions.IndexSegments;

/// <summary>
/// A subview of an IndexSegment that returns the entity ids of the segment
/// </summary>
public struct EntityIds(IndexSegment segment, int start, int end) :
IEnumerable<EntityId>, IIndexSegment<EntityId>
IReadOnlyCollection<EntityId>, IIndexSegment<EntityId>
{
/// <summary>
/// Gets the value at the given location
Expand Down Expand Up @@ -46,4 +47,13 @@ IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}

/// <summary>
/// Creates a view of these Ids that auto-casts every Id into a model of the given model type
/// </summary>
public Entities<TModel> AsModels<TModel>(IDb db)
where TModel : IReadOnlyModel<TModel>
{
return new Entities<TModel>(this, db);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.Collections;
using System.Collections.Generic;
using NexusMods.MnemonicDB.Abstractions.Models;

namespace NexusMods.MnemonicDB.Abstractions.IndexSegments;

/// <summary>
/// A wrapper around Values that auto-creates the given ReadModel on-the-fly
/// </summary>
public readonly struct ValueEntities<TModel> : IReadOnlyCollection<TModel>
where TModel : IReadOnlyModel<TModel>
{
private readonly Values<EntityId, ulong> _values;

/// <summary>
/// The database the models are read from
/// </summary>
private IDb Db { get; }

/// <summary>
/// Creates a new ValueEntities, from the given values, database, and entity id
/// </summary>
public ValueEntities(Values<EntityId, ulong> values, IDb db)
{
_values = values;
Db = db;
}

/// <inheritdoc />
public IEnumerator<TModel> GetEnumerator()
{
foreach (var value in _values)
{
yield return TModel.Create(Db, value);
}
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}


/// <inheritdoc />
public int Count => _values.Count;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,13 @@ public static Entities<Values<EntityId, ulong>, TModel> As<TModel>(this Values<E
{
return new Entities<Values<EntityId, ulong>, TModel>(ids, db);
}

/// <summary>
/// Returns a view of the values as models whose ids are the given id
/// </summary>
public static ValueEntities<TModel> AsModels<TModel>(this Values<EntityId, ulong> values, IDb db)
where TModel : IReadOnlyModel<TModel>
{
return new ValueEntities<TModel>(values, db);
}
}

This file was deleted.

22 changes: 22 additions & 0 deletions src/NexusMods.MnemonicDB.Abstractions/Models/IReadOnlyModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Collections.Generic;

namespace NexusMods.MnemonicDB.Abstractions.Models;

/// <summary>
/// Base interface for all read-only models
/// </summary>
public interface IReadOnlyModel : IHasEntityIdAndDb, IReadOnlyCollection<IReadDatom>
{

}

/// <summary>
/// Typed version of IReadOnlyModel, that has a static Create method
/// </summary>
public interface IReadOnlyModel<out TModel> : IReadOnlyModel
{
/// <summary>
/// Creates a new instance of the model with the given id
/// </summary>
public static abstract TModel Create(IDb db, EntityId id);
}
10 changes: 2 additions & 8 deletions src/NexusMods.MnemonicDB.Abstractions/Models/ReadOnlyModel.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
using System.Collections;
using System.Collections.Generic;
using NexusMods.MnemonicDB.Abstractions.Attributes;

namespace NexusMods.MnemonicDB.Abstractions.Models;

/// <summary>
/// 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.
/// </summary>
public class ReadOnlyModel(IDb db, EntityId id) : IHasEntityIdAndDb, IEnumerable<IReadDatom>
public readonly struct ReadOnlyModel(IDb db, EntityId id) : IHasEntityIdAndDb, IEnumerable<IReadDatom>
{
/// <inheritdoc />
public EntityId Id => id;
Expand Down Expand Up @@ -45,14 +44,9 @@ public bool Contains(IAttribute attribute)
return false;
}

/// <summary>
/// Override this to provide a custom model name
/// </summary>
protected virtual string ModelName => GetType().Name;

/// <inheritdoc />
public override string ToString()
{
return $"{ModelName}<{Id.Value:x}>";
return $"ReadOnlyModel<{Id.Value:x}>";
}
}
83 changes: 76 additions & 7 deletions src/NexusMods.MnemonicDB.SourceGenerator/Template.weave
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#nullable enable

using System;
using System.Collections;
using System.Collections.Generic;

namespace {{= model.Namespace}};
using __ABSTRACTIONS__ = NexusMods.MnemonicDB.Abstractions;
Expand Down Expand Up @@ -172,13 +174,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 readonly __SEGMENTS__.IndexSegment _segment;

private ReadOnly(__ABSTRACTIONS__.IDb db, __SEGMENTS__.IndexSegment segment, __ABSTRACTIONS__.EntityId id) {
Db = db;
Id = id;
_segment = segment;
}

/// <summary>
/// Constructs a new ReadOnly model of the entity.
/// </summary>
public ReadOnly(__ABSTRACTIONS__.IDb db, __ABSTRACTIONS__.EntityId id) :
this(db, db.Get(id), id)
{
}

/// <summary>
/// The entity id of the model.
/// </summary>
public __ABSTRACTIONS__.EntityId Id { get; }

/// <summary>
/// The database that the entity is associated with.
/// </summary>
public __ABSTRACTIONS__.IDb Db { get; }

/// <summary>
/// Constructs a new ReadOnly model of the entity.
/// </summary>
public static ReadOnly Create(__ABSTRACTIONS__.IDb db, __ABSTRACTIONS__.EntityId id) {
return new ReadOnly(db, db.Get(id), id);
}

/// <inheritdoc />
public int Count => _segment.Count;


/// <inheritdoc />
public IEnumerator<IReadDatom> GetEnumerator()
{
for (var i = 0; i < _segment.Count; i++)
{
yield return _segment[i].Resolved;
}
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}


/// <summary>
/// Looks for the given attribute in the entity
/// </summary>
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}}
Expand All @@ -195,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}}
Expand All @@ -211,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}}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion tests/NexusMods.MnemonicDB.Tests/DbTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<EId:200000000000002>");

await VerifyTable(mod);
}
Expand Down

0 comments on commit d366e34

Please sign in to comment.