2016-10-20 1 views
1

私はゲームエンジンを開発しています。 anglejsフロントエンドは、IIS上で実行されている.NET WebAPIプロジェクトのエンドポイントと通信します。このWeb APIは、独立したプロジェクトにあるゲームのドメインロジックに対してコマンドを実行するファサードです。私は現在エンティティフレームワークを使用している作業単位を持っています。オブジェクトの状態を更新するときに、どのようにコマンドを同期させることができますか?

したがって、angularjsコードはIIS上のwebapiウェブサイトのエンドポイントを呼び出し、ゲームに対して実行するコマンドを作成します。たとえば、開かれた銀行口座。

このコマンドは、データストアからゲームのインスタンスを読み込み、完了を確認してからコマンドを実行して銀行口座を開きます。ゲームインスタンスのデータを変更するコマンドを実行すると、多くのイベントが発生します。

私はこれらのコマンドがすべて同じ基本クラスを持っていますが、一度に呼び出されるはずのものは1つだけです。

public abstract class GameInstanceCommandHandler<T> 
    : CommandHandlerBase<T, GameInstanceIDCommandResult> 
    where T : GameInstanceCommand 
{ 
    protected GameInstance GameInstance { get; private set; } 

    protected GameInstanceCommandHandler() { } 

    public GameInstanceCommandHandler(IUnitOfWork uow) 
     : base(uow) 
    { 
    } 

    public override GameInstanceIDCommandResult Execute(T command) 
    { 
     Check.Null(command, "command"); 

     GameInstance = UnitOfWork.GetGameInstance(command.GameInstanceID); 

     if (GameInstance == null) 
      return GetResult(false, "Could not find game instance."); 

     if (GameInstance.IsComplete) 
     { 
      var completeResult = GetResult(GameInstance.ID); 
      completeResult.Messages.Add("Game is complete, you cannot update the game state."); 
      completeResult.Success = false; 
      completeResult.IsGameComplete = true; 
      return completeResult; 
     } 

     UnitOfWork.LoadCollection(GameInstance, p => p.AppInstances); 

     var result = ExecuteCommand(command); 

     UnitOfWork.Save(); 

     return result; 
    } 

    protected GameInstanceIDCommandResult GetResult(bool success, string message) 
    { 
     return new GameInstanceIDCommandResult(success, message); 
    } 

    protected GameInstanceIDCommandResult GetResult(Guid id) 
    { 
     return new GameInstanceIDCommandResult(id); 
    } 

    protected void LoadGameAndGameApps() 
    { 
     UnitOfWork.LoadReference(GameInstance, p => p.Game); 
     UnitOfWork.LoadCollection(GameInstance.Game, p => p.GameApps); 
    } 

    protected abstract GameInstanceIDCommandResult ExecuteCommand(T command); 
} 

すべての基本クラスは、抽象ExecuteCommandをオーバーライドします。すべてがうまく動作し、コンソールプロジェクトを使用してエンジンを実行したり、IISやどこででもうまく動作します。

複数のコマンドが同時にゲームインスタンスの状態を変更したい場合に問題が発生します。ゲームで銀行口座の金利を計算するコマンドを呼び出した場合、現在同じコマンドを5回呼び出して5回の計算が行われます。

特定のゲームインスタンスに対して一度に1つのコマンドしか実行できないようにします。これは別のライブラリであり、IISプロセスで動作するように構築されたものではありませんので、この問題をこのファイルで処理する必要があります。私は以下の一致するコードの更新について考えた:

