Example #1
0
        /// <summary>
        /// Funcion available to overide to include more networkobjects that can be erased and undone.
        /// </summary>
        /// <param name="netObj"></param>
        public virtual void TryAndErase(NetworkedGameObject netObj)
        {
            var entityID = entityManager.GetComponentData <NetworkEntityIdentificationComponentData>(netObj.Entity).entityID;

            //draw functionality
            if (entityManager.HasComponent <DrawingTag>(netObj.Entity))
            {
                //turn it off for ourselves and others
                netObj.gameObject.SetActive(false);

                // when actions of erasing are being captured, the curStrokepos and curColor will both be set to 0.
                DrawingInstanceManager.Instance.SendStrokeNetworkUpdate(entityID, Entity_Type.LineNotRender);

                //save our reverted action for undoing the process with the undo button
                if (UndoRedoManager.IsAlive)
                {
                    UndoRedoManager.Instance.savedStrokeActions.Push(() =>
                    {
                        netObj.gameObject.SetActive(true);

                        DrawingInstanceManager.Instance.SendStrokeNetworkUpdate(entityID, Entity_Type.LineRender);
                    }
                                                                     );
                }
            }
        }
Example #2
0
 /// <summary>
 /// Place GameObject in update loop to send network information across
 /// </summary>
 /// <param name="nRO attached to an objects to keep network relevant data"></param>
 public void PlaceInNetworkUpdateList(NetworkedGameObject nRO)
 {
     if (!entityContainers_InNetwork_OutputList.Contains(nRO))
     {
         entityContainers_InNetwork_OutputList.Add(nRO);
     }
 }
Example #3
0
 /// <summary>
 /// Remove GameObject from update loop to stop sending network information
 /// </summary>
 /// <param name="netObject attached to an objects to keep network relevant data"></param>
 public void RemoveUpdatable(NetworkedGameObject netObject)
 {
     if (networkedEntities.Contains(netObject))
     {
         networkedEntities.Remove(netObject);
     }
 }
Example #4
0
 /// <summary>
 /// Place GameObject in update loop to send network information across
 /// </summary>
 /// <param name="netObject attached to an objects to keep network relevant data"></param>
 public void AddUpdatable(NetworkedGameObject netObject)
 {
     if (!networkedEntities.Contains(netObject))
     {
         networkedEntities.Add(netObject);
     }
 }
Example #5
0
 /// <summary>
 /// Remove GameObject from update loop to stop sending network information
 /// </summary>
 /// <param name="nRO attached to an objects to keep network relevant data"></param>
 public void RemoveFromInNetworkUpdateList(NetworkedGameObject nRO)
 {
     if (entityContainers_InNetwork_OutputList.Contains(nRO))
     {
         entityContainers_InNetwork_OutputList.Remove(nRO);
     }
 }
