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

Restrict actions and states types #552

Merged
merged 1 commit into from
Oct 15, 2019
Merged

Conversation

moreal
Copy link
Contributor

@moreal moreal commented Oct 1, 2019

This pr is related with #541.

  • Restrict types of IAction.PlainValue to Bencodex.Types.IValue.

@moreal moreal self-assigned this Oct 1, 2019
<Project
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
Sdk="Microsoft.NET.Sdk">
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" Sdk="Microsoft.NET.Sdk">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file seems unnecessary to be changed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still the diff remains. Should be unchanged.

@moreal moreal force-pushed the restrict-types branch 7 times, most recently from 3e2c2de to e8ee68a Compare October 4, 2019 12:42
@codecov
Copy link

codecov bot commented Oct 4, 2019

Codecov Report

Merging #552 into master will decrease coverage by 0.33%.
The diff coverage is 88.28%.

@@            Coverage Diff             @@
##           master     #552      +/-   ##
==========================================
- Coverage   90.85%   90.52%   -0.34%     
==========================================
  Files         201      202       +1     
  Lines       15300    15315      +15     
==========================================
- Hits        13901    13864      -37     
- Misses       1107     1163      +56     
+ Partials      292      288       -4
Impacted Files Coverage Δ
Libplanet.Tests/Action/ActionContextTest.cs 90.47% <ø> (ø) ⬆️
Libplanet.Tests/Common/Action/BaseAction.cs 50% <ø> (ø) ⬆️
Libplanet.Tests/Common/Action/DetectRehearsal.cs 0% <0%> (ø) ⬆️
Libplanet.Tests/Common/Action/BattleResult.cs 41.07% <100%> (-23.38%) ⬇️
Libplanet/Tx/RawTransaction.cs 82.35% <100%> (-2.32%) ⬇️
Libplanet.Tests/Store/StoreTest.cs 99.06% <100%> (ø) ⬆️
Libplanet/Tx/Transaction.cs 94.48% <100%> (-0.11%) ⬇️
Libplanet/Action/IAccountStateDelta.cs 100% <100%> (ø) ⬆️
Libplanet/Net/Messages/RecentStates.cs 100% <100%> (ø) ⬆️
Libplanet/Store/LiteDBStore.cs 88.48% <100%> (ø) ⬆️
... and 27 more

@moreal moreal force-pushed the restrict-types branch 2 times, most recently from 963b682 to c3a1860 Compare October 7, 2019 05:45
@moreal moreal changed the title [WIP] Restrict actions and states types Restrict actions and states types Oct 7, 2019
@moreal moreal marked this pull request as ready for review October 7, 2019 06:25
Copy link
Contributor

@dahlia dahlia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to update example source code here and there so that they are compatible to the new interface.

