Skip to content

Commit

Permalink
Merge pull request #109 from Nexus-Mods/better-optionals
Browse files Browse the repository at this point in the history
Better optionals
  • Loading branch information
halgari authored Nov 13, 2024
2 parents ea97d53 + 6673951 commit 45f278f
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public async Task<ulong> ReadThenWrite()
using var tx = Connection.BeginTransaction();
var mod = Mod.Load(Connection.Db, _modId);
var oldHash = mod.OptionalHash;
tx.Add(_modId, Mod.OptionalHash, Hash.From(oldHash.Value + 1));
tx.Add(_modId, Mod.OptionalHash, Hash.From((ulong)oldHash.Value + 1));
var nextdb = await tx.Commit();

var loadout = Loadout.Load(Connection.Db, mod.LoadoutId);
Expand Down
103 changes: 49 additions & 54 deletions src/NexusMods.MnemonicDB.Abstractions/Attributes/ScalarAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using DynamicData.Kernel;
using JetBrains.Annotations;
using NexusMods.MnemonicDB.Abstractions.IndexSegments;
using NexusMods.MnemonicDB.Abstractions.Models;

namespace NexusMods.MnemonicDB.Abstractions.Attributes;
Expand Down Expand Up @@ -32,84 +34,77 @@ public bool Contains<T>(T entity) where T : IHasIdAndIndexSegment
}

/// <summary>
/// Gets the value of the attribute from the entity.
/// Tries to get the value of the attribute from the entity.
/// </summary>
public TValue Get(IHasIdAndIndexSegment entity)
public bool TryGetValue<T>(T entity, IndexSegment segment, [NotNullWhen(true)] out TValue? value)
where T : IHasEntityIdAndDb
{
var segment = entity.IndexSegment;
var dbId = entity.Db.AttributeCache.GetAttributeId(Id);
for (var i = 0; i < segment.Count; i++)
var attributeId = entity.Db.AttributeCache.GetAttributeId(Id);
foreach (var datom in segment)
{
var datom = segment[i];
if (datom.A != dbId) continue;
return ReadValue(datom.ValueSpan, datom.Prefix.ValueTag, entity.Db.Connection.AttributeResolver);
if (datom.A != attributeId) continue;
value = ReadValue(datom.ValueSpan, datom.Prefix.ValueTag, entity.Db.Connection.AttributeResolver);
return true;
}

if (DefaultValue.HasValue)
return DefaultValue.Value;

ThrowKeyNotfoundException(entity);
return default!;
value = default!;
return false;
}

/// <summary>
/// Gets the value of the attribute from the entity, this performs a lookup in the database
/// so prefer using the overload with IHasIdAndIndexSegment if you already have the segment.
/// Tries to get the value of the attribute from the entity.
/// </summary>
protected TValue Get(IHasEntityIdAndDb entity)
public bool TryGetValue<T>(T entity, [NotNullWhen(true)] out TValue? value)
where T : IHasIdAndIndexSegment
{
var segment = entity.Db.Get(entity.Id);
var dbId = entity.Db.AttributeCache.GetAttributeId(Id);
for (var i = 0; i < segment.Count; i++)
{
var datom = segment[i];
if (datom.A != dbId) continue;
return ReadValue(datom.ValueSpan, datom.Prefix.ValueTag, entity.Db.Connection.AttributeResolver);
}

if (DefaultValue.HasValue)
return DefaultValue.Value;

ThrowKeyNotfoundException(entity);
return default!;
return TryGetValue(entity, segment: entity.IndexSegment, out value);
}

/// <summary>
/// Retracts the attribute from the entity.
/// Gets the value of the attribute from the entity.
/// </summary>
public void Retract(IAttachedEntity entityWithTx)
public TValue Get<T>(T entity, IndexSegment segment)
where T : IHasEntityIdAndDb
{
Retract(entityWithTx, Get(entityWithTx));
if (TryGetValue(entity, segment, out var value)) return value;
return ThrowKeyNotfoundException(entity.Id);
}

private void ThrowKeyNotfoundException(IHasEntityIdAndDb entity)
/// <summary>
/// Gets the value of the attribute from the entity.
/// </summary>
public TValue Get<T>(T entity)
where T : IHasIdAndIndexSegment
{
throw new KeyNotFoundException($"Entity {entity.Id} does not have attribute {Id}");
var segment = entity.IndexSegment;
return Get(entity, segment);
}