Example #6
0
        public void CreateExternalClientStrokeInstance(int strokeID, LineRenderer currentLineRenderer)
        {
            GameObject pivot = new GameObject("LineRender:" + strokeID, typeof(BoxCollider));

            NetworkedGameObject nAGO = NetworkedObjectsManager.Instance.CreateNetworkedGameObject(pivot, strokeID, strokeID, true);

            //overide interactable tag when creatingNetworkGameObject since we are not moving drawings only deleting them
            pivot.tag = TagList.drawing;
            //tag created drawing object will be useful in the future for having items with multiple tags
            entityManager.AddComponentData(nAGO.Entity, new DrawingTag {
            });

            var bColl = pivot.GetComponent <BoxCollider>();

            Bounds newBounds = new Bounds(currentLineRenderer.GetPosition(0), Vector3.one * 0.01f);

            for (int i = 0; i < currentLineRenderer.positionCount; i++)
            {
                newBounds.Encapsulate(new Bounds(currentLineRenderer.GetPosition(i), Vector3.one * 0.01f));
            }

            pivot.transform.position = newBounds.center;
            bColl.center             = currentLineRenderer.transform.position;
            bColl.size = newBounds.size;

            currentLineRenderer.transform.SetParent(pivot.transform, true);

            pivot.transform.SetParent(externalStrokeParent, true);
        }
        public void ApplyCatchup()
        {
            if (!UIManager.IsAlive)
            {
                Debug.LogWarning("Tried to process session state for lock and visibility, but there was no UIManager.");
            }

            if (!SceneManagerExtensions.IsAlive)
            {
                Debug.LogWarning("Tried to process session state for scene, but there was no SceneManagerExtensions.");
            }

            if (!ClientSpawnManager.IsAlive)
            {
                Debug.LogWarning("Tried to process session state for clients, but there was no ClientSpawnManager.");
            }

            if (_state == null)
            {
                Debug.LogWarning("SessionStateManager: state was null. State catch-up will not be applied.");

                return;
            }

            SceneManagerExtensions.Instance.SelectScene(_state.scene);

            ClientSpawnManager.Instance.AddNewClients(_state.clients);

            foreach (EntityState entityState in _state.entities)
            {
                NetworkedGameObject netObject = GetNetObjectFromEntityState(entityState);

                if (netObject == null)
                {
                    Debug.LogWarning($"Could not catch up state for entity with ID {entityState.id}. Skipping.");

                    continue;
                }

                UIManager.Instance.ProcessNetworkToggleVisibility(netObject.thisEntityID, entityState.render);

                int interactionType = entityState.locked ? (int)INTERACTIONS.LOCK : (int)INTERACTIONS.UNLOCK;

                ApplyInteraction(new Interaction(
                                     sourceEntity_id: -1,
                                     targetEntity_id: entityState.id,
                                     interactionType: interactionType
                                     ));

                ApplyPosition(entityState.latest);
            }
        }
Example #8
0
        public void LinkNetObjectToButton(int entityID, NetworkedGameObject netObject)
        {
            if (entityManager.HasComponent <ButtonIDSharedComponentData>(netObject.Entity))
            {
                var buttonID = entityManager.GetSharedComponentData <ButtonIDSharedComponentData>(netObject.Entity).buttonID;

                if (buttonID < 0 || buttonID >= ModelImportInitializer.Instance.networkedGameObjects.Count)
                {
                    throw new System.Exception("Button ID value is out-of-bounds for networked objects list.");
                }

                ModelImportInitializer.Instance.networkedGameObjects[buttonID] = netObject;
            }
        }
Example #9
0
        /// <summary>
        /// A call to remove Physics funcionality from specified netObject
        /// </summary>
        /// <param name="eContainer"></param>
        public void StopPhysicsUpdates(NetworkedGameObject eContainer)
        {
            Position coords = default;

            NetworkEntityIdentificationComponentData entityIDContainer = entityManager.GetComponentData <NetworkEntityIdentificationComponentData>(eContainer.Entity);

            coords = new Position
            {
                clientId   = entityIDContainer.clientID,
                entityId   = entityIDContainer.entityID,
                entityType = (int)Entity_Type.physicsEnd,
            };

            coordExport.Invoke(coords);
        }
        //pick up our closest collider and obtain its references
        private Transform GetNearestUnlockedNetObject(Collider[] colliders)
        {
            float minDistance = float.MaxValue;

            float distance;

            Collider nearestTransform = null;

            foreach (Collider col in colliders)
            {
                if (!col.CompareTag(TagList.interactable))
                {
                    continue;
                }

                if (!col.gameObject.activeInHierarchy)
                {
                    continue;
                }

                distance = (col.ClosestPoint(thisHandTransform.position) - thisHandTransform.position).sqrMagnitude;

                if (distance > 0.01f)
                {
                    continue;
                }

                if (distance < minDistance)
                {
                    minDistance = distance;

                    nearestTransform = col;
                }
            }

            if (nearestTransform == null)
            {
                return(null);
            }

            currentGrabbedObjectRigidBody = null;

            currentGrabbedNetObject = null;

            SetNearestParentWhenChangingGrabbingHand(nearestTransform);

            return(GetUnlockedNetworkedObjectTransformIfExists(nearestTransform));
        }
        private EntityState GetEntityStateFromNetObject(NetworkedGameObject netObject)
        {
            int desiredEntityId = entityManager.GetComponentData <NetworkEntityIdentificationComponentData>(netObject.Entity).entityID;

            foreach (var candidateEntityState in _state.entities)
            {
                if (candidateEntityState.id == desiredEntityId)
                {
                    return(candidateEntityState);
                }
            }

            Debug.LogError($"SessionStateManager: Could not find EntityState that matched netObject with entity ID {desiredEntityId}");

            return(new EntityState());
        }