/// <code><![CDATA[
/// using System;
/// using System.Collections.Immutable;
/// using Libplanet;
/// using Libplanet.Action;
///
/// public class MyAction : IAction
/// {
/// // Declare an enum type to distinguish types of in-game logic.
/// public enum ActType { CreateCharacter, Attack, Heal }
///
/// // Declare properties (or fields) to store "bound" argument values.
/// public ActType Type { get; private set; }
/// public Address TargetAddress { get; private set; }
///
/// // Action must has a public parameterless constructor.
/// // Usually this is used only by Libplanet's internals.
/// public MyAction() {}
///
/// // Take argument values to "bind" through constructor parameters.
/// public MyAction(ActType type, Address targetAddress)
/// {
/// Type = type;
/// TargetAddress = targetAddress;
/// }
///
/// // The main game logic belongs to here. It takes the
/// // previous states through its parameter named context,
/// // and is offered "bound" argument values through
/// // its own properties (or fields).
/// IAccountStateDelta IAction.Execute(IActionContext context)
/// {
/// // Gets the state immediately before this action is executed.
/// // ImmutableDictionary<string, uint> is just for example,
/// // As far as it is serializable, you can store any types.
/// // (We recommend to use immutable types though.)
/// var state = (ImmutableDictionary<string, uint>)
/// context.PreviousStates.GetState(TargetAddress);
///
/// // This variable purposes to store the state
/// // right after this action finishes.
/// ImmutableDictionary<string, uint> nextState;
///
/// // Does different things depending on the action's type.
/// // This way is against the common principals of programming
/// // as it is just an example. You could compare this with
/// // a better example of PolymorphicAction<T> class.
/// switch (Type)
/// {
/// case ActType.CreateCharacter:
/// if (!TargetAddress.Equals(context.Signer))
/// throw new Exception(
/// "TargetAddress of CreateCharacter action " +
/// "only can be the same address to the " +
/// "Transaction<T>.Signer.");
/// else if (!(state is null))
/// throw new Exception(
/// "Character was already created.");
///
/// nextState = ImmutableDictionary<string, uint>.Empty
/// .Add("hp", 20);
/// break;
///
/// case ActType.Attack:
/// nextState =
/// state.SetItem("hp", Math.Max(state["hp"] - 5, 0));
/// break;
///
/// case ActType.Heal:
/// nextState =
/// state.SetItem("hp", Math.Min(state["hp"] + 5, 20));
/// break;
///
/// default:
/// throw new Exception(
/// "Properties are not properly initialized.");
/// }
///
/// // Builds a delta (dirty) from previous to next states, and
/// // returns it.
/// return context.PreviousStates.SetState(TargetAddress,
/// nextState);
/// }
///
/// // Side effects, i.e., any effects on other than states, are
/// // done here.
/// void IAction.Render(
/// IActionContext context,
/// IAccountStateDelta nextStates)
/// {
/// Character c;
///
/// // You could compare this with a better example of
/// // PolymorphicAction<T> class.
/// switch (Type)
/// {
/// case ActType.CreateCharacter:
/// c = new Character
/// {
/// Address = TargetAddress,
/// Hp = 0,
/// };
/// break;
///
/// case ActType.Attack:
/// case ActType.Heal:
/// c = Character.GetByAddress(TargetAddress);
/// break;
///
/// default:
/// break;
/// }
///
/// c?.Hp = nextStates.GetState(TargetAddress)["hp"];
/// c?.Draw();
/// }
///
/// // Sometimes a block to which an action belongs can be
/// // a "stale." If that action already has been rendered,
/// // it should be undone.
/// void IAction.Unrender(
/// IActionContext context,
/// IAccountStateDelta nextStates)
/// {
/// Character c = Character.GetByAddress(TargetAddress);
///
/// // You could compare this with a better example of
/// // PolymorphicAction<T> class.
/// switch (Type)
/// {
/// case ActType.CreateCharacter:
/// c.Hide();
/// break;
///
/// case ActType.Attack:
/// case ActType.Heal:
/// IAccountStateDelta prevStates = context.PreviousStates;
/// c.Hp = prevStates.GetState(TargetAddress)["hp"];
/// c.Draw();
/// break;
///
/// default:
/// break;
/// }
/// }
///
/// // Serializes its "bound arguments" so that they are transmitted
/// // over network or stored to the persistent storage.
/// // It uses .NET's built-in serialization mechanism.
/// IImmutableDictionary<string, object> IAction.PlainValue =>
/// ImmutableDictionary<string, object>.Empty
/// .Add("type", Type)
/// .Add("target_address", TargetAddress);
///
/// // Deserializes "bound arguments". That is, it is inverse
/// // of PlainValue property.
/// void IAction.LoadPlainValue(
/// IImmutableDictionary<string, object> plainValue)
/// {
/// Type = (ActType)plainValue["type"];
/// TargetAddress = (Address)plainValue["target_address"];
/// }
/// }
/// ]]></code>