public abstract class GameInstanceCommandHandler<T> 
    : CommandHandlerBase<T, GameInstanceIDCommandResult> 
    where T : GameInstanceCommand 
{ 
    private static readonly object @lock = new object(); 
    private static volatile List<Guid> GameInstanceIDs = new List<Guid>(); 

    protected GameInstance GameInstance { get; private set; } 

    protected GameInstanceCommandHandler() { } 

    public GameInstanceCommandHandler(IUnitOfWork uow) 
     : base(uow) 
    { 
    } 

    public override GameInstanceIDCommandResult Execute(T command) 
    { 
     Check.Null(command, "command"); 

     lock(@lock) 
     { 
      // if game id is updating then return 
      if(GameInstanceIDs.Any(p => p == command.GameInstanceID)) 
       return GetResult(false, "The game is already being updated."); 

      // (lock for update) 
      GameInstanceIDs.Add(command.GameInstanceID); 
     } 

     try 
     { 
      GameInstance = UnitOfWork.GetGameInstance(command.GameInstanceID); 

      if (GameInstance == null) 
       return GetResult(false, "Could not find game instance."); 

      if (GameInstance.IsComplete) 
      { 
       var completeResult = GetResult(GameInstance.ID); 
       completeResult.Messages.Add("Game is complete, you cannot update the game state."); 
       completeResult.Success = false; 
       completeResult.IsGameComplete = true; 
       return completeResult; 
      } 

      UnitOfWork.LoadCollection(GameInstance, p => p.AppInstances); 

      var result = ExecuteCommand(command); 

      UnitOfWork.Save(); 
      return result; 
     } 
     catch (Exception ex) 
     { } 
     finally 
     { 
      lock (@lock) 
      { 
       GameInstanceIDs.Remove(command.GameInstanceID); 
      } 
     } 
     return GetResult(false, "There was an error."); 
    } 

    protected GameInstanceIDCommandResult GetResult(bool success, string message) 
    { 
     return new GameInstanceIDCommandResult(success, message); 
    } 

    protected GameInstanceIDCommandResult GetResult(Guid id) 
    { 
     return new GameInstanceIDCommandResult(id); 
    } 

    protected void LoadGameAndGameApps() 
    { 
     UnitOfWork.LoadReference(GameInstance, p => p.Game); 
     UnitOfWork.LoadCollection(GameInstance.Game, p => p.GameApps); 
    } 

    protected abstract GameInstanceIDCommandResult ExecuteCommand(T command); 
} 

これは完全に私の問題を修正したが、多くの問題があります。

  1. これは、私はIISでそれを実行していたとき、私は頭痛原因でしょうか?
  2. 多くのアプリケーションサーバーでこのライブラリの負荷を分散したい場合、これは機能しません。
  3. 万のゲームインスタンスがある場合、リストは膨大になり、パフォーマンスが低下します。

他の修正は、データベースにロックを移動することで、このです:

public abstract class GameInstanceCommandHandler<T> 
    : CommandHandlerBase<T, GameInstanceIDCommandResult> 
    where T : GameInstanceCommand 
{ 
    private static readonly object @lock = new object(); 

    protected GameInstance GameInstance { get; private set; } 

    protected GameInstanceCommandHandler() { } 

    public GameInstanceCommandHandler(IUnitOfWork uow) 
     : base(uow) 
    { 
    } 

    public override GameInstanceIDCommandResult Execute(T command) 
    { 
     Check.Null(command, "command"); 

     lock(@lock) 
     { 
      GameInstance = UnitOfWork.GetGameInstance(command.GameInstanceID); 

      if (GameInstance == null) 
       return GetResult(false, "Could not find game instance."); 

      if(GameInstance.IsLocked) 
       return GetResult(false, "Game is locked by another command."); 

      // Lock the game in the database or datastore 
      GameInstance.Lock(); 
      UnitOfWork.Save(); 

      // Unlock only local copy 
      GameInstance.UnLock(); 
     } 

     try 
     { 
      if (GameInstance.IsComplete) 
      { 
       var completeResult = GetResult(GameInstance.ID); 
       completeResult.Messages.Add("Game is complete, you cannot update the game state."); 
       completeResult.Success = false; 
       completeResult.IsGameComplete = true; 
       return completeResult; 
      } 

      UnitOfWork.LoadCollection(GameInstance, p => p.AppInstances); 

      var result = ExecuteCommand(command); 
      // this will unlock the gameinstance on the save 
      UnitOfWork.Save(); 
      return result; 
     } 
     catch (Exception ex) 
     { } 
     return GetResult(false, "There was an error."); 
    } 

    protected GameInstanceIDCommandResult GetResult(bool success, string message) 
    { 
     return new GameInstanceIDCommandResult(success, message); 
    } 

    protected GameInstanceIDCommandResult GetResult(Guid id) 
    { 
     return new GameInstanceIDCommandResult(id); 
    } 

    protected void LoadGameAndGameApps() 
    { 
     UnitOfWork.LoadReference(GameInstance, p => p.Game); 
     UnitOfWork.LoadCollection(GameInstance.Game, p => p.GameApps); 
    } 

    protected abstract GameInstanceIDCommandResult ExecuteCommand(T command); 
} 

