Persistence doesn't work in Blazing Trains due to use of task.getawaiter, the unreliable WindowUnload event and the lifecycle of service creation.
See original GitHub issueWhen playing Blazing trains I want to be able to save the train track so that if I close the browser, when I visit the game again my previous track is still there.
This already works in the WPF version, and it looks like 99% of the code is there for it to run in Blazor but it doesn’t work.
I’m happy to send a PR, but I’m not sure if my approach to async refactoring is to your liking so I’d like your opinion first.
Asynchronous Loading
In Blazor Game storage the async method LocalStorageService.GetItemAsync
is called using .GetAwaiter().GetResult()
which fails to run because the WASM run time does not support .GetAwaiter() because it is single threaded.
The best solution for this is to refactor things so that the calls to LocalStorageService happen in async methods, but the call is a long way down a call stack which ultimately starts with the Game
field initializer in ServiceLocator.
Proposed Solution
- Change IGameStorage
IEnumerable<IEntity> ReadEntities();
toTask<IEnumerable<IEntity>> ReadEntitiesAsync();
- Change IGameBoard
void GameBoard.Initialize(...)
toTask GameBoard.InitializeAsync(...)
- Move the call to
GameBoard.Initialize()
out of the constructor and into a new methodInitializeAsync
- Change Index.razor.cs
protected override void OnInitialized()
toprotected override Task OnInitializedAsync()
- After ServiceLocating _game, call
_game.InitializeAsync()
- Find an appropriate place in WPF and WinForms apps to call
_game.InitializeAsync()
This may seem a bit drastic, and it requires changes to other platforms, but even in the windows versions of Trains, it probably wasn’t ideal to be doing File I/O on the main thread from field initializers so this should be a win all round, even if it’s insignificant.
Save not running because WindowUnload is limited
In Index.razor.cs you subscribe to the WindowUnload even which in turn calls GameBoard.Dispose();
which saves. WindowUnload is not a very useful place to make calls because:
- It is only called with some kinds of navigation, not when you close the browser
- You only get a very short period of time to exectute code, and it can’t be asynchronous
The async/sync boarder is a littly fuzzy here. I think the async call that would fail would be when JS calls C#, If not it’s probably when you call _ = ...SetItemAsync()
with a fire and forget in Blazor Storage.
Proposed Solution
- Move the Save logic out of GameBoard.Dispose() and into a Save or SaveAsync() method.
- Call Save() from somewhere in the Blazor Code. This could be triggered by a save button in the UI, a timer or after every time the user modifies the entities.
Optionally we could also refactor all the levels of saving to happen in async methods. I’m not sure if this actually gains us anything useful but as a rule I like to have unawaited anyc calls happening as close to the top of the call stack as possible, but changing this would mean chaning all platforms.
Local Storage Service is null if called from Game Constructor but works on subsequent call.
I’m not sure exactly what’s going on here but I have a theory:
- App Starts
- Source generated Static field services are initialized
- As part of Game being initialized it tries to locate LocalStorageService using ASP.Net Core IOC, but it is null
- BlazorGameStorage is resolved and gets assigned to AspNetCoreServices. I don’t really know what this means but there’s a comment in Program.cs
- Subsequent calls (added by me, triggered in UI) can succesfully locate LocalStorageService
Proposed Solution
Do nothing. The refactoring to fix the asynchronous loading will fix this because we’ll no longer be trying to call the storage service from a constructor.
Alternatively we could probably restructure the lifecycle of creating services, but I’m not sure how to do that cleanly, I think Blazored.LocalStorageServices likes to be run with the Asp.Net Core IOC and I’m not sure how to get it running with your source generator IOC.
Issue Analytics
- State:
- Created 2 years ago
- Comments:9 (9 by maintainers)
Top GitHub Comments
Yes, exactly right.
Oh, there is a yield break there too. Change that to “return null” and it’ll work