Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better optionals #109

Merged
merged 5 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading