planetarium / libplanet

Blockchain in C#/.NET for on-chain, decentralized gaming
https://docs.libplanet.io/
GNU Lesser General Public License v2.1
506 stars 142 forks source link

Provide API to do post-process right after a certain action executed #31

Closed ipdae closed 5 years ago

ipdae commented 5 years ago

In games that currently use libplanet, there is no way to know created action has been processed. so, in the game, we waiting for process has finished like this. However, this can cause the game to hang if the block containing the Tx is not included in the block chain. In my opinion, if libplanet provides an event based on the result of the process, the game will be handling the internal state change as event-driven and solved the problem.

moreal commented 5 years ago

the link looks like broken :(
Now, how is it working? (this issue)

dahlia commented 5 years ago

I guess @ipdae wanted to link to https://github.com/planetarium/nekoyume-unity/blob/4e2e942d1916bd76f43233fb4dea3e7111512696/nekoyume/Assets/_Scripts/Game/Stage.cs#L103.

dahlia commented 5 years ago

Ah, that repository seems not public yet. ☹️

dahlia commented 5 years ago

Quote the code he referred to:

private IEnumerator SleepAsync()
{
    var tables = this.GetRootComponent<Data.Tables>();
    Data.Table.Stats statsData;
    if (tables.Stats.TryGetValue(ActionManager.Instance.Avatar.Level, out statsData))
    {
        ActionManager.Instance.Sleep(statsData);
        while (ActionManager.Instance.Avatar.CurrentHP == ActionManager.Instance.Avatar.HPMax)
        {
            yield return new WaitForSeconds(1.0f);
        }
    }
    OnRoomEnter();
}
dahlia commented 5 years ago

The quoted code looks helpful to explain his intention though.

To sum up, the current API enforces game apps to poll every state change of the blockchain in order to execute game logic depending on that that state change, which is inefficient.

moreal commented 5 years ago

I agree about that opinion, the pattern based on event
It'll be so helpful for reducing useless waiting.

dahlia commented 5 years ago

I started to work on this yesterday, and have sketched my rough idea on it. Let me explain my current idea.

The reason why we need to be aware when an action is actually processed (whatever it technically means) is not to create a new consequent action that depends on previous states causes by the previous action. Making an action depending on another action is already possible and easy; you only need to put both actions into two different blocks.

To me, the real reason why we need a such event handler is to render the result to display it to game players. More precisely, in order to render it as early as possible right after it gets determined. “Determined” here technically means ⅰ) a block that the action was put into is mined, ⅱ) a branch containing that block is promoted as the canon, and ⅲ) that block is finalized in the network.

Unfortunately, Libplanet's blockchain does not satisfy finality at present, and even if it already satisfies finality, the latency between the moment an action is created and the moment a block which contains that action is promoted and finalized in the network is too high for multiplayer games. So let's deal with only ⅰ) and ⅱ) at the moment.

There are two moments that a node becomes aware that a block is promoted (or, treated as a candidate at least): a) when another node propagates that block which is not mined by this node, and b) when another node propagates a next block on top of that block which is mined by this node. The reason to wait a next block in the case b) is for minimal finality. If low latency is prior to finality we might determine it early: b²) when that block which is mined by this node is propagated to other n nodes.

At such moments, Swarm could notify a signal to BlockChain<T>, and BlockChain<T> calls post-processing callbacks of all actions in the block received through that signal. These callbacks purpose to render something for the most part, so I'm considering to name it Render(). It also needs the current states of the blockchain, so Render() method should be given them:

public interface IAction
{
    // Other methods are omitted here.
    IAccountStateDelta Execute(IActionContext context);
    void Render(AccountStateGetter previousStateGetter, AccountStateGetter nextStateGetter);
}

What if a block which consists of already Render()ed actions loses its status as a canon? Since Libplanet's blockchain currently doesn't satisfy finality, such situations may occur, so we need to deal with this. My best idea to deal with this at the moment is to add another method named Unrender() which rolls back the effect made by an action:

public interface IAction
{
    // Other methods are omitted here.
    IAccountStateDelta Execute(IActionContext context);
    void Render(AccountStateGetter previousStateGetter, AccountStateGetter nextStateGetter);
    void Unrender(AccountStateGetter nextStateGetter, AccountStateGetter previousStateGetter);
}

When an existing canon branch is replaced by a new canon branch, Swarm currently calls BlockChain<T>.Swap() under the hood. Swarm determines the branch point between two branches, so it can tell BlockChain<T>.Swap() what blocks should be invalidated. When IAction.Unrender() methods are called, they should be invoked in the reverse order when the Render() methods are called.

Although I should adjust API details, could anyone leave comments on this?