私はゲームエンジンを開発しています。 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);
}
これは完全に私の問題を修正したが、多くの問題があります。
- これは、私はIISでそれを実行していたとき、私は頭痛原因でしょうか?
- 多くのアプリケーションサーバーでこのライブラリの負荷を分散したい場合、これは機能しません。
- 万のゲームインスタンスがある場合、リストは膨大になり、パフォーマンスが低下します。
他の修正は、データベースにロックを移動することで、このです:
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);
}
おそらく、私はこれについて間違った方法を考えています。どんな助けも素晴らしいだろう。
GameInstance.Lock(); UnitOfWork.Save();内部のtryブロックとGameInstance.UnLock();最終的にブロック内に。したがって、Saveを実行している例外が発生した場合、ロックは確実に解放されます。 –
@SouvikGhoshありがとう、はい私はいくつかの周りにキャッチしようとします。私は私の答えを更新しました。 –