おそらく、私はこれについて間違った方法を考えています。どんな助けも素晴らしいだろう。

+0

GameInstance.Lock(); UnitOfWork.Save();内部のtryブロックとGameInstance.UnLock();最終的にブロック内に。したがって、Saveを実行している例外が発生した場合、ロックは確実に解放されます。 –

+0

@SouvikGhoshありがとう、はい私はいくつかの周りにキャッチしようとします。私は私の答えを更新しました。 –

答えて

0

私は現在、IDのキューを持つことが最善の方法であると判断しました。私はすぐにゲームのインスタンスIDを入力しようとし、挿入がOKならば続行します。作業の後、リストからIDを削除します。 IDの挿入に失敗した場合は、すでに存在します。

この修正は、ロックとほぼ同じですが、ユニークなキー値ストアに依存しています。私はおそらく、これを処理するインターフェイスとクラスを作成するつもりです。データベースが失敗した場合は、後でidsをクリアするためにファイルや他の場所に失敗を格納することができます。

これは本当に悪いコードのにおいがあるので、私は間違いなくこれ以上の提案をしています。

public abstract class GameInstanceCommandHandler<T> 
    : CommandHandlerBase<T, GameInstanceIDCommandResult> 
    where T : GameInstanceCommand 
{ 
    protected GameInstance GameInstance { get; private set; } 

    protected GameInstanceCommandHandler() { } 

    public GameInstanceCommandHandler(IUnitOfWork uow) 
     : base(uow) 
    { 
    } 

    public override GameInstanceIDCommandResult Execute(T command) 
    { 
     Check.Null(command, "command"); 

     try 
     { 
      AddCommandInstance(command.GameInstanceID); 
     } 
     catch(Exception ex) 
     { 
      return GetResult(false, "Only one command can be ran on an instance at a time."); 
     } 

     GameInstance = UnitOfWork.GameInstances.FirstOrDefault(p => p.ID == command.GameInstanceID); 

     if (GameInstance == null) 
     { 
      RemoveCommandInstance(command.GameInstanceID); 
      return GetResult(false, "Could not find game instance."); 
     } 

     if (GameInstance.IsComplete) 
     { 
      var completeResult = GetResult(GameInstance.ID); 
      completeResult.Messages.Add("Game is complete, you cannot update the game state."); 
      completeResult.Success = false; 
      completeResult.IsGameComplete = true; 
      RemoveCommandInstance(command.GameInstanceID); 
      return completeResult; 
     } 

     UnitOfWork.LoadCollection(GameInstance, p => p.AppInstances); 

     GameInstanceIDCommandResult result = null; 
     try 
     { 
      result = ExecuteCommand(command); 
      UnitOfWork.Save(); 
     } 
     catch(Exception ex) 
     { 
      result = GetResult(false, ex.Message); 
     } 
     finally 
     { 
      RemoveCommandInstance(command.GameInstanceID); 
     } 
     return result; 
    } 

    private void AddCommandInstance(Guid gameInstanceID) 
    { 
     UnitOfWork.Add(new CommandInstance() { ID = gameInstanceID }); 
     UnitOfWork.Save(); 
    } 

    private void RemoveCommandInstance(Guid gameInstanceID) 
    { 
     UnitOfWork.Remove(UnitOfWork.CommandInstances.First(p => p.ID == gameInstanceID)); 
     UnitOfWork.Save(); 
    } 

    protected GameInstanceIDCommandResult GetResult(bool success, string message) 
    { 
     return new GameInstanceIDCommandResult(success, message); 
    } 

    protected GameInstanceIDCommandResult GetResult(Guid id) 
    { 
     return new GameInstanceIDCommandResult(id); 
    } 

    protected void LoadGameAndGameApps() 
    { 
     UnitOfWork.LoadReference(GameInstance, p => p.Game); 
     UnitOfWork.LoadCollection(GameInstance.Game, p => p.GameApps); 
    } 

    protected abstract GameInstanceIDCommandResult ExecuteCommand(T command); 
} 
+0

私は何を考えていたのですか?独立したスレッドでロックブロックを個別に実行しようとすることができます。共有リソースに影響を与える可能性がありますが、それを処理する方法があります。これにより、新しいスレッドが独立して動作するたびに、メールスレッドは空きになります。スレッドプールも作成する必要があります。 –

関連する問題