/// <summary>
/// Try to get the value of the attribute from the entity.
/// Gets the value of the attribute from the entity, <see cref="DefaultValue"/>, or <see cref="Optional{TValue}.None"/>.
/// </summary>
public bool TryGet(IHasIdAndIndexSegment entity, out TValue value)
public Optional<TValue> GetOptional<T>(T entity)
where T : IHasIdAndIndexSegment
{
var segment = entity.IndexSegment;
var dbId = entity.Db.AttributeCache.GetAttributeId(Id);
for (var i = 0; i < segment.Count; i++)
{
var datom = segment[i];
if (datom.A != dbId) continue;
value = ReadValue(datom.ValueSpan, datom.Prefix.ValueTag, entity.Db.Connection.AttributeResolver);
return true;
}
if (TryGetValue(entity, entity.IndexSegment, out var value)) return value;
return DefaultValue.HasValue ? DefaultValue : Optional<TValue>.None;
}

if (DefaultValue.HasValue)
{
value = DefaultValue.Value;
return true;
}
/// <summary>
/// Retracts the attribute from the entity.
/// </summary>
public void Retract(IAttachedEntity entityWithTx)
{
Retract(entityWithTx, value: Get(entityWithTx, segment: entityWithTx.Db.Get(entityWithTx.Id)));
}

value = default!;
return false;
[DoesNotReturn]
private TValue ThrowKeyNotfoundException(EntityId entityId)
{
throw new KeyNotFoundException($"Entity `{entityId}` doesn't have attribute {Id}");
#pragma warning disable CS0162 // Unreachable code detected
return default!;
#pragma warning restore CS0162 // Unreachable code detected
}

/// <summary>
Expand Down
4 changes: 4 additions & 0 deletions src/NexusMods.MnemonicDB.SourceGenerator/Template.weave
Original file line number Diff line number Diff line change
Expand Up @@ -416,8 +416,12 @@ public partial class {{= model.Name}} : __MODELS__.IModelFactory<{{= model.Name}
{{elif attr.IsReference}}
public {{= attr.ReferenceType.ToDisplayString()}}Id {{= attr.ContextualName}} => {{= attr.ReferenceType.ToDisplayString()}}Id.From({{= attr.FieldName}}.Get(this));
{{else}}
{{if attr.IsOptional}}
public global::DynamicData.Kernel.Optional<{{= attr.HighLevelType.ToDisplayString()}}> {{= attr.ContextualName }} => {{= attr.FieldName}}.GetOptional(this);
{{else}}
public {{= attr.HighLevelType.ToDisplayString()}} {{= attr.ContextualName}} => {{= attr.FieldName}}.Get(this);
{{/if}}
{{/if}}


{{if attr.IsReference && attr.IsScalar}}
Expand Down
14 changes: 6 additions & 8 deletions tests/NexusMods.MnemonicDB.Tests/DbTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -541,13 +541,13 @@ public async Task CanReadAndWriteOptionalAttributes()

var remapped = result.Remap(modWithDescription);
remapped.Contains(Mod.Description).Should().BeTrue();
Mod.Description.TryGet(remapped, out var foundDesc).Should().BeTrue();
Mod.Description.TryGetValue(remapped, remapped.IndexSegment, out var foundDesc).Should().BeTrue();
foundDesc.Should().Be("Test Description");
remapped.Description.Should().Be("Test Description");
remapped.Description.Value.Should().Be("Test Description");

var remapped2 = result.Remap(modWithoutDiscription);
remapped2.Contains(Mod.Description).Should().BeFalse();
Mod.Description.TryGet(remapped2, out var foundDesc2).Should().BeFalse();
Mod.Description.TryGetValue(remapped2, remapped2.IndexSegment, out var foundDesc2).Should().BeFalse();
}

[Fact]
Expand Down Expand Up @@ -1039,12 +1039,10 @@ public async Task CanGetAttributesThatRequireDI()
Name = "Test Loadout",
GamePath = path
};

var result = await tx.Commit();

var loadout = result.Remap(loadout1);

loadout.GamePath.Should().Be(path);
loadout.GamePath.Value.Should().Be(path);
}

[Fact]
Expand Down

0 comments on commit 45f278f

Please sign in to comment.