joshuaauerbachwatson / unigame

The core client layer for apps that use the unigame server, covering the game-agnostic aspects
0 stars 0 forks source link

Work out relationship between `UnigameModel` and `GameHandle` #22

Open joshuaauerbachwatson opened 1 week ago

joshuaauerbachwatson commented 1 week ago

As it stands now, the unigame model points to the app-specific game handle but not vice versa. So, it is the unigame model that is generally passed through the environment and the app accesses its game handle from there.

As it stand now, the unigame model stores any GameHandle so a cast is always needed to get to the app-specific definition. This cast is not only a pain to manage but it interferes with SwiftUI binding logic.

I've tried making the UnigameModel generic so that it can have a GameHandle of known type. This might be possible to work out but as I started working it out I found it leads to lots of complications. At the unigame level, it needs to be instantiated with DummyGameHandle but at app level, of course, the specific handle must be used. I could come back to this design if there is nothing better.

If it were the app-specific GameHandle that was in the environment and the navigation worked the other way, some things would be better, but the unigame model still needs a reference to its GameHandle, so I would either have to use a weak reference or I would end up with a strong cycle. We don't really want the model or handle to be short-lived and repeatedly allocated, so perhaps a strong cycle is ok and, anyway, a weak reference is no big deal. However, it seems a bit weird that we have to go to this extent to get the relationship right.

Another possible design is for the app-specific model to simply subclass UnigameModel. The methods that UnigameModel now calls in the game handle would be there in pseudo-abstract ("fatal error") form, and would have to be overridden. The class would be open rather than public final.

I kind of like the third solution. It might be that we could just ditch the DummyGameHandle and leave the fatal error methods in place since, in a preview situation, they would not be called and there is no meaningful way to run unigame all by itself.

joshuaauerbachwatson commented 1 week ago

The problem with the third solution is that the UnigameModel is retrieved by class name from the environment by many views that are part of the unigame layer. Only views that are part of the specific app will want the subtype. But, if observed objects are placed in the environment keyed by their type, then both types need to be in the environment.

In fact, this probably means the idea of making UnigameModel generic also won't work. The unigame views won't be able to retrieve the object not knowing its precise type.

Some things to note.

  1. For the unigame layer, it is sufficient that the UnigameModel is in the environment and that that model object has access to a game-specific implementation of GameHandle (stored as any GameHandle).
  2. For the app layer, access is needed both to UnigameModel and to the app's own model. Both objects must be put into the environment when the app initializes. So, navigation from one to the other doesn't really buy the app layer anything.
  3. The app's model, per se, need not implement GameHandle as long as some type specific to the app does implement it.

I will keep this open as a unigame issue but I think all of the real work to address it must be done in the higher layer.