Search Unity

Question [ Tilemap ] When is a tile's Startup() called in relation to the corresponding SetTile()?

Discussion in '2D' started by organick, Mar 3, 2021.

  1. organick

    organick

    Joined:
    Jul 17, 2012
    Posts:
    17
    A major pain point while working with the 2D tilemap is that StartUp() of a tile seems to not be synchronous relative to its SetTile() function.

    It seems that StartUp() is called the next frame, can anyone confirm this?

    I can imagine this might have be done for performance reasons (would be nice if it's documented), but it would be nice to have an option for the initialization to just be blocking/synchronous. Am I missing something obvious here?
     
  2. ChuanXin

    ChuanXin

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    1,068
    The StartUp call should occur as part of SetTile during PlayMode. The only time it does not happen that way is if the Tilemap is loaded from a Scene. The StartUp call will occur in the first frame after the Tilemap is loaded.

    Do let us know if that is not the case for you, thanks!
     
  3. organick

    organick

    Joined:
    Jul 17, 2012
    Posts:
    17
    Sorry took me awhile to get back to this. I took another look at the behaviour and definitely seem two different patterns. One where StartUp is called synchronously after SetTile, and another one where StartUp called seems to becalled from Tilemap.Update. The two cases are two different scenarios in my game and I couldn't figure out if it is causing the difference in behaviour. Here are the call stacks for the two cases.

    Edit: apologies for the clutter, I marked interesting parts (SetTile and StartUp) using ###

    Code (CSharp):
    1.  
    2. (Mono JIT Code) [ObjectTile.cs:57] pxcrpg.gameengine.visual.ObjectTile:GetTileData (UnityEngine.Vector3Int,UnityEngine.Tilemaps.ITilemap,UnityEngine.Tilemaps.TileData&)
    3. (Mono JIT Code) (wrapper runtime-invoke) <Module>:runtime_invoke_void__this___Vector3Int_object_intptr& (object,intptr,intptr,intptr)
    4. (mono-2.0-bdwgc) [mini-runtime.c:2812] mono_jit_runtime_invoke
    5. (mono-2.0-bdwgc) [object.c:2921] do_runtime_invoke
    6. (mono-2.0-bdwgc) [object.c:2968] mono_runtime_invoke
    7. (Unity) scripting_method_invoke
    8. (Unity) ScriptingInvocation::Invoke
    9. (Unity) InvokeGetTileData
    10. (Unity) Tilemap::RefreshTileAsset
    11. (Unity) Tilemap::RefreshTileAsset
    12. (Unity) Tilemap::RefreshTileAssetsInQueue<0>
    13. (Unity) Tilemap::SetTileAsset
    14. (Unity) Tilemap_CUSTOM_SetTileAsset_Injected
    15. (Mono JIT Code) (wrapper managed-to-native) UnityEngine.Tilemaps.Tilemap:SetTileAsset_Injected (UnityEngine.Tilemaps.Tilemap,UnityEngine.Vector3Int&,UnityEngine.Object)
    16. (Mono JIT Code) UnityEngine.Tilemaps.Tilemap:SetTileAsset (UnityEngine.Vector3Int,UnityEngine.Object)
    17. ### (Mono JIT Code) UnityEngine.Tilemaps.Tilemap:SetTile (UnityEngine.Vector3Int,UnityEngine.Tilemaps.TileBase) ###
    18. (Mono JIT Code) [WorldTile.cs:236] pxcrpg.gameengine.presentation.WorldTile:AddWorldObjectToTileMap (UnityEngine.Vector2Int,string)
    19. ### a bunch of game script code ###
    20. 0x000002530f0b4243 (Mono JIT Code) pxcrpg.GameController:Update ()
    21. 0x000002531344b2e8 (Mono JIT Code) (wrapper runtime-invoke) object:runtime_invoke_void__this__ (object,intptr,intptr,intptr)
    22. (mono-2.0-bdwgc) [mini-runtime.c:2812] mono_jit_runtime_invoke
    23. (mono-2.0-bdwgc) [object.c:2921] do_runtime_invoke
    24. (mono-2.0-bdwgc) [object.c:2968] mono_runtime_invoke
    25. (Unity) scripting_method_invoke
    26. (Unity) ScriptingInvocation::Invoke
    27. (Unity) MonoBehaviour::CallMethodIfAvailable
    28. (Unity) MonoBehaviour::CallUpdateMethod
    29. (Unity) BaseBehaviourManager::CommonUpdate<BehaviourManager>
    30. (Unity) BehaviourManager::Update
    31. (Unity) `InitPlayerLoopCallbacks'::`2'::UpdateScriptRunBehaviourUpdateRegistrator::Forward
    32. (Unity) ExecutePlayerLoop
    33. (Unity) ExecutePlayerLoop
    34. (Unity) PlayerLoop

    Code (CSharp):
    1. ### (Mono JIT Code) [ObjectTile.cs:19] pxcrpg.gameengine.visual.ObjectTile:StartUp (UnityEngine.Vector3Int,UnityEngine.Tilemaps.ITilemap,UnityEngine.GameObject) ###
    2. (Mono JIT Code) (wrapper runtime-invoke) <Module>:runtime_invoke_bool__this___Vector3Int_object_object (object,intptr,intptr,intptr)
    3. (mono-2.0-bdwgc) [mini-runtime.c:2812] mono_jit_runtime_invoke
    4. (mono-2.0-bdwgc) [object.c:2921] do_runtime_invoke
    5. (mono-2.0-bdwgc) [object.c:2968] mono_runtime_invoke
    6. (Unity) scripting_method_invoke
    7. (Unity) ScriptingInvocation::Invoke
    8. (Unity) InvokeStartUp
    9. (Unity) Tilemap::StartUpTileAsset
    10. (Unity) Tilemap::StartUpAllTileAssets
    11. ### (Unity) Tilemap::Update ###
    12. (Unity) BaseBehaviourManager::CommonUpdate<BehaviourManager>
    13. (Unity) BehaviourManager::Update
    14. (Unity) `InitPlayerLoopCallbacks'::`2'::UpdateScriptRunBehaviourUpdateRegistrator::Forward
    15. (Unity) ExecutePlayerLoop
    16. (Unity) ExecutePlayerLoop
    17. (Unity) PlayerLoop

    Code (CSharp):
    1. ### (Mono JIT Code) [ObjectTile.cs:19] pxcrpg.gameengine.visual.ObjectTile:StartUp (UnityEngine.Vector3Int,UnityEngine.Tilemaps.ITilemap,UnityEngine.GameObject) ###
    2. (Mono JIT Code) (wrapper runtime-invoke) <Module>:runtime_invoke_bool__this___Vector3Int_object_object (object,intptr,intptr,intptr)
    3. (mono-2.0-bdwgc) [mini-runtime.c:2812] mono_jit_runtime_invoke
    4. (mono-2.0-bdwgc) [object.c:2921] do_runtime_invoke
    5. (mono-2.0-bdwgc) [object.c:2968] mono_runtime_invoke
    6. (Unity) scripting_method_invoke
    7. (Unity) ScriptingInvocation::Invoke
    8. (Unity) InvokeStartUp
    9. (Unity) Tilemap::StartUpTileAsset
    10. (Unity) Tilemap::RefreshTileAsset
    11. (Unity) Tilemap::RefreshTileAsset
    12. (Unity) Tilemap::RefreshTileAssetsInQueue<0>
    13. (Unity) Tilemap::SetTileAsset
    14. (Unity) Tilemap_CUSTOM_SetTileAsset_Injected
    15. (Mono JIT Code) (wrapper managed-to-native) UnityEngine.Tilemaps.Tilemap:SetTileAsset_Injected (UnityEngine.Tilemaps.Tilemap,UnityEngine.Vector3Int&,UnityEngine.Object)
    16. (Mono JIT Code) UnityEngine.Tilemaps.Tilemap:SetTileAsset (UnityEngine.Vector3Int,UnityEngine.Object)
    17. ### (Mono JIT Code) UnityEngine.Tilemaps.Tilemap:SetTile (UnityEngine.Vector3Int,UnityEngine.Tilemaps.TileBase) ###
    18. (Mono JIT Code) [WorldTile.cs:236] pxcrpg.gameengine.presentation.WorldTile:AddWorldObjectToTileMap (UnityEngine.Vector2Int,string)
    19. <<< a bunch of dame script code >>>
    20. (Mono JIT Code) pxcrpg.command.CommandRunner:Execute (pxcrpg.command.ICommand)
    21. (Mono JIT Code) [BlueprintInputHandler.cs:93] pxcrpg.ui.BlueprintInputHandler:PlaceBlueprint (UnityEngine.InputSystem.InputAction/CallbackContext)
    22. (Mono JIT Code) UnityEngine.Events.InvokableCall`1<UnityEngine.InputSystem.InputAction/CallbackContext>:Invoke (UnityEngine.InputSystem.InputAction/CallbackContext)
    23. <<< a bunch of InputSystem code >>>
    24. (Mono JIT Code) UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate (UnityEngineInternal.Input.NativeInputUpdateType,intptr)
    25. (Mono JIT Code) (wrapper runtime-invoke) <Module>:runtime_invoke_void_int_intptr (object,intptr,intptr,intptr)
    26. (mono-2.0-bdwgc) [mini-runtime.c:2812] mono_jit_runtime_invoke
    27. (mono-2.0-bdwgc) [object.c:2921] do_runtime_invoke
    28. (mono-2.0-bdwgc) [object.c:2968] mono_runtime_invoke
    29. (Unity) scripting_method_invoke
    30. (Unity) ScriptingInvocation::Invoke
    31. (Unity) ScriptingInvocation::Invoke<void>
    32. (Unity) Scripting::UnityEngineInternal::Input::NativeInputSystemProxy::NotifyUpdate
    33. (Unity) SendInputEventsToScript
    34. (Unity) `InternalInitializeModule_Input'::`2'::PreUpdateNewInputUpdateRegistrator::Forward
    35. (Unity) ExecutePlayerLoop
    36. (Unity) ExecutePlayerLoop
    37. (Unity) PlayerLoop
     
  4. ChuanXin

    ChuanXin

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    1,068
    Yes, the two scenarios which you are seeing are correct.
    • Startup called on SetTile
    • Startup called when Tilemap is loaded. This is for users who have need this callback to set up their Tiles whenever the Tilemap is loaded (eg. instanced from a Prefab). As SetTile is not called for this by the user, this is done on the first update after the Tilemap is loaded.
    @organick Could you share your use-case where one scenario works for you but the other does not? Thanks!
     
  5. organick

    organick

    Joined:
    Jul 17, 2012
    Posts:
    17
    It's not they don't work entirely, it's just that it adds complication in dealing with the difference in behaviour.

    I see, I think now that you mention Tilemap loading, the call stack makes more sense to me, as I do create a new tilemap right before calling SetTile() on it. And before the first Update() call is made to the tilemap, any of its tiles' StartUp won't be called.

    To elaborate on my specific use cases I mentioned above:

    1. Open world chunking. As the player avatar moves around, new chunks (containing tilemap) are added to the scene, and immediately populate with objects (tiles). This is where I see the disjoint between SetTile() vs StartUp(), which as you explained, happened because the Tilemap was just created.

    2. Player place objects (tiles) on an world chunk (containing tilemap) that has already been loaded. In this case, calling SetTile() is followed with a synchronous call to StartUp().

    In either cases, I do bunch of setups and initialization for the newly created GameObject within StartUp(). And my intented code flow is something like this:

    1. SetTile()
    2. StartUp() // the new game objects are now ready
    3. more code that relies on new game objects being ready

    But obviously, this won't work if StartUp() is called only at some later point in time.

    Again, I can write extra code to deal with this, but I rather have one single code path. It seems that if I can somehow call Update() on the Tilemap right after I created it, it might fix my problem. Not sure if that's possible or if it's even a good idea.

    Thanks for taking a look at this.
     
  6. ChuanXin

    ChuanXin

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    1,068
    @organick Ok, I am beginning to understand the issue here. If it is possible, could you share more details on how you create the Tilemap and set the Tiles? I assume that the flow that you are seeing is something like this:
    • Create Tilemap
      • SetTile
      • StartUp
    • First Update
      • StartUp
    • Second Update
    • ...
     
  7. organick

    organick

    Joined:
    Jul 17, 2012
    Posts:
    17
    On an arbitrary frame N:
    1. Something in the game triggered the instantiation of a prefab that has a Tilemap somewhere in its hierarchy. (The exact scenario is that player moving to an unloaded part of the world and triggers a new world chunk to get loaded into the scene).
    2. Immediately after the Tilemap instantiation, the script gets a reference to that Tilemap component and call SetTile() a bunch of times with some Tile T.
    3. At this point I expected Tile T to immediately call its StartUp() method, but as you explained in your previous post (and as I observed in the call stack), the StartUp() method for Tile T only gets called later in the frame when Tilemap.Update() gets called for the first time ever.

    The game is almost entirely script-driven, so the tilemap is also created from a script during runtime.

    I hope this is not too confusing :)
     
  8. ChuanXin

    ChuanXin

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    1,068
    Thanks for the clarification! We will try to resolve this case so that you do not need special handling for the two different StartUp cases.

    Also, would it be possible to share the version of the Unity Editor you are using?
     
  9. organick

    organick

    Joined:
    Jul 17, 2012
    Posts:
    17
    Here's the Unity Editor version I'm using:
    2020.2.7f1.4104

    I do have one follow-up question I have that sort of stems from this.

    Would you recommend using 2D Tilemap for a purely script-driven game?
    A couple of folks on another post suggested that the 2D Tilemap might not be for my game, due to the lack of some hooks and therefore control (the topic of my other post was about object-pooling the instantiated tile GameObjects).

    I'm curious about your opinion on this. Does the 2D Tilemap "vision" includes purely script-driven games? I'm trying to decide whether rolling out my own solution would be the better way to go.

    Thanks!
     
  10. ChuanXin

    ChuanXin

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    1,068
    Could you clarify what you mean by a purely-script driven game? I am not certain if the 2D Tilemap would suit all your needs and use-cases, but if you do share them, we would certainly try to improve it and add new features where possible! Thanks!
     
  11. organick

    organick

    Joined:
    Jul 17, 2012
    Posts:
    17
    Yes, for sure.

    The world in my game is not handcrafted, it's procedurally generated. So after the world data gets generated (or loaded from a save file), my scripts will simply create whatever objects necessary and put them into the scene. One such object is the tilemap.

    Since the world can be huge, I needed to divide the world into "world chunks" such that I only materialize -- that is, putting GameObjects into the scene -- when a world chunk is nearby the player. One of the main GameObject in a world chunk is a 2D Tilemap. So as player moves around in the world, new world chunks (Tilemap) gets loaded in, and the ones far away gets unloaded.

    As you can see, the game project does not benefit much from the nice editor tools such as the various brushes and tilemap editor.

    Here's a snapshot of the game, each of the cyan squares is a 2D Tilemap. Well, more precisely there could be multiple 2D tilemaps in a world chunk. One for the ground, one for interactable objects (e.g. trees, rocks), and there might be more.

    Edit: just adding a bit more info about which Tilemap API my game is using at the moment:
    1. Tilemap.SetTile(); to add new tile or clear a tile during runtime
    2. Tile.Startup(); put custom code for the GameObject instantiated by the Tile
    3. Tile.GetDataTile(); sets the prefab for the GameObject I want the tile to instantiate
    4. RuleTile; this was quite handy for handling walls and doors
    5. Tilemap.ClearAllTiles(); I used this when unloading a world chunk (tilemap), however clearing out plenty of tiles that have gameobjects seems to slow for one frame operation. I instead call SetTile(null) over multiple frames.

    Let me know what you think. Happy fridays :)

    tilemap.png
     
    Last edited: Mar 19, 2021
  12. ChuanXin

    ChuanXin

    Unity Technologies

    Joined:
    Apr 7, 2015
    Posts:
    1,068
    That sounds like the Unity 2D Tilemap would suit your needs.

    For better performance, you could prefer to use
    Tilemap.SetTiles
    or
    Tilemap.SetTilesBlock
    , which reduces the API overhead compared to calling
    Tilemap.SetTile
    individually.

    If you have other suggestions or are lacking certain features for your use-case, do let us know! Thanks!