question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

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 issue

When 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

  1. Change IGameStorage IEnumerable<IEntity> ReadEntities(); to Task<IEnumerable<IEntity>> ReadEntitiesAsync();
  2. Change IGameBoard void GameBoard.Initialize(...) to Task GameBoard.InitializeAsync(...)
  3. Move the call to GameBoard.Initialize() out of the constructor and into a new method InitializeAsync
  4. Change Index.razor.cs protected override void OnInitialized() to protected override Task OnInitializedAsync()
  5. After ServiceLocating _game, call _game.InitializeAsync()
  6. 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

  1. Move the Save logic out of GameBoard.Dispose() and into a Save or SaveAsync() method.
  2. 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:

  1. App Starts
  2. Source generated Static field services are initialized
  3. As part of Game being initialized it tries to locate LocalStorageService using ASP.Net Core IOC, but it is null
  4. BlazorGameStorage is resolved and gets assigned to AspNetCoreServices. I don’t really know what this means but there’s a comment in Program.cs
  5. 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:closed
  • Created 2 years ago
  • Comments:9 (9 by maintainers)

github_iconTop GitHub Comments

1reaction
davidwengiercommented, Nov 5, 2021

Does having a “yield” keyword anywhere in the method change how the rest of it works? A bit like how making a method async changes everything?

Yes, exactly right.

1reaction
davidwengiercommented, Nov 4, 2021

Oh, there is a yield break there too. Change that to “return null” and it’ll work

Read more comments on GitHub >

github_iconTop Results From Across the Web

Managing workflow lifecycle without a "persistence service"
In my app, I implemented the WorkflowRuntime as a singleton because I found that there was a huge memory leak when I created...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found