2016-10-20 68 views
1

我正在開發一款遊戲引擎。一個angularjs前端與在IIS上運行的.NET WebAPI項目中的端點對話。這個web api是一個外觀,只是運行命令反對在一個單獨的項目中的遊戲領域邏輯。我有一個目前正在使用實體框架的工作單元。如何在更新對象的狀態時使命令同步?

因此,angularjs代碼在IIS上的webapi網站中調用一個端點,該端點會創建一個針對遊戲運行的命令。例如:開立銀行賬戶。

該命令將從數據存儲中加載遊戲實例,檢查完成並執行命令以打開銀行帳戶。執行命令中會發生很多事件,這些事件會更改遊戲實例數據。

我有很多這些命令都具有相同的基類,但只有一個應該一次被調用。

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次計算。

我想確保只有一個命令允許一次執行給定的遊戲實例。由於這是一個單獨的庫,並不僅僅是爲了在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();裏面終於擋住了。因此,如果有任何異常情況發生,保存鎖定將被釋放。 –

+0

@SouvikGhosh謝謝你,是的,我會試一試。我已經更新了我的答案。 –

回答

0

我已經決定目前最好的方法是有一個ID隊列。我嘗試立即輸入遊戲實例ID,如果插入是OK,則繼續。做完工作後,從列表中刪除該ID。如果ID插入失敗,那麼它已經存在。

此修復與鎖相差無幾,但我依賴的是唯一的鍵值存儲。我可能會創建一個接口和類來處理這個問題,所以如果數據庫失敗,我可以將失敗存儲在一個文件或其他地方,以確保在稍後清除id。

我絕對有更多的建議,因爲它確實有一個不好的代碼味道。

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

我在想什麼 - 你可以嘗試獨立地在一個單獨的線程中執行鎖定塊。它可能會影響共享資源,但有辦法照顧。因此,這將保持郵件線程免費,每次一個新的線程將獨立工作。您可能還必須創建一個線程池。 –

相關問題