Example #12
0
        protected NetworkedGameObject InstantiateNetworkedGameObject(NetworkedGameObject netObject, int entityId, int modelListIndex)
        {
            //to enable only imported objects to be grabbed
            //TODO: change for drawings
            netObject.tag = TagList.interactable;

            //We then set up the data to be used through networking
            if (entityId == 0)
            {
                netObject.Instantiate(modelListIndex);

                return(netObject);
            }

            netObject.Instantiate(modelListIndex, entityId);

            return(netObject);
        }
        private Transform GetUnlockedNetworkedObjectTransformIfExists(Collider nearestTransform)
        {
            if (nearestTransform.TryGetComponent(out NetworkedGameObject netObj))
            {
                if (entityManager.HasComponent <TransformLockTag>(netObj.Entity))
                {
                    // TODO(Brandon) -- instead of returning null here, keep searching for the next rigid body. Otherwise, we trap smaller, unlocked objects inside larger, locked objects.

                    return(null);
                }

                currentGrabbedNetObject = netObj;

                InitializeNetworkedPhysicsObjectIfNeeded();
            }

            return(nearestTransform.transform);
        }
Example #14
0
        public void SendVisibilityUpdate(int index, bool doShow)
        {
            GameObject gObject = NetworkedObjectsManager.Instance.GetNetworkedGameObject(index).gameObject;

            NetworkedGameObject netObject = gObject.GetComponent <NetworkedGameObject>();

            if (!netObject)
            {
                Debug.LogError("no NetworkedGameObject found on currentObj in ClientSpawnManager.cs");

                return;
            }

            int entityID = entityManager.GetComponentData <NetworkEntityIdentificationComponentData>(netObject.Entity).entityID;

            if (doShow)
            {
                NetworkUpdateHandler.Instance.SendSyncInteractionMessage(new Interaction
                {
                    sourceEntity_id = NetworkUpdateHandler.Instance.client_id,
                    targetEntity_id = entityID,
                    interactionType = (int)INTERACTIONS.SHOW,
                });
            }
            else
            {
                NetworkUpdateHandler.Instance.SendSyncInteractionMessage(new Interaction
                {
                    sourceEntity_id = NetworkUpdateHandler.Instance.client_id,
                    targetEntity_id = entityID,
                    interactionType = (int)INTERACTIONS.HIDE,
                });
            }

            //TODO(Brandon): what is this code for?
            try
            {
                Entity currentEntity = NetworkedObjectsManager.Instance.GetEntity(index);
            }
            catch (Exception e)
            {
                Debug.LogWarning($"Tried to get entity with index {index}. Error: {e.Message}");
            }
        }
