public ServerMiddleware(Store <PlayerData> store, IStoreService client, StoreLocalPersister persister) { _store = store; _persistedStore = new Store <PlayerData>(store.State, store.Modifiers); _client = client; _persister = persister; _hasher = SHA256.Create(); _channel = Channel.CreateUnbounded <ActionEvent>(new UnboundedChannelOptions() { SingleReader = true, SingleWriter = true }); _nextId = 0; Task.Run(async() => { while (await _channel.Reader.WaitToReadAsync()) { while (_channel.Reader.TryRead(out var e)) { // TODO: handle timeout if no server response. var response = await _client.SendEvent(e); if (response.Id == e.Id) { switch (response.Status) { case EventStatus.Accepted: // persist to local storage. _persistedStore.Update(e.Action); _persister.Set <PlayerData>("player", _persistedStore.State); _persister.Save(); break; case EventStatus.Rejected: // discard remaining transactions in channel and revert to last valid store state. break; case EventStatus.Resend: // resend last event. break; default: break; } } else { Console.WriteLine("Invalid Id = " + response.Id); } } } }); }
static async Task Main(string[] args) { MagicOnion.MagicOnionInitializer.Register(); RegisterResolvers(); // var channel = new Channel("35.228.244.163", 80, ChannelCredentials.Insecure); // gcp load balancer external ip and port. var channel = new Channel("0.0.0.0", 8080, ChannelCredentials.Insecure); // local dev server. var client = MagicOnionClient.Create <IStoreService>(channel); const int kDefaultStartCoins = 5000; var gameConfig = new GameConfig(); var configProvider = new ConfigProvider(); configProvider.Add("default", gameConfig); var store = new Store <Models.PlayerData>(); var config = new StoreLocalPersister.Config { FilePath = "LocalData/Player.txt", TextFormat = true }; var persister = new StoreLocalPersister(config); var server = new ServerMiddleware(store, client, persister); var history = new HistoryMiddleware(); store.AddMiddlewares(server, history); var guest = new GuestModifierProvider(new GuestModifierFactory(configProvider)); var modifiers = new List <Modifier <Models.PlayerData> >(); modifiers.AddRange(Currency.Modifiers); modifiers.AddRange(Inventory.Modifiers); modifiers.AddRange(Hotel.Modifiers); modifiers.AddRange(guest.Modifiers); store.AddModifiers(modifiers.ToArray()); // store.Select().Subscribe(x => Console.WriteLine(x)); store.Select(x => x.Stats.Coins).Subscribe(x => Console.WriteLine("Coins: " + x)); store.Select(x => x.Inventory.Items).Subscribe(x => Console.WriteLine("Decos: " + (x == null ? "Empty" : x.Sum(x => x.Value.Count).ToString()))); // store.Select(x => x.Hotel.Rooms).Subscribe(x => x.ForEach(r => Console.WriteLine($"Room[{r.TypeId}] => Level {r.Level}"))); store.Select(x => x.Hotel.Rooms).Subscribe(x => { if (x == null) { Console.WriteLine("Rooms: Empty"); } else { x.ForEach(r => Console.WriteLine($"Room[{r.TypeId}] => Level {r.Level}")); } }); store.Update(new NewGameAction { StartCoins = kDefaultStartCoins }); var app = new Game(store); app.Command("newgame", config => { var coins = config.Option("--coins", "Starting coins", CommandOptionType.SingleValue); config.OnExecute(() => { if (!int.TryParse(coins.Value(), out int val)) { val = kDefaultStartCoins; } store.Update(new NewGameAction { StartCoins = val }); }); }); app.Command("buy", config => { var typeIdArg = config.Argument("TypeId", "Item TypeId"); var costArg = config.Argument("Cost", "Item Cost"); config.OnExecute(() => { int.TryParse(typeIdArg.Value, out int typeId); int.TryParse(costArg.Value, out int cost); store.Update(new BuyDecoAction { TypeId = typeId, Cost = cost }); }); }); app.Command("room", config => { config.Command("up", sub => { const int kUpgradeRoomCost = 100; var typeIdArg = sub.Argument("TypeId", "Item TypeId"); sub.OnExecute(() => { int.TryParse(typeIdArg.Value, out int typeId); store.Update(new UpgradeRoomAction { TypeId = typeId, Cost = kUpgradeRoomCost }); }); }); }); app.AddCommand <int, int>("guest", "cin", (guestId, roomId) => new GuestCheckinAction { GuestTypeId = guestId, RoomTypeId = roomId }); app.AddCommand <int>("guest", "cout", (guestId) => new GuestCheckoutAction { GuestTypeId = guestId }); app.AddCommand("hotel", "status", () => ShowHotelStatus(store)); app.AddAsyncCommand("sync", "new", async() => { var t = new ActionEvent { Id = 1, Hash = "abc", Action = new NewGameAction { StartCoins = 250 } }; var result = await client.SendEvent(t); Console.WriteLine("sync result = " + result); }); app.AddCommand("sync", "test", () => { var t = new ActionEvent { Id = 1, Hash = "abc", Action = new NewGameAction { StartCoins = 250 } }; var bytes = MessagePackSerializer.Serialize(t); var json = MessagePackSerializer.ToJson(bytes); var result = MessagePackSerializer.Deserialize <ActionEvent>(bytes); var newGame = result.Action as NewGameAction; Console.WriteLine("sync result = " + json + " => " + result.ToString()); }); app.AddCommand("data", "save", () => { persister.Set <Models.PlayerData>("player", store.State); persister.Save(); }); app.AddCommand("data", "load", () => { persister.Load(); persister.Get <Models.PlayerData>("player", out var player); store.Update(new LoadGameAction { State = player }); }); app.Command("timer", config => { config.OnExecute(() => { var start = DateTime.Now; var prev = start; Console.Write("Timer: " + 0); Console.CursorVisible = false; while (!Console.KeyAvailable) { var now = DateTime.Now; double dt = (now - prev).TotalSeconds; if (dt >= 1.0) { prev = now; Console.SetCursorPosition(0, Console.CursorTop); Console.Write("Timer: " + (int)(now - start).TotalSeconds); } } Console.ReadKey(true); Console.WriteLine(); }); }); while (true) { var command = Prompt.GetString("Command", "#"); if (command == "bye") { Console.WriteLine("bye!"); break; } var tokens = command.Split(" "); try { await app.ExecuteAsync(tokens); } catch (Exception e) { Console.WriteLine($"{e.GetType()}: {e.Message}"); Console.WriteLine(e.StackTrace); } } }