unity – How to connect the Data Layer (Models) with the Visual/Interactive Layer (MonoBehaviours)?

I have been struggling with this dilemma for quite a while already.

Our game is complex enough that we have tons of logic in normal C# scripts that function very independently of any Unity specific component.

This logic is eventually modifying the Data Layer which is basically a batch of simple Classes with state that we call Models.

Everything works fine until we have to connect this Layer with the visualization layer (aka Unity MonoBehaviours)

These are the approaches I have tried (they all work but I am not totally satisfied with any)

Using a MonoBehaviourWrapper

I have a MonoBehaviour class with only one property:

(SerializeField) DataModel _dataModel;

I call it ModelMonoBehaviourWrapper

I have a GameObject in the Scene to visualize the state of the Model I call it ModelView

I have a script that listens to changes in the Model and modifies the ModelView. I call it ModelViewController. This requires access to the Model to listen to changes so it has this property:

(SerializeField) ModelMonoBehaviourWrapper _modelMonoBehaviourWrapper;

I attach ModelViewController to the ModelView GameObject as component (The property _modelMonoBehaviourWrapper remains unlinked at this moment)

I have a GameObject in the Scene as a holder of all the internal logic. I call it SceneManager. This GameObject has an script attached called SceneManagerController who is in charge of running the Game. This script has, therefore, access to the Data Models. This script has also this property:

(SerializeField) ModelMonoBehaviourWrapper _modelMonoBehaviourWrapper;

I attach a ModelMonoBehaviourWrapper Script as a component to SceneManager, then I link this Component to the property _modelMonoBehaviourWrapper in both: ModelViewController on ModelView and SceneManagerController in SceneManager

This way I manage to have the same Model instance connected in ModelViewController and SceneManagerController.

I initialize ModelMonoBehaviourWrapper._dataModel through SceneManagerController and I can read it from ModelViewController.

If you think this is over-messy I also think the same. There are also some initialization race condition problems, for example if you are trying to access to the Model (through ModelMonoBehaviourWrapper) in ModelViewController before SceneManagerController has initialized it. If you are careful it works though.

Here you have a diagram trying to represent this structure:

Eternal Quest - Connecting Data Layer and Display Layer (Through ModelMonoBehaviourWrapper)

Using a public static pointer to SceneManagerController

In a similar structure as above but instead of using the ModelMonoBehaviourWrapper to make the Model accessible in both Controllers I make the SceneManagerController public and static like this:

public static SceneManagerController Instance;

This way I can access to the Model from the ModelViewController like this:

SceneManagerController.Instance.GetModel();

This works, but I am concerned about the strong counter-intuitive dependency generated here. I would like ModelViewController to be depended only in the Model but now it is depending on this intermediate class SceneManagerController just to have access to the model

Diagram:

Eternal Quest - Connecting Data Layer and Display Layer (The static SceneManagerController)

Using the SceneManagerController as a Initializer Orchestrator

The SceneManagerController can contain references to all elements, not only the Data Models (that it already has), but also to all Visual GameObjects like:

(SerializeField) GameObject ModelView;

This way SceneManagerController can instantiate the ModelViewController and give it access to the Model and to the View element.

This way I keep ModelViewController only dependent to the Model. I am adding all references to the SceneManagerController what is even helpful because this way I have to only wire Component Properties in one GameObject.

I am playing with this structure right now. The problem I see is that now the SceneManagerController becomes too much dependent in the set up of the Scene and I can not Add/Remove Scene elements easily because they are not self-managed as it is the case in the previous above architectures.

Diagram:

Eternal Quest - Connecting Data Layer and Display Layer (SceneManagerController as an Orchestrator)

I would like to know, from people with experience in similar situations, how you have solved it and what solutions you have found more solid and maintainable.

Update 1

Answering to some questions in the comments:

Are you using or aware of RX?

No, I am reading now:

It looks like nice solutions to have code reacting to changes in a Model. We are solving this problem with basic EventHandlers..

But even if I integrate RX, if I understood it properly, I will have the same problem: how can I share a Model instance between my DataLayer/GameLogicLayer (where the instance has been created) and the VisualLayer (MonoBehviour scripts in the Scene GameObjects, where the the scripts are listening to changes in the instance?

Is there specific reason for data layer being owned by a game object, as opposed to a program itself?

Not necessarily, a part of having things separate. I am ok of having a DataManager storing all the Models.

Is DI/IoC used for data layer?

Yes we have different level of DI/IoC implementations

What sort of component is responsible for creating data-layer-game-object?

The Models instances are created in the DataManager.cs which acts as a factory. But they are owned by the GameScripts that needed to create them.

Is there a controller on a data layer to communicate with a scene/engine?

DataManager.cs has static public methods to instantiate Models. These methods can be accessed by the Scene/Engine scripts