/// <code><![CDATA[
/// using System;
/// using System.Collections.Immutable;
/// using Libplanet;
/// using Libplanet.Action;
///
/// // Instead of having multiple in-game actions in a class,
/// // in this example, we declare one abstract base class
/// // and its three concrete subclasses.
/// public abstract class ActionBase : IAction
/// {
/// public ActionBase() { }
///
/// public ActionBase(Address targetAddress)
/// {
/// TargetAddress = targetAddress;
/// }
///
/// public Address TargetAddress { get; private set; }
///
/// // Leaves Execute() abstract so that concrete subclasses
/// // implement their own logic.
/// public abstract IAccountStateDelta Execute(IActionContext context);
///
/// // Makes Render() no-op by default, but overrideable by subclasses.
/// public virtual void Render(
/// IActionContext context,
/// IAccountStateDelta nextStates)
/// {
/// }
///
/// // Makes Unrender() no-op by default,
/// // but overrideable by subclasses.
/// public virtual void Unrender(
/// IActionContext context,
/// IAccountStateDelta nextStates)
/// {
/// }
///
/// IImmutableDictionary<string, object> IAction.PlainValue =>
/// ImmutableDictionary<string, object>.Empty
/// .Add("target_address", TargetAddress);
///
/// void IAction.LoadPlainValue(
/// IImmutableDictionary<string, object> plainValue)
/// {
/// TargetAddress = (Address)plainValue["target_address"];
/// }
/// }
///
/// // PolymorphicAction<T> requires concrete action classes marked with
/// // ActionTypeAttribute.
/// // There is only one required parameter to ActionTypeAttribute,
/// // which takes a unique identifier of the action type.
/// // This is used for serialization and deserialization under the hood.
/// [ActionType("create_character")]
/// public sealed class CreateCharacter : ActionBase
/// {
/// public override IAccountStateDelta Execute(IActionContext context)
/// {
/// var state = (ImmutableDictionary<string, uint>)
/// context.PreviousStates.GetState(TargetAddress);
/// if (!TargetAddress.Equals(context.Signer))
/// throw new Exception(
/// "TargetAddress of CreateCharacter action only can be " +
/// "the same address to the Transaction<T>.Signer."
/// );
/// else if (!(state is null))
/// throw new Exception("Character was already created.");
/// return context.PreviousStates.SetState(
/// TargetAddress,
/// ImmutableDictionary<string, uint>.Empty.Add("hp", 20)
/// );
/// }
///
/// void IAction.Render(
/// IActionContext context,
/// IAccountStateDelta nextStates)
/// {
/// var c = new Character
/// {
/// Address = TargetAddress,
/// Hp = nextStates.GetState(TargetAddress)["hp"],
/// };
/// c.Draw();
/// break;
/// }
///
/// void IAction.Unrender(
/// IActionContext context,
/// IAccountStateDelta nextStates)
/// {
/// Character c = Character.GetByAddress(TargetAddress);
/// c.Hide();
/// }
/// }
///
/// [ActionType("attack")]
/// public sealed class Attack : ActionBase
/// {
/// public override IAccountStateDelta Execute(IActionContext context)
/// {
/// var state = (ImmutableDictionary<string, uint>)
/// context.PreviousStates.GetState(TargetAddress);
/// return context.PreviousStates.SetState(
/// TargetAddress,
/// state.SetItem("hp", Math.Max(state["hp"] - 5, 0))
/// );
/// }
///
/// void IAction.Render(
/// IActionContext context,
/// IAccountStateDelta nextStates)
/// {
/// Character c = Character.GetByAddress(TargetAddress);
/// c.Hp = nextStates.GetState(TargetAddress)["hp"];
/// c.Draw();
/// }
///
/// void IAction.Unrender(
/// IActionContext context,
/// IAccountStateDelta nextStates)
/// {
/// Character c = Character.GetByAddress(TargetAddress);
/// c.Hp = context.PreviousStates.GetState(TargetAddress)["hp"];
/// c.Draw();
/// }
/// }
///
/// [ActionType("heal")]
/// public sealed class Heal : ActionBase
/// {
/// public override IAccountStateDelta Execute(IActionContext context)
/// {
/// var state = (ImmutableDictionary<string, uint>)
/// context.PreviousStates.GetState(TargetAddress);
/// return context.PreviousStates.SetState(
/// TargetAddress,
/// state.SetItem("hp", Math.Min(state["hp"] + 5, 20))
/// );
/// }
///
/// void IAction.Render(
/// IActionContext context,
/// IAccountStateDelta nextStates)
/// {
/// Character c = Character.GetByAddress(TargetAddress);
/// c.Hp = nextStates.GetState(TargetAddress)["hp"];
/// c.Draw();
/// }
///
/// void IAction.Unrender(
/// IActionContext context,
/// IAccountStateDelta nextStates)
/// {
/// Character c = Character.GetByAddress(TargetAddress);
/// c.Hp = context.PreviousStates.GetState(TargetAddress)["hp"];
/// c.Draw();
/// }
/// }
/// ]]></code>

@dahlia dahlia added this to the 0.7.0 milestone Oct 7, 2019
CHANGES.md Outdated Show resolved Hide resolved
Assert.Equal("a", init.GetState(_addr[0]));
Assert.Equal("b", a.GetState(_addr[1]));
Assert.Equal("b", init.GetState(_addr[1]));
IAccountStateDelta a = init.SetState(_addr[0], "A".ToBencodex());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seem two ways to convert a string object to a Text object: casting operator and .ToBencodex() method, and in this code two ways coexist. Is there any guideline to choose the appropriate way for each case between two ways?

Personally, .ToBencodex() has a shortcoming that it hides the resulting type; although we readers could find it's something IValue, it's difficult to guess its concrete type (e.g., Text, not just IValue).