Example #15
0
        /// <summary>
        /// used to turn on models that were setup with SetUp_ButtonURL.
        /// </summary>
        /// <param name="index"></param>
        /// <param name="button"></param>
        /// <param name="sendNetworkCall is used to determine if we should send a call for others to render the specified object"></param>
        public void ToggleModelVisibility(int index, bool activeState)
        {
            GameObject gObject = clientManager.GetNetworkedGameObject(index).gameObject;

            NetworkedGameObject netObject = gObject.GetComponent <NetworkedGameObject>();

            if (!netObject)
            {
                Debug.LogError("no NetworkedGameObject found on currentObj in ClientSpawnManager.cs");

                return;
            }

            int entityID = entityManager.GetComponentData <NetworkEntityIdentificationComponentData>(netObject.Entity).entityID;

            Entity currentEntity = clientManager.GetEntity(index);

            if (activeState)
            {
                gObject.SetActive(true);

                NetworkUpdateHandler.Instance.InteractionUpdate(new Interaction
                {
                    sourceEntity_id = NetworkUpdateHandler.Instance.client_id,
                    targetEntity_id = entityID,
                    interactionType = (int)INTERACTIONS.RENDERING,
                });
            }
            else
            {
                gObject.SetActive(false);

                //if (GameStateManager.Instance.useEntityComponentSystem)
                //    if (currentEntity != Entity.Null)
                //        entityManager.SetEnabled(currentEntity, false);

                NetworkUpdateHandler.Instance.InteractionUpdate(new Interaction
                {
                    sourceEntity_id = NetworkUpdateHandler.Instance.client_id,
                    targetEntity_id = entityID,
                    interactionType = (int)INTERACTIONS.NOT_RENDERING,
                });
            }
        }
Example #16
0
        /// <summary>
        /// Meant to convert our Physics GameObject data send  data to follow our POSITION struct to be sent each update
        /// </summary>
        /// <param name="Net_Register_GameObject container of data"></param>
        public void SendPhysicsGameObjectUpdatesToNetwork(NetworkedGameObject eContainer)
        {
            int entityID = default;
            NetworkEntityIdentificationComponentData entityIDContainer = default;

            entityIDContainer = entityManager.GetComponentData <NetworkEntityIdentificationComponentData>(eContainer.Entity);
            entityID          = entityIDContainer.entityID;

            //make sure that we setup the reference to our rigidBody of our physics object that we are using to send data from
            if (!ClientSpawnManager.Instance.rigidbodyFromEntityId.ContainsKey(entityID))
            {
                ClientSpawnManager.Instance.rigidbodyFromEntityId.Add(entityID, eContainer.GetComponent <Rigidbody>());
            }
            var rb = ClientSpawnManager.Instance.rigidbodyFromEntityId[entityID];

            if (!rb)
            {
                Debug.LogError("There is no rigidbody in netobject entity id DICTIONARY: " + entityID);
                return;
            }

            Position coords = default;

            if (!rb.isKinematic && rb.IsSleeping() || entityManager.HasComponent <TransformLockTag>(eContainer.Entity))
            {
                physicsnRGOToRemove.Add(eContainer);

                //Send a last update for our network objects to be remove their physics funcionality to sync with others.
                StopPhysicsUpdates(eContainer);
            }

            coords = new Position
            {
                clientId    = entityIDContainer.clientID,
                entityId    = entityIDContainer.entityID,
                entityType  = (int)entityIDContainer.current_Entity_Type,
                rot         = eContainer.transform.rotation,
                pos         = eContainer.transform.position,
                scaleFactor = eContainer.transform.lossyScale.x,
            };

            coordExport.Invoke(coords);
        }
Example #17
0
        /// <summary>
        /// Allows ClientSpawnManager have reference to the network reference gameobject to update with calls
        /// </summary>
        /// <param name="gObject"></param>
        /// <param name="modelListIndex"> This is the model index in list</param>
        /// <param name="customEntityID"></param>
        public NetworkedGameObject CreateNetworkedGameObject(GameObject gObject, int modelListIndex = -1, int customEntityID = 0, bool doNotLinkWithButtonID = false)
        {
            //add a Net component to the object
            NetworkedGameObject netObject = gObject.AddComponent <NetworkedGameObject>();

            //to look a decomposed set of objects we need to keep track of what Index we are iterating over regarding or importing models to create sets
            //we keep a list reference for each index and keep on adding to it if we find a model with the same id
            //make sure we are using it as a button reference
            if (doNotLinkWithButtonID)
            {
                return(InstantiateNetworkedGameObject(netObject, customEntityID, modelListIndex));
            }

            if (modelListIndex == -1)
            {
                return(InstantiateNetworkedGameObject(netObject, customEntityID, modelListIndex));
            }

            List <NetworkedGameObject> subObjects;

            Dictionary <int, List <NetworkedGameObject> > netSubObjectLists = Instance.networkedSubObjectListFromIndex;

            if (!netSubObjectLists.ContainsKey(modelListIndex))
            {
                subObjects = new List <NetworkedGameObject>();

                subObjects.Add(netObject);

                netSubObjectLists.Add(modelListIndex, subObjects);

                return(InstantiateNetworkedGameObject(netObject, customEntityID, modelListIndex));
            }

            subObjects = Instance.GetNetworkedSubObjectList(modelListIndex);

            subObjects.Add(netObject);

            netSubObjectLists[modelListIndex] = subObjects;

            return(InstantiateNetworkedGameObject(netObject, customEntityID, modelListIndex));
        }
Example #18
0
        /// <summary>
        /// Meant to convert our GameObject data to follow our POSITION struct to be sent each update
        /// </summary>
        /// <param name="Net_Register_GameObject container of data"></param>
        public void Send_GameObject_UpdatesToNetwork(NetworkedGameObject eContainer)
        {
            var      entityData = entityManager.GetComponentData <NetworkEntityIdentificationComponentData>(eContainer.Entity);
            Position coords     = default;


            coords = new Position
            {
                clientId   = entityData.clientID,
                entityId   = entityData.entityID,
                entityType = (int)entityData.current_Entity_Type,
                rot        = eContainer.transform.rotation,
                pos        = eContainer.transform.position,

                //since using parenting for objects, we need to translate local to global scalling when having it in your hand, when releasing we need to return such objects scalling from global to local scale
                scaleFactor = eContainer.transform.lossyScale.x,
            };



            coordExport.Invoke(coords);
        }
Example #19
0
        public void SendSyncNetObject(NetworkedGameObject eContainer)
        {
            var entityData = entityManager.GetComponentData <NetworkEntityIdentificationComponentData>(eContainer.Entity);

            Position position = new Position
            {
                clientId = entityData.clientID,

                entityId = entityData.entityID,

                entityType = (int)entityData.current_Entity_Type,

                rot = eContainer.transform.rotation,

                pos = eContainer.transform.position,

                //since using parenting for objects, we need to translate local to global scalling when having it in your hand, when releasing we need to return such objects scalling from global to local scale
                scaleFactor = eContainer.transform.lossyScale.x,
            };

            netUpdateHandler.SendSyncPoseMessage(position);
        }
        public static void ConvertObjectsToEntities(NetworkedGameObject netObject, Transform newParent, int menuButtonIndex)
        {
            EntityManager entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;

            EntityCommandBuffer ecbs = entityManager.World.GetOrCreateSystem <EntityCommandBufferSystem>().CreateCommandBuffer();

            using (BlobAssetStore blobAssetStore = new BlobAssetStore())
            {
                var entity = Entity.Null;

                if (netObject && netObject.Entity != Entity.Null)
                {
                    entity = netObject.Entity;
                }
                else
                {
                    entity = entityManager.CreateEntity();
                }

#if ECS
                entityManager.SetName(entity, newParent.gameObject.name);
#endif

                NetworkedObjectsManager.Instance.topLevelEntityList.Add(entity);

                var buff = ecbs.AddBuffer <LinkedEntityGroup>(entity);

                SetEntityReferences(entityManager, newParent.transform, buff, menuButtonIndex, entity, true);

                //to be in par with gameobject representation current state
                entityManager.SetEnabled(entity, false);

                //play back our structural changes after adding them to our command buffer
                ecbs.ShouldPlayback = false;

                ecbs.Playback(entityManager);
            }
        }
        /// <summary>
        /// Set up imported objects with colliders, register them to be used accross the network, and set properties from the data received and the setup flags from AssetImportSetupSettings
        /// </summary>
        /// <param name="menuButtonIndex"> index of Menu Button. </param>
        /// <param name="modelData"> custom data received by the network</param>
        /// <param name="loadedObject"> our loaded object</param>
        /// <param name="setupFlags"> setup instructions</param>
        /// <returns></returns>
        public static GameObject SetUpGameObject(int menuButtonIndex, ModelDataTemplate.ModelImportData modelData, GameObject loadedObject, ModelImportSettings setupFlags = null)
        {
            const float defaultFitToScale       = 2;
            const bool  defaultDoSetUpColliders = true;
            const bool  defaultIsNetworked      = true;
            const float defaultModelSpawnHeight = 0.0f;

            if (loadedObject == null)
            {
                throw new System.Exception("Failed to import an model at runtime because the loaded object was null. Please ensure your custom runtime importer properly returns a valid GameObject.");
            }

            if (setupFlags == null)
            {
                setupFlags                  = ScriptableObject.CreateInstance <ModelImportSettings>();
                setupFlags.fitToScale       = defaultFitToScale;
                setupFlags.doSetUpColliders = defaultDoSetUpColliders;
                setupFlags.isNetworked      = defaultIsNetworked;
                setupFlags.spawnHeight      = defaultModelSpawnHeight;
            }

            //parent of model in list
            Transform newParent = new GameObject(menuButtonIndex.ToString()).transform;

            NetworkedGameObject nRGO = default;

            if (setupFlags.isNetworked)
            {
                //set up reference to use with network
                nRGO = NetworkedObjectsManager.Instance.CreateNetworkedGameObject(newParent.gameObject, menuButtonIndex, modelData.id);
            }

            //provide appropriate tag to enable it to be grabbed
            newParent.gameObject.gameObject.tag = TagList.interactable;

            newParent.gameObject.SetActive(false);

            Bounds bounds = new Bounds();

            if (setupFlags.doSetUpColliders)
            {
                SetUpColliders(loadedObject, bounds, newParent, modelData, setupFlags, menuButtonIndex);

                //turn off whole colliders for non-whole objects
                if (!modelData.isWholeObject)
                {
                    SetUpSubObjects(newParent, loadedObject, menuButtonIndex);
                }

                SetUpAnimation(newParent, loadedObject, modelData.isWholeObject);
                AdjustHeight(newParent, setupFlags);
                AdjustScale(newParent, bounds, setupFlags);
            }

            AdjustPose(newParent, modelData, bounds);

            //Initialize fields for ECS
            ConvertObjectsToEntities(nRGO, newParent, menuButtonIndex);

            newParent.gameObject.SetActive(false);

            return(newParent.gameObject);
        }
        //pick up our closest collider and obtain its references
        private Transform GetNearestRigidBody(Collider[] colliders)
        {
            float            minDistance       = float.MaxValue;
            float            distance          = 0.0f;
            List <Transform> transformToRemove = new List <Transform>();
            Collider         nearestTransform  = null;

            foreach (Collider col in colliders)
            {
                if (!col.CompareTag(TagList.interactable))
                {
                    continue;
                }

                if (!col.gameObject.activeInHierarchy)
                {
                    continue;
                }

                distance = (col.ClosestPoint(thisTransform.position) - thisTransform.position).sqrMagnitude; // (contactBody.position - thisTransform.position).sqrMagnitude;

                if (distance > 0.01f)
                {
                    continue;
                }

                //   Debug.Log("pick up is called");
                if (distance < minDistance)
                {
                    minDistance      = distance;
                    nearestTransform = col;
                }
            }
            // didnt find nearest collider return null
            if (nearestTransform == null)
            {
                return(null);
            }

            currentRB = null;
            //   currentParent = null;
            currentNetRegisteredGameObject = null;

            Transform nearPar = null;

            //set shared parent to reference when changing hands - set this ref when someone is picking up first object and//
            //whenever someone has on object on left hand then grabs that same object with the right hand, releases right hand to grab new object
            //with the left hand grab this new object - however, the shared parent is still the left

            //set last object to be picked up as the shared parent

            nearPar = nearestTransform.transform.parent;

            if (nearPar)
            {
                if (nearPar != firstControllerInteraction.thisTransform && nearPar != secondControllerInteraction.thisTransform && nearPar != StretchManager.Instance.midpoint && nearPar != StretchManager.Instance.endpoint1 && StretchManager.Instance.stretchParent != nearPar)
                {
                    var parent = nearestTransform.transform.parent;

                    if (firstControllerInteraction == this)
                    {
                        StretchManager.Instance.originalParentOfFirstHandTransform = parent;
                    }

                    if (secondControllerInteraction == this)
                    {
                        StretchManager.Instance.originalParentOfSecondHandTransform = parent;
                    }
                }
            }

            //var netObj = nearestTransform.GetComponent<NetworkAssociatedGameObject>();
            if (nearestTransform.TryGetComponent(out NetworkedGameObject netObj))
            {
                if (entityManager.HasComponent <TransformLockTag>(netObj.Entity))
                {
                    return(null);
                }

                currentNetRegisteredGameObject = netObj;

                Entity_Type netObjectType = default;
                netObjectType = entityManager.GetComponentData <NetworkEntityIdentificationComponentData>(currentNetRegisteredGameObject.Entity).current_Entity_Type;

                if (netObjectType == Entity_Type.physicsObject)
                {
                    currentRB = currentNetRegisteredGameObject.GetComponent <Rigidbody>();

                    if (currentRB == null)
                    {
                        Debug.LogWarning("No Rigid body on physics object Entity Type");
                    }
                }
            }
            return(nearestTransform.transform);
        }
Example #23
0
 public void Register(int entityID, NetworkedGameObject netObject)
 {
     networkedObjectFromEntityId.Add(entityID, netObject);
 }
Example #24
0
        private IEnumerator Start()
        {
            //WebGLMemoryStats.LogMoreStats("ModelImportInitializer.Start Setup BEFORE");

            if (loader == null)
            {
                throw new System.Exception("Missing loader");
            }
            if (modelData == null)
            {
                throw new System.Exception("Missing model data");
            }
            if (progressDisplay == null)
            {
                throw new System.Exception("Missing progress display");
            }

            CreateAndSetPositionForModelsList();

            //initialize a list of blank gameObjects so we can instantiate models even if they load out-of-order.
            for (int i = 0; i < modelData.models.Count; i += 1)
            {
                NetworkedGameObject netObject = default;
                networkedGameObjects.Add(netObject);
            }

            //WebGLMemoryStats.LogMoreStats("ModelImportInitializer.Start Setup AFTER");

            WebGLMemoryStats.ChooseMemoryLimitForDevice(doForceMobileMemoryLimit);

            localFiles = new List <ModelFile>();

            //since we have coroutines and callbacks, we should keep track of the number of models that have finished retrieving.
            GameStateManager.Instance.isAssetImportFinished = false;
            modelsToRetrieve = modelData.models.Count;

            //Wait until all objects are finished loading
            StartCoroutine(RetrieveModelFiles());

            //Debug.Log("Model files finished loading.");

            yield return(new WaitUntil(() =>
            {
                //Debug.Log($"{GameStateManager.Instance.modelsToInstantiate} models left to instantiate.");
                return modelsToRetrieve == 0;
            }));

            //since we have coroutines and callbacks, we should keep track of the number of models that have finished instantiating.
            GameStateManager.Instance.isAssetImportFinished = false;
            modelsToInstantiate = modelData.models.Count;

            //Wait until all objects are finished loading
            StartCoroutine(InstantiateModels());

            //Debug.Log("Finished instantiating models.");

            yield return(new WaitUntil(() =>
            {
                //Debug.Log($"{GameStateManager.Instance.modelsToInstantiate} models left to instantiate.");
                return modelsToInstantiate == 0;
            }));

            GameStateManager.Instance.isAssetImportFinished = true;
        }