Skip to content

Commit

Permalink
Merge pull request #47 from chickensoft-games/fix/restoration
Browse files Browse the repository at this point in the history
fix: restrict state restoration by interface instead of disjoint type
  • Loading branch information
jolexxa authored Jul 25, 2024
2 parents 1b344a2 + fe24eaf commit 521d622
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 54 deletions.
5 changes: 3 additions & 2 deletions Chickensoft.LogicBlocks.Tests/test/src/LogicBlockTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -605,10 +605,11 @@ public void HashCodesAreDifferentForEquivalentLogicBlocks() {
}

[Fact]
public void RestoreFromThrowsIfNotSameTypeOfLogicBlock() {
public void RestoreFromThrowsIfOtherIsNotStarted() {
var logic = new MyLogicBlock();
var other = new FakeLogicBlock();
var other = new MyLogicBlock();

// Other is not yet started, so nothing to restore.
Should.Throw<LogicBlockException>(() => logic.RestoreFrom(other));
}

Expand Down
6 changes: 3 additions & 3 deletions Chickensoft.LogicBlocks/src/LogicBlock.Serialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ JsonSerializerOptions options
// enables us to respect new persisted values or non-persisted values that
// have already been added to the blackboard during construction.
foreach (var objType in blackboard.Types) {
_blackboard.OverwriteObject(objType, blackboard.GetObject(objType));
Blackboard.OverwriteObject(objType, blackboard.GetObject(objType));
}

if (
Expand All @@ -71,7 +71,7 @@ JsonSerializerOptions options
// Load the state from the logic block's blackboard, since we have
// preallocated states during construction, and deserialization has
// overwritten any preallocated states with the deserialized state.
var state = _blackboard.GetObject(stateType);
var state = Blackboard.GetObject(stateType);

// Set the state to be used (instead of the logic block's initial state)
// whenever the logic block is started.
Expand Down Expand Up @@ -102,7 +102,7 @@ JsonSerializerOptions options
json[STATE_PROPERTY] = stateJson;

json[BLACKBOARD_PROPERTY] = JsonSerializer.SerializeToNode(
_blackboard,
Blackboard,
options
);
}
Expand Down
60 changes: 30 additions & 30 deletions Chickensoft.LogicBlocks/src/LogicBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ namespace Chickensoft.LogicBlocks;
/// </para>
/// </summary>
/// <typeparam name="TState">State type.</typeparam>
public interface ILogicBlock<TState> : ISerializableBlackboard
where TState : StateLogic<TState> {
public interface ILogicBlock<TState> :
ILogicBlockBase, ISerializableBlackboard where TState : StateLogic<TState> {
/// <summary>
/// Logic block execution context.
/// </summary>
Expand Down Expand Up @@ -104,7 +104,7 @@ public interface ILogicBlock<TState> : ISerializableBlackboard
/// Restores the logic block from a deserialized logic block.
/// </summary>
/// <param name="logic">Other logic block.</param>
void RestoreFrom(LogicBlockBase logic);
void RestoreFrom(ILogicBlock<TState> logic);

/// <summary>
/// Adds a binding to the logic block. This is used internally by the standard
Expand Down Expand Up @@ -161,7 +161,7 @@ public abstract partial class LogicBlock<TState> : LogicBlockBase,

#region LogicBlockBase
/// <inheritdoc />
internal override object? ValueAsObject => _value;
public override object? ValueAsObject => _value;

/// <inheritdoc />
public override void RestoreState(object state) {
Expand All @@ -171,7 +171,7 @@ public override void RestoreState(object state) {
);
}

_restoredState = (TState)state;
RestoredState = (TState)state;
}
#endregion LogicBlockBase

Expand Down Expand Up @@ -313,63 +313,63 @@ protected Transition To<TStateType>()

#region IReadOnlyBlackboard
/// <inheritdoc />
public IReadOnlySet<Type> Types => _blackboard.Types;
public IReadOnlySet<Type> Types => Blackboard.Types;

/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TData Get<TData>() where TData : class => _blackboard.Get<TData>();
public TData Get<TData>() where TData : class => Blackboard.Get<TData>();

/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public object GetObject(Type type) => _blackboard.GetObject(type);
public object GetObject(Type type) => Blackboard.GetObject(type);

/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Has<TData>() where TData : class => _blackboard.Has<TData>();
public bool Has<TData>() where TData : class => Blackboard.Has<TData>();

/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasObject(Type type) => _blackboard.HasObject(type);
public bool HasObject(Type type) => Blackboard.HasObject(type);
#endregion IReadOnlyBlackboard

#region IBlackboard
/// <inheritdoc cref="IBlackboard.Set{TData}(TData)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Set<TData>(TData data) where TData : class =>
_blackboard.Set(data);
Blackboard.Set(data);

/// <inheritdoc cref="IBlackboard.SetObject(Type, object)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetObject(Type type, object data) =>
_blackboard.SetObject(type, data);
Blackboard.SetObject(type, data);

/// <inheritdoc cref="IBlackboard.Overwrite{TData}(TData)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Overwrite<TData>(TData data) where TData : class =>
_blackboard.Overwrite(data);
Blackboard.Overwrite(data);

/// <inheritdoc cref="IBlackboard.OverwriteObject(Type, object)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void OverwriteObject(Type type, object data) =>
_blackboard.OverwriteObject(type, data);
Blackboard.OverwriteObject(type, data);
#endregion IBlackboard

#region ISerializableBlackboard
/// <inheritdoc cref="ISerializableBlackboard.SavedTypes" />
public IEnumerable<Type> SavedTypes => _blackboard.SavedTypes;
public IEnumerable<Type> SavedTypes => Blackboard.SavedTypes;

/// <inheritdoc cref="ISerializableBlackboard.TypesToSave" />
public IEnumerable<Type> TypesToSave => _blackboard.TypesToSave;
public IEnumerable<Type> TypesToSave => Blackboard.TypesToSave;

/// <inheritdoc cref="ISerializableBlackboard.Save{TData}(Func{TData})" />
public void Save<TData>(Func<TData> factory)
where TData : class, IIdentifiable => _blackboard.Save(factory);
where TData : class, IIdentifiable => Blackboard.Save(factory);

/// <inheritdoc
/// cref="ISerializableBlackboard.SaveObject(Type, Func{object}, object?)" />
public void SaveObject(
Type type, Func<object> factory, object? referenceValue
) => _blackboard.SaveObject(type, factory, referenceValue);
) => Blackboard.SaveObject(type, factory, referenceValue);
#endregion ISerializableBlackboard

internal TState ProcessInputs<TInputType>(
Expand All @@ -379,8 +379,8 @@ internal TState ProcessInputs<TInputType>(

if (_value is null) {
// No state yet. Let's get the first state going!
ChangeState(_restoredState as TState ?? GetInitialState().State);
_restoredState = null;
ChangeState(RestoredState as TState ?? GetInitialState().State);
RestoredState = null;
}

// We can always process the first input directly.
Expand Down Expand Up @@ -489,7 +489,7 @@ TState fallback
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private TState Flush() {
if (_value is null) {
_blackboard.InstantiateAnyMissingSavedData();
Blackboard.InstantiateAnyMissingSavedData();
}

return ProcessInputs<int>();
Expand Down Expand Up @@ -518,16 +518,16 @@ public override bool Equals(object? obj) {
}

// Ensure blackboard entries are equal.
var types = _blackboard.Types;
var otherTypes = logic._blackboard.Types;
var types = Blackboard.Types;
var otherTypes = logic.Blackboard.Types;

if (types.Count != otherTypes.Count) { return false; }

foreach (var type in types) {
if (!otherTypes.Contains(type)) { return false; }

var obj1 = _blackboard.GetObject(type);
var obj2 = logic._blackboard.GetObject(type);
var obj1 = Blackboard.GetObject(type);
var obj2 = logic.Blackboard.GetObject(type);

if (SerializationUtilities.IsEquivalent(obj1, obj2)) {
continue;
Expand All @@ -545,15 +545,15 @@ public override bool Equals(object? obj) {
public override int GetHashCode() => base.GetHashCode();

/// <inheritdoc />
public void RestoreFrom(LogicBlockBase logic) {
if ((logic.ValueAsObject ?? logic._restoredState) is not TState state) {
throw new LogicBlockException($"Cannot restore from logic block {logic}");
public void RestoreFrom(ILogicBlock<TState> logic) {
if ((logic.ValueAsObject ?? logic.RestoredState) is not TState state) {
throw new LogicBlockException($"Cannot restore from logic block {logic}.");
}

Stop();

foreach (var type in logic._blackboard.Types) {
_blackboard.OverwriteObject(type, logic._blackboard.GetObject(type));
foreach (var type in logic.Blackboard.Types) {
Blackboard.OverwriteObject(type, logic.Blackboard.GetObject(type));
}

var stateType = state.GetType();
Expand Down
52 changes: 35 additions & 17 deletions Chickensoft.LogicBlocks/src/LogicBlockBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,54 @@ namespace Chickensoft.LogicBlocks;
using Chickensoft.Serialization;

/// <summary>
/// Common, non-generic base type for all logic blocks. This exists to allow
/// all logic blocks in a codebase to be identified by inspecting the derived
/// types computed from the generated type registry that the logic blocks
/// generator produces.
/// <inheritdoc cref="LogicBlockBase" path="/summary" />
/// </summary>
public abstract class LogicBlockBase {
public interface ILogicBlockBase {
/// <summary>
/// Current state of the logic block, if any. Reading this will not start
/// the logic block and can return null.
/// </summary>
internal abstract object? ValueAsObject { get; }
object? ValueAsObject { get; }

/// <summary>
/// Used by the logic blocks serializer to see if a given logic block state
/// has diverged from an unaltered copy of the state that's stored here —
/// one reference state for every type (not instance) of a logic block state.
/// </summary>
internal static ConcurrentDictionary<Type, object> ReferenceStates { get; } =
new();
/// <summary>State that will be restored when started, if any.</summary>
object? RestoredState { get; }

/// <summary>Internal blackboard of the logic block.</summary>
SerializableBlackboard Blackboard { get; }

/// <summary>
/// Restore the state from a given object. Only works if the current
/// state has not been initialized and the <paramref name="state"/> is
/// of the correct type.
/// </summary>
/// <param name="state">State to restore.</param>
public abstract void RestoreState(object state);
void RestoreState(object state);
}

/// <summary>Internal blackboard of the logic block.</summary>
internal readonly SerializableBlackboard _blackboard = new();
/// <summary>
/// Common, non-generic base type for all logic blocks. This exists to allow
/// all logic blocks in a codebase to be identified by inspecting the derived
/// types computed from the generated type registry that the logic blocks
/// generator produces.
/// </summary>
public abstract class LogicBlockBase : ILogicBlockBase {
/// <inheritdoc />
public abstract object? ValueAsObject { get; }

/// <inheritdoc />
public object? RestoredState { get; set; }

/// <inheritdoc />
public SerializableBlackboard Blackboard { get; } = new();

/// <inheritdoc />
public abstract void RestoreState(object state);

internal object? _restoredState;
/// <summary>
/// Used by the logic blocks serializer to see if a given logic block state
/// has diverged from an unaltered copy of the state that's stored here —
/// one reference state for every type (not instance) of a logic block state.
/// </summary>
internal static ConcurrentDictionary<Type, object> ReferenceStates { get; } =
new();
}
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"rollForward": "latestMinor",
"version": "8.0.303"
"version": "7.0.410"
}
}
2 changes: 1 addition & 1 deletion renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"matchPackagePrefixes": [
"dotnet-sdk"
],
"allowedVersions": "!/preview/"
"allowedVersions": "/^$/"
},
{
"matchPackagePrefixes": [
Expand Down

0 comments on commit 521d622

Please sign in to comment.