Copy link
Contributor Author

@moreal moreal Oct 8, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I fixed all cases like (Text)"string" and I'll prepare the guideline.
  2. Yes, so I tried to rename the names like AsText, AsInteger and AsBinary etc..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, what's the point of having v.AsText() instead of (Text)v?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my thought, v.AsText() looks better than (Text)v.

CHANGES.md Outdated Show resolved Hide resolved
CHANGES.md Show resolved Hide resolved
Libplanet/Action/AccountStateGetter.cs Show resolved Hide resolved
<Project
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
Sdk="Microsoft.NET.Sdk">
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" Sdk="Microsoft.NET.Sdk">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still the diff remains. Should be unchanged.

Libplanet/Action/IAction.cs Outdated Show resolved Hide resolved
@@ -214,7 +215,7 @@ public interface IAction
/// (or fields) of an action, so that they can be transmitted over
/// network or saved to persistent storage.
/// <para>Serialized values are deserialized by <see
/// cref="LoadPlainValue(IImmutableDictionary{string,object})"/> method
/// cref="LoadPlainValue(IValue)"/> method
/// later.</para>
/// <para>It uses <a href=
/// "https://docs.microsoft.com/en-us/dotnet/standard/serialization/"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This explanation is outdated, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I will update it.

@@ -225,8 +226,8 @@ public interface IAction
/// "https://docs.microsoft.com/en-us/dotnet/standard/serialization/"
/// >serializable</a>.</returns>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This explanation is also outdated. Should be rewritten.

.IsAssignableFrom(typeof(T))
? kv.Value
: FromBencodexValue(kv.Value);
if (key == "actions")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this key so special?

@moreal moreal force-pushed the restrict-types branch 2 times, most recently from 4180757 to a9a5065 Compare October 8, 2019 11:34
CHANGES.md Outdated Show resolved Hide resolved
CHANGES.md Outdated Show resolved Hide resolved
CHANGES.md Outdated Show resolved Hide resolved
CHANGES.md Outdated Show resolved Hide resolved
Libplanet.Tests/Libplanet.Tests.csproj Outdated Show resolved Hide resolved
Libplanet/Libplanet.csproj Outdated Show resolved Hide resolved
@moreal moreal force-pushed the restrict-types branch 4 times, most recently from b1c24c0 to a79d7da Compare October 10, 2019 11:34
@moreal
Copy link
Contributor Author

moreal commented Oct 10, 2019

Also IMO: all the objects like Block, Transaction, should be serialized with Bencodex for scalability to other languages and avoid the situation like BencodexFormatter.cs#L65

CHANGES.md Outdated
- `IAction.LoadPlainValue(IImmutableDictionary<string, object>)` method
became replaced by `LoadPlainValue(IValue)`.
- `AccountStateGetter` became to return `IValue`, not `object`.
- Added `BencodexExtension` to write more readable code.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

“To write readable code” seems a too broad description (i.e., provides no much information).

Suggested change
- Added `BencodexExtension` to write more readable code.
- Added `BencodexExtension` static class.

Assert.Equal("a", init.GetState(_addr[0]));
Assert.Equal("b", a.GetState(_addr[1]));
Assert.Equal("b", init.GetState(_addr[1]));
IAccountStateDelta a = init.SetState(_addr[0], "A".ToBencodex());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, what's the point of having v.AsText() instead of (Text)v?

@dahlia
Copy link
Contributor

dahlia commented Oct 11, 2019

@moreal I'd like to do pair programming on this patch next week. Is it alright?

@moreal
Copy link
Contributor Author

moreal commented Oct 12, 2019

@moreal I'd like to do pair programming on this patch next week. Is it alright?

Of course, it seems essential because it is too huge.

dahlia
dahlia previously approved these changes Oct 14, 2019
@dahlia dahlia requested review from earlbread, longfin and limebell and removed request for libplanet October 14, 2019 12:47
Libplanet.Tests/Blockchain/BlockChainTest.cs Outdated Show resolved Hide resolved
Libplanet.Tests/Blockchain/BlockChainTest.cs Outdated Show resolved Hide resolved
Libplanet/Net/Messages/RecentStates.cs Outdated Show resolved Hide resolved
dahlia
dahlia previously approved these changes Oct 15, 2019
@dahlia dahlia requested a review from earlbread October 15, 2019 06:14
@dahlia dahlia merged commit 79fcc1d into planetarium:master Oct 15, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants