Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

[Released] Ultimate Replay 2.0 - Next generation state-based replay system

Discussion in 'Assets and Asset Store' started by scottyboy805, Sep 8, 2020.

  1. mirrorboy

    mirrorboy

    Joined:
    Apr 18, 2015
    Posts:
    4
    Code (CSharp):
    1.            
    2.             //Playing & Recording
    3.             state.Where(value => value == StateManager.APP_STATE.PLAYING_READY).Subscribe(_ =>
    4.             {
    5.                 // Start a fresh recording
    6.                 if (ReplayManager.IsRecording(recordHandle) == false)
    7.                 {
    8.                     // Clear old data
    9.                     if (storageTarget.MemorySize > 0)
    10.                     {
    11.                         storageTarget.Clear();
    12.                     }
    13.  
    14.                     recordHandle = ReplayManager.BeginRecording(storageTarget, null, true, false);
    15.                 }
    16.             });
    17.  
    18.             //Replay
    19.             state.Where(value => value == StateManager.APP_STATE.REPLAYING).Subscribe(_ =>
    20.             {
    21.                 ReplayHandle playbackHandle = ReplayHandle.invalid;
    22.  
    23.                 // Start playback
    24.                  playbackHandle = ReplayManager.BeginPlayback(storageTarget, null, false);
    25.                    
    26.  
    27.                     ReplayManager.AddPlaybackEndListener(playbackHandle, () => {
    28.                         state.Value = StateManager.APP_STATE.RESULT;
    29.                     });
    30.             });
    31.  
    32.             //Result
    33.             state.Where(value => value == StateManager.APP_STATE.RESULT).Subscribe(_ =>
    34.             {
    35.                 // Stop all recording
    36.                 if (ReplayManager.IsRecording(recordHandle) == true)
    37.                 {
    38.                     ReplayManager.StopRecording(ref recordHandle);
    39.                 }
    40.  
    41.             });
     
  2. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi,
    We have not tested on IOS as we have no devices but the asset was designed with support for all platforms so should work fine.There are no special setup steps to get IOS to work and it should work as is.
    Do you get any errors or exceptions when running a build? If so, could you post the messages and stack traces if possible.
    Could you try the included 'CubeTest' demo scene which can be found in the folder 'Assets/Ultimate Replay 2.0/Demo'. Does this scene work as expected or are there stil problems?
     
  3. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Ultimate Replay 2.1.2 has been submitted to the asset store for review and should be available within the next day or two. This version includes:
    • Fixed a bug in the playback system where incorrect frame delta values could be used in some cases when replaying in reverse, causing non-smooth or jerky movement.
    • Fixed an issue in the file target storage device where the background streaming thread could run at unrestricted speed causing high CPU usage in some cases.
    • Fixed a bug in the replay system where replaying in reverse could sometimes freeze the editor due to a logic error.
     
    julianr likes this.
  4. MikhaskoS

    MikhaskoS

    Joined:
    Jun 24, 2018
    Posts:
    52
    Hi. Could you please demonstrate an example of "ReplayGhostVehicle" with writing data to a file. My attempts to modify the code lead to "ThreadAbortException" errors, which are hard to understand. Thanks.
     
  5. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi,
    Making the ghost vehicle example work with files requires a little more work to ensure that the replay file is not accessed at the same time by mutiple replay operations, and that files get closed properly so that we can reload them later without issues.

    Here is a modified example script which as you can see, is a little more involved than the memory target apoproach:

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using System.IO;
    3. using UltimateReplay.Storage;
    4. using UnityEngine;
    5.  
    6. namespace UltimateReplay.Example
    7. {
    8.     public class StartFinishLineFile : MonoBehaviour
    9.     {
    10.         // Private
    11.         private ReplayScene recordScene = null;
    12.         private ReplayScene playbackScene = null;
    13.         private ReplayFileTarget recordStorage = null;
    14.         private ReplayFileTarget playbackStorage = null;
    15.         private ReplayHandle recordHandle;
    16.         private ReplayHandle playbackHandle;
    17.         private bool playbackEndFlag = false;
    18.  
    19.         // Public
    20.         public ReplayObject playerCar;
    21.         public ReplayObject ghostCar;
    22.  
    23.         public string replayFileRecord = "GhostVehicle_Temp.replay";
    24.         public string replayFileReplay = "GhostVehicle.replay";
    25.  
    26.         // Methods
    27.         public void Start()
    28.         {
    29.             // Load temp file for recording
    30.             recordStorage = ReplayFileTarget.CreateReplayFile(replayFileRecord);
    31.  
    32.             // Create a recordable scene containg the player car only
    33.             recordScene = new ReplayScene(playerCar);
    34.             playbackScene = new ReplayScene(ghostCar);
    35.         }
    36.  
    37.         public void OnDestroy()
    38.         {
    39.             if(recordStorage != null)
    40.             {
    41.                 recordStorage.Dispose();
    42.                 recordStorage = null;
    43.             }
    44.  
    45.             if(playbackStorage != null)
    46.             {
    47.                 playbackStorage.Dispose();
    48.                 playbackStorage = null;
    49.             }
    50.         }
    51.  
    52.         public void OnTriggerEnter(Collider other)
    53.         {
    54.             // Stop replaying
    55.             if (ReplayManager.IsReplaying(playbackHandle) == true)
    56.             {
    57.                 ReplayManager.StopPlayback(ref playbackHandle);
    58.  
    59.                 // Release file
    60.                 playbackStorage.Dispose();
    61.             }
    62.  
    63.             if(playbackEndFlag == true)
    64.             {
    65.                 playbackStorage.Dispose();
    66.                 playbackEndFlag = false;
    67.             }
    68.  
    69.             // Stop recording
    70.             if(ReplayManager.IsRecording(recordHandle) == true)
    71.             {
    72.                 // Stop recording
    73.                 ReplayManager.StopRecording(ref recordHandle);
    74.  
    75.                 // Release file
    76.                 recordStorage.Dispose();
    77.  
    78.                 // Delete old file
    79.                 if (File.Exists(replayFileReplay) == true)
    80.                     File.Delete(replayFileReplay);
    81.  
    82.                 // Rename file
    83.                 File.Move(replayFileRecord, replayFileReplay);
    84.  
    85.                 playbackStorage = ReplayFileTarget.ReadReplayFile(replayFileReplay);
    86.                 recordStorage = ReplayFileTarget.CreateReplayFile(replayFileRecord);
    87.  
    88.                 // Enable the ghost car
    89.                 ghostCar.gameObject.SetActive(true);
    90.  
    91.                 // Clone identities - This allows the ghost car to be replayed as the player car
    92.                 ReplayObject.CloneReplayObjectIdentity(playerCar, ghostCar);
    93.  
    94.                 // Start replaying
    95.                 playbackHandle = ReplayManager.BeginPlayback(playbackStorage, playbackScene);
    96.  
    97.                 // Add end playback listener
    98.                 ReplayManager.AddPlaybackEndListener(playbackHandle, OnGhostVehiclePlaybackComplete);
    99.             }
    100.  
    101.             // Start recording
    102.             recordHandle = ReplayManager.BeginRecording(recordStorage, recordScene);
    103.         }
    104.  
    105.         private void OnGhostVehiclePlaybackComplete()
    106.         {
    107.             // Hide ghost car
    108.             ghostCar.gameObject.SetActive(false);
    109.  
    110.             playbackEndFlag = true;
    111.         }
    112.     }
    113. }
    114.  
    This code will create a temp file to record the ghost vehicle to and then when the car crosses the line, this file is then renamed to the correct file name. This avoids the issue of accessing the same file by both the recording and replay operation as this is not allowed.

    I hope this helps you.
     
    Fibonaccov and julianr like this.
  6. justaddice83

    justaddice83

    Joined:
    Sep 19, 2012
    Posts:
    45
    Hello,

    I would like to use the reply data outside of Unity, I think it would be easier to use JSON export for this.

    I've tried enabling ULTIMATEREPLAY_JSON in Unity's Scripting Defined Symbols. But I get errors, and on inspection it looks like it wasn't quite completed.

    I say this, as a notable error is an undefined reference in file:

    ReplayFileJsonTarget.cs line:17 referring to a binaryStream object.
     
  7. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi,
    We have had to disable the json support for the moment because we were having too many issues with it, namely, crashing the editor in some cases. We will be working to fix this as soon as possible but unfortunately it is not a quick fix. In the meantime, you can access the stored replay data from code using the replay serializers API. It is not fully documented but is also not too difficult to use. Here is a quick example of how you could access transform data:

    Code (CSharp):
    1. using System.Collections;
    2. using UltimateReplay;
    3. using UltimateReplay.Storage;
    4. using UnityEngine;
    5.  
    6. class Example : MonoBehaviour
    7. {
    8.     IEnumerator Start()
    9.     {
    10.         ReplayMemoryTarget target = new ReplayMemoryTarget();
    11.      
    12.      
    13.         // Record some data for 1 second
    14.         ReplayHandle handle = ReplayManager.BeginRecording(target);
    15.      
    16.         yield return new WaitForSeconds(1f);
    17.      
    18.         ReplayManager.StopRecording(ref handle);
    19.      
    20.         // Make sure target is ready for read requests
    21.         target.PrepareTarget(ReplayTargetTask.PrepareRead);
    22.  
    23.         // Serializer api
    24.         ReplaySnapshot snapshot = target.FetchSnapshot(0.5f);    // Find the snapshot for the 0.5 second mark
    25.      
    26.         foreach(ReplayIdentity id in snapshot.Identities)
    27.         {
    28.             // Get data for object with the target id
    29.             ReplayState state = snapshot.RestoreSnapshot(id);
    30.          
    31.             // Create an object serializer instance
    32.             ReplayObjectSerializer objectSerializer = new ReplayObjectSerializer();
    33.          
    34.             // Run deserialize
    35.             objectSerializer.OnReplayDeserialize(state);
    36.          
    37.             // Access component data - Similar properties exist for replay variables, events methods etc.
    38.             IList<ReplayComponentData> components = objectSerializer.ComponentStates;
    39.          
    40.             foreach(ReplayComponentData data in components)
    41.             {
    42.                 Debug.Log("Target replay component id: " + data.BehaviourIdentity);
    43.              
    44.                 // Get the serialize type for this component
    45.                 Type serializerType = ReplaySerializers.GetSerializerTypeFromID(data.ComponentSerializerID);
    46.              
    47.                 // CHeck for transform
    48.                 if(serializerType == typeof(ReplayTransformSerializer))
    49.                 {
    50.                     ReplayTransformSerializer transformSerializer = new ReplayTransformSerializer();
    51.                  
    52.                     // Read the transform data
    53.                     transformSerializer.OnReplayDeserialize(data.ComponentStateData);
    54.                  
    55.                     Debug.Log("Position: " + transformSerializer.Position);
    56.                     Debug.Log("Rotation: " + transformSerializer.Rotation);
    57.                     Debug.Log("Scale: " + transformSerializer.Scale);
    58.                 }
    59.             }
    60.         }
    61.     }
    62. }
     
    julianr likes this.
  8. DJ_Design

    DJ_Design

    Joined:
    Mar 14, 2013
    Posts:
    124
    Ok Interesting! the TRAIL Replay component, this must be a bug because as soon as I remove it everything works smooth as butter.

    Getting it working besides that, I have to say it it's a great tool. Well worth the price thank you.
     
    Last edited: Dec 13, 2020
  9. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi,
    Thanks for reporting this issue.
    We have not experienced or received any reports of instability of the main asset so the trail renderer component is likley causing the issue, since many users likley wont use it or experience crashes. The crashes we mention were only reproducable when the json file formating code was enabled which we have since diabled until it can be properly fixed.
    We will look into it further when we are back in the office to see if we can find and fix the issue.

    Let me know if there is anything else.
     
    DJ_Design likes this.
  10. DJ_Design

    DJ_Design

    Joined:
    Mar 14, 2013
    Posts:
    124

    Awesome, yeah it was a huge relief just knowing I wasn't crazy with what I had written in code but at the same time trails are a big part of what I'm using this for actually, so I hope we can get that working at some point.

    One thing I would like to ask, I see in the examples unless I'm mistaken they all showcase the asset used in a linear Record-Replay-back to Live, can for example U.R 2.0 do something similar to Brave or prince of persia- where it's not just resetting you to where you begin replaying the recording the time table is able to go live at the current moment of replaying? Instead of it just being a preview.

    Is this at all possible? If so what should I be looking for? Thanks for your time.
     
  11. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Yes this is possible to do. As it happens, we just finsihed implementing a time rewind feature for a client using Ultimate Replay 2.0. If you want the replay to leave game objects in their current position when exiting playback mode, you should take a look at the fourth parameter of 'ReplayManager.BeginPlayback'. This is a bool parameter named 'restoreReplayScene' which is enabled by default. Passing false for this argument will cause the replay system to leave replay objects at their current location when 'StopPlayback' is called.

    You can also call the method 'ReplayManager.SetPlaybackEndRestoreSceneMode' while playback is running if you need to change that value dynamically. This will allow you to create a time rewind feature or similar.
     
    DJ_Design likes this.
  12. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi again,
    Update 2.1.3 has been submitted which now includes support for writing json files only. This should allow you to inspect the replay data in a thrid party application. We have tracked the cause of the editor crash to the reading code so will require further work to have full json support. The update should be available within a day or two but feel free to email us with your invoice number if you need the update asap: info(at)trivialinteractive.co.uk.
     
  13. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Ultimate Replay 2.1.3 has been submitted to the asset store. This version includes:
    • Fixed a playback bug where seeking to the boundaries of a replay could cause an invalid ReplayTime.Delta value to be generated (NaN) causing issues in replay components.
    • Playback end listeners will now be invoked after the replay system has performed it frame update. This means that you can now call replay methods like 'BeginPlayback' or 'BeginRecording' from within a playback end listener without issue. Previously this would have caused an exception.
    • Fixed a bug in the ReplayTrailRenderer where it is possible to enter an infinite loop if the observed trail render componet does not have enough position slots. This would lead to editor freezes and ultimately a crash.
    • Reintroduced support for limited json file format support. At the moment, only writing json files is supported so that you can evaluate the replay data in a third party application. Read support will be added in the future.
     

    Attached Files:

    julianr and DJ_Design like this.
  14. justaddice83

    justaddice83

    Joined:
    Sep 19, 2012
    Posts:
    45

    Thanks for this, and the prompt reply. However its generating an error I am not sure how to resolve. Specially this line:

    Code (csharp):
    1.  
    2. transformSerializer.OnReplayDeserialize(data.ComponentStateData);
    3.  
    Reports: "InvalidOperationException: There is no data in the object state".

    I am simply adding the above code as a component to the QuickStart scene. I can see data in the object if I do in-line code inspection, so not sure what's causing the issue.
     
  15. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi again,
    Sorry, it looks like I missed out a small detail in that example code.
    If you add the following line before calling 'OnReplayDeserialize', then that should fix the issue:

    Code (CSharp):
    1. data.ComponentStateData.PrepareForRead();
    If the error persists, then there is likley something else going wrong at a different stage causing no replay data to be recorded.
     
  16. lorewap3

    lorewap3

    Joined:
    Jun 24, 2020
    Posts:
    58
    Hi guys! Thanks for the help with the Plugins folder issue! I have another question for you guys but feel like others could benefit as well.

    My project is more of a content creation tool than a game, as such I'm using UR more for recording of character movements and animations. Simple enough, although the plan is to have a alot of characters (possibly 100+) in the scene, so memory usage and fidelity are main concerns I'm watching out for, but also managing recordings with possibly dozens of characters in them. Most of the characters will be rather small and won't require super high fidelity so I don't think it's going to be a big deal.

    But the managing of these recordings and knowing what's in them is the biggest challenge. I'm working to code a custom editor to help manage these and I wanted to ask about any already built functionality that could help me out. In essence I'm building a custom inspector for the .replay files as they don't already have one. This might be an idea to include in future versions? But what I'm trying to code is something that will just basically show you:

    Recording
    Summary:
    - Scene
    - Total Duration
    - Total Static # GameObjects
    - Total Dynamically created GameObjects (prefabs)

    Drilldown:
    - GameObject 1
    a) ReplayTransform
    b) ReplayAnimator
    c) ...
    - GameObject 2
    a) ReplayTransform
    b) ReplayAnimator
    c) ...
    - GameObject 3
    a) ReplayTransform
    b) ReplayAnimator
    c) ...

    etc

    I'm just hoping you guys have some good advice that could save me time when building this? I'm reading the API, but it's not obvious so far how to read that data. I found the ReplayStreamHeader will give me summary stuff like duration and memory usage, but I'm not sure how to drilldown to get the list of game objects in the recording.

    From reading the guide, I know the data is stored by the ReplayIdentity set on the ReplayObject. I'm trying to close that loop to include the name of the game object the ReplayObject is attached to in an editor window so I know what's inside this recording.

    Any advice/suggestions you might have are much appreciated! Thanks guys!
    Will
     
  17. lorewap3

    lorewap3

    Joined:
    Jun 24, 2020
    Posts:
    58
    Greetings again!

    I wanted to update you guys to possibly get more detailed feedback when you have a chance. I built a replay manager window that looks like this:

    upload_2021-1-10_19-14-50.png

    As I couldn't figure out a way to display data about replay files without an intermediary, I created a ReplayBridge ScriptableObject that has some basic metadata shown above. This is the list of assets being displayed in the left side menu tree.

    The 'Create New Replay' creates the SO, but not the actual ReplayFile. The 'Delete Selected Replay', though, will delete both the SO and the replay file (if it exists).

    Then I modified the ReplayControls script to use the SO:
    upload_2021-1-10_19-22-31.png

    If the ReplayFile exists, it can be played when the Start button is pressed. If you record, it will record over the replay file specified in the SO.

    That's where I'm at now, but what I can't figure out is how to use ReplayStorageTarget in a non-live setting. For the editor window above, as you can see I'm trying to show some basic statistics of it, but reading the file results in threading exceptions. I'm doing this on an InspectorInit function:
    Code (CSharp):
    1.  
    2.             if (replayFile != "")
    3.             {
    4.                 try {
    5.                     storageTarget = ReplayFileTarget.ReadReplayFile(replayFile);
    6.                     replayDuration = storageTarget.Duration;
    7.                     replayMemorySize = storageTarget.MemorySize;
    8.                 } catch (Exception e)
    9.                 {
    10.                     storageTarget = null;
    11.                 }
    12.                
    13.             }
    14.  
    Which doesn't seem to get any correct data as both values stay 0, and not long after I get a number of exceptions like this:

    An exception caused the 'ReplayFileTarget' to fail (file stream thread : 1663)
    UnityEngine.Debug:LogError(Object)
    UltimateReplay.Storage.ReplayStreamTarget:StreamThreadMain() (at Assets/Plugins/Recording Plugins/Ultimate Replay 2.0/Scripts/Storage/Stream/ReplayStreamTarget.Threading.cs:119)
    System.Threading.ThreadHelper:ThreadStart()


    eventually followed by:

    ThreadAbortException
    System.Threading.Thread.Sleep (System.Int32 millisecondsTimeout) (at <9577ac7a62ef43179789031239ba8798>:0)
    UltimateReplay.Storage.ReplayStreamTarget.StreamThreadMain () (at Assets/Plugins/Recording Plugins/Ultimate Replay 2.0/Scripts/Storage/Stream/ReplayStreamTarget.Threading.cs:107)
    UnityEngine.Debug:LogException(Exception)
    UltimateReplay.Storage.ReplayStreamTarget:StreamThreadMain() (at Assets/Plugins/Recording Plugins/Ultimate Replay 2.0/Scripts/Storage/Stream/ReplayStreamTarget.Threading.cs:120)
    System.Threading.ThreadHelper:ThreadStart()


    I'm assuming ReplayStorageTarget was never meant to be read in the Editor? I can understand why as this is a niche case, but is there another means I can read the replay data?

    Since I'm using my ReplayManagerWindow to create the ReplayBridge SOs, I can add logic to the manager to store all the gameobject lists in the SO (like looping through all ReplayObjects in the scene and storing them in the SO manually), but I'd rather not do that as I'd rather use the actual replay file as the 'master' file that shows everything it contains. If I add 1 new ReplayObject to the scene then the list my manager saved with the SO becomes inaccurate.

    Any assistance you might have is most appreciated!
    Will
     
  18. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi,
    The first thing I would say is that metadata such as scene name and object names are not stored in the replay file. The reasons for this:
    • Scene Name: Ultimate Replay 2.0 supports playback in any scene (Althoug playback accuracy errors may occur) in order to be more flexible. As a result, the scene name is not stored as it has no use in the replay system.
    • Object Name: Storing the object name is not required as the object identity is represented by a unique id. This allows the replay storage size to be massivley reduced. If we were to store object names instead of id's, we could expect an increase in storage size of 4-5X or more.
    As you say, replay objects are stored by their replay id in storage targets so that name information is lost. It is possible to resolve a target replay object based on its identity for a particular replay frame though so you could then access the name information of the object.

    For accessing replay object information for a particular frame, such as number of scene objects, number of instantiated objects etc. you can use the API's shown below. Each replay frame is known as a 'ReplaySnapshot' and can be retrieved directlry from the storage target using the method 'FetchSnapshot'. This method accepts a float value representing the playback time (passing 1.5f would fetch the ReplaySnapshot for the 1.5 second mark). It also accepts an int value known as a 'SequenceID'. The sequence id is essentially an ordered frame number starting from 1-X. You can use this method to fetch ReplaySnapshots in order.

    Code (CSharp):
    1. using System.Collections;
    2. using UltimateReplay;
    3. using UltimateReplay.Storage;
    4. using UnityEngine;
    5. class Example : MonoBehaviour
    6. {
    7.     IEnumerator Start()
    8.     {
    9.         ReplayMemoryTarget target = new ReplayMemoryTarget();
    10.    
    11.    
    12.         // Record some data for 1 second
    13.         ReplayHandle handle = ReplayManager.BeginRecording(target);
    14.    
    15.         yield return new WaitForSeconds(1f);
    16.    
    17.         ReplayManager.StopRecording(ref handle);
    18.    
    19.         // Make sure target is ready for read requests
    20.         target.PrepareTarget(ReplayTargetTask.PrepareRead);
    21.         // Serializer api
    22.         ReplaySnapshot snapshot = target.FetchSnapshot(0.5f);    // Find the snapshot for the 0.5 second mark
    23.    
    24.         foreach(ReplayIdentity id in snapshot.Identities)
    25.         {
    26.             // Get data for object with the target id
    27.             ReplayState state = snapshot.RestoreSnapshot(id);
    28.        
    29.             // Create an object serializer instance
    30.             ReplayObjectSerializer objectSerializer = new ReplayObjectSerializer();
    31.        
    32.             // Run deserialize
    33.             objectSerializer.OnReplayDeserialize(state);
    34.        
    35.             // Access component data - Similar properties exist for replay variables, events methods etc.
    36.             IList<ReplayComponentData> components = objectSerializer.ComponentStates;
    37.        
    38.             foreach(ReplayComponentData data in components)
    39.             {
    40.                 Debug.Log("Target replay component id: " + data.BehaviourIdentity);
    41.            
    42.                 // Get the serialize type for this component
    43.                 Type serializerType = ReplaySerializers.GetSerializerTypeFromID(data.ComponentSerializerID);
    44.            
    45.                 // CHeck for transform
    46.                 if(serializerType == typeof(ReplayTransformSerializer))
    47.                 {
    48.                     ReplayTransformSerializer transformSerializer = new ReplayTransformSerializer();
    49.                
    50.                     // Read the transform data
    51.                     data.ComponentStateData.PrepareForRead();
    52.                     transformSerializer.OnReplayDeserialize(data.ComponentStateData);
    53.                
    54.                     Debug.Log("Position: " + transformSerializer.Position);
    55.                     Debug.Log("Rotation: " + transformSerializer.Rotation);
    56.                     Debug.Log("Scale: " + transformSerializer.Scale);
    57.                 }
    58.             }
    59.         }
    60.     }
    61. }
    This API can work with any replay storage target in the same way.

    The ReplayFileTarget should work in editor although it is not something we have really tested. I would say that if you are using it from an editor script, then you need to be very aware of script reloads as this does not play well with background threads. The file target will spawn a streaming thread when the file is opened so that data can be streamed without blocking the main thread. If this thread does not get closed properly before a script reload, then I would expect unpredictable behaviour from the Unity editor. We are well aware that exiting play mode without cleaning up background threads can actually crash the editor which is why we added a sort of garbage collector feature for replay resources which will detect and release any hanging resources such as un-disposed replay files.
    So I would make sure that you call 'Dispose' on the file target before Unity will reload scripts.

    If you call the folllowing methods on the Replay File Target, does it return 'null' or a valid ReplaySnapshot instance?

    Code (CSharp):
    1. storageTarget.PrepareTarget(ReplayTargetTask.PrepareRead);
    2. ReplaySnapshot snapshot = storageTarget.FetchSnapshot(1);
    3.  
    4. Debug.Log(snapshot);
     
    julianr likes this.
  19. lorewap3

    lorewap3

    Joined:
    Jun 24, 2020
    Posts:
    58
    Thanks Scotty that was immensely helpful!

    You're right as soon as you said it made sense on the gameobjects. All you need is the component data and compression is key. Your example was very helpful in helping me understanding the file part of it. It's not as detailed in the documentation.

    But I got it mostly working! At least the concept:

    upload_2021-1-11_22-30-37.png

    Just need to tie it in with the prefabs somehow to get their name. Didn't have as much time to work on it today.

    I'm still getting some threading errors as I'd expect, as I'm not sure how I'm calling the Dispose method? I see it's on a ReplayFileTarget but I read the file into a ReplayStorageTarget like this:
    (I declare storageTarget as a member variable but showing here for brevity)
    Code (CSharp):
    1.  
    2. // Read replay file
    3. ReplayStorageTarget storageTarget = ReplayFileTarget.ReadReplayFile(replayFile);
    4.  
    5. // Make sure target is ready for read requests
    6. storageTarget.PrepareTarget(ReplayTargetTask.PrepareRead);
    7.  
    8. // Save stats
    9. replayDuration = storageTarget.Duration;
    10. replayMemoryUsage = storageTarget.MemorySize;
    11. replayCurrentSnapshot.Clear();
    12.  
    13. // Serializer api
    14. ReplaySnapshot snapshot = storageTarget.FetchSnapshot(seekPosition);    // Find the snapshot for the 0.5 second mark
    15.  
    16.                
    17. foreach (ReplayIdentity id in snapshot.Identities)
    18. {
    19.     // Get data for object with the target id
    20.     ReplayState state = snapshot.RestoreSnapshot(id);
    21.                    
    22.     // Create an object serializer instance
    23.     ReplayObjectSerializer objectSerializer = new ReplayObjectSerializer();
    24.  
    25.     // Run deserialize
    26.     objectSerializer.OnReplayDeserialize(state);
    27.  
    28.     // Access component data - Similar properties exist for replay variables, events methods etc.
    29.     IList<ReplayComponentData> components = objectSerializer.ComponentStates;
    30.  
    31.     // Save components for current object
    32.     replayCurrentSnapshot[id.IDValue] = components;
    33.  
    34. }
    35. //ReplayFileTarget.Dispose();
    36. //storageTarget.PrepareTarget(ReplayTargetTask.);
    This is in a function I'm only calling OnInspectorInit and whenever the seek bar value changes. That's where I need some extra error checking but not sure what yet.

    But if I could intelligently handle the replay files and dispose of using them as soon as I've read the snapshot data that would be a great start! Let me know what to do there please!

    Thanks guys this has been super helpful!
    Will
     
  20. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi again,
    Looks like you are getting there.

    You can resolve the replay identities to object names is required but you will need to have a ReplayScene instance for this. You could just use 'ReplayScene.FromCurrentScene()' I would imagine. Once you have a replay scene instance, it is just a case of looking up the associated ReplayObject like so:

    Code (CSharp):
    1. ReplayScene scene = ReplayScene.FromCurrentScene();
    2.  
    3. foreach(ReplayIdentity id in someIdentities)
    4. {
    5.     ReplayObject targetObject = scene.GetReplayObject(id);
    6.    
    7.     if(targetObject == null)
    8.         Debug.Log("Object does not exist in the current scene (Possibly a prefab instance)");
    9.     else
    10.         Debug.Log("Object name is: " + targetObject.gameObject.name);
    11. }
    Getting the associated prefab for missing replay objects (objects that have been instantiated or destroyed during recording) is a little more involved but is certainly achievable. First you will need to find out which objects are possibly prefab instances. The logic used by the replay system to determine this is objects that exist in the replay storage target at a given frame, but not in the specified replay scene. The object must also have a valid PrefabIdentity to be considered a replay prefab.Here is some example code that does the following:

    • Checks if a given ReplayIdentity is considered a prefab instance.
    • Attempts to resolve the prefab asset using the PrefabIdentity.
    • Fetches the 'initial data' for construction of the prefab instance. (Data such as position, rotation, parent etc.)
    Code (CSharp):
    1. public bool GetReplayPrefab(ReplayIdentity id, ReplaySnapshot snapshot, ReplayScene scene, out ReplayObject prefabObject, out ReplayInitialData initialInstanceData)
    2. {
    3.     prefabObject = null;
    4.     initialInstanceData = default;
    5.    
    6.     if(scene.GetReplayObject(id) == null)
    7.     {
    8.         bool found = false;
    9.        
    10.         foreach(ReplayIdentity storageID in snapshot.Identities)
    11.             if(id == storageID)
    12.                 found = true;
    13.            
    14.         if(found == true)
    15.         {
    16.             ReplayState state = snapshot.RestoreSnapshot(id);
    17.            
    18.             // Create an object serializer instance
    19.             ReplayObjectSerializer objectSerializer = new ReplayObjectSerializer();
    20.      
    21.             // Run deserialize
    22.             state.PrepareForRead();
    23.             objectSerializer.OnReplayDeserialize(state);
    24.            
    25.             // Get prefab id
    26.             ReplayIdentity prefabID = objectSerializer.PrefabIdentity;
    27.            
    28.             // Check for registered prefab asset
    29.             ReplayObject prefab = UltimateReplay.Settings.prefabs.GetReplayPrefab(prefabID);
    30.            
    31.             if(prefab == null)
    32.                 Debug.Log("Object does not have a prefab");
    33.             else
    34.             {
    35.                 Debug.Log("Object prefab is: " + prefab.gameObject.name);
    36.                 prefabObject = prefab;
    37.                 initialInstanceData = snapshot.GetReplayObjectInitialState(id);
    38.                
    39.                 if(initialInstanceData != null)
    40.                 {                  
    41.                     if((initialInstanceData.InitialFlags & ReplayInitialData.ReplayInitialDataFlags.Position) != 0)
    42.                         Debug.Log("Initial position: " + initialInstanceData.position);
    43.                    
    44.                     if((initialInstanceData.InitialFlags & ReplayInitialData.ReplayInitialDataFlags.Parent) != 0)
    45.                         Debug.Log("Initial parent: " + initialInstanceData.parentIdentity);
    46.                    
    47.                     for(int i = 0; i < initialInstanceData.observedComponentIdentities.Length; i++)
    48.                         Debug.Log("Observed component id: " + initialInstanceData.observedComponentIdentities[i]);
    49.                 }
    50.                
    51.                 return true;
    52.             }
    53.         }
    54.     }
    55.     return false;
    56. }
    As for calling dispose for file storage targets: Since you are using an inspector to display the UI, you should be able to cleanup all resouces in the Unity 'OnDsiable' event which will be calledbefore script reloads etc. so should be ideal.

    As you have mentioned, you can also just call Dispose immediatley after you have read all required data. This is probably not ideal though unless you have very small replay files or are not reading the entire file data.
     
  21. lorewap3

    lorewap3

    Joined:
    Jun 24, 2020
    Posts:
    58
    Scott,

    All that information is very helpful! The scene/prefab info will help me alot when I get to that point.

    But I'm still confused about the Dispose method you're talking about. Ok sure, I'll try adding the cleanup code to OnDisable, but I don't know exactly what I'm calling. I'm only working with a ReplayStorageTarget when I read in the file using
    Code (CSharp):
    1. // ReplayStorageTarget storageTarget          
    2. storageTarget = ReplayFileTarget.ReadReplayFile(replayFile);
    And then I read all the data as in the code in my previous post. But then at the end of the function I'm left with:

    Code (CSharp):
    1.  
    2.             // Does not have Dispose method as is a ReplayStorageTarget object
    3.             storageTarget.Dispose(); // Error
    4.            
    5.             // This does not take an argument and is an abstract function
    6.             //  And am calling as a static function in this case... which makes no sense and isn't marked as one.
    7.             // I have no ReplayFileTarget object... only a ReplayStorageTarget
    8.              ReplayFileTarget.Dispose(); // Error
    9.  
    That's all I can figure out from the documentation. Both of those lines of code are compilation errors.

    I must be missing something obvious and just don't see it. Please tell me how I'm supposed to call the Dispose method that you're talking about.

    Thanks!
    Will
     
  22. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    The Dispose method is only applicable to file storage targets so you will need to store the variable like this to have full access to any file related members:

    Code (CSharp):
    1. ReplayFileTarget storageTarget = ReplayFileTarget.ReadReplayFile(...);
    2.  
    3. storageTarget.Dispose();
    Alternativley, you can also cast the storage target like so, but personally I prefer the first option:

    Code (CSharp):
    1. (storageTarget as ReplayFileTarget).Dispose();
     
  23. lorewap3

    lorewap3

    Joined:
    Jun 24, 2020
    Posts:
    58
    Aah I see! Thanks alot I knew it was something silly! Didn't realize ReplayFileTarget derived from ReplayStorageTarget and didn't ocur to me to look. I changed it and it seems to be working just fine.

    It seems like ReplayScene has most of what I want to show in the inspector window, at least for the cursory glance. So what I did was add a ReplayScene member to the ReplayBridge ScriptableObject I made. The intent was I click the 'Create New Replay' button, it creates the SO and that's when I run the FromCurrentScene(), and it'd save them in the SO so I could just reference later. But I'm not sure it serializes properly or at all because ActiveReplayObjects is always null when the editor window reads it. You can see it's trying to draw it up at the top lol.
    upload_2021-1-12_21-5-26.png

    Is there a way to serialize it so it'll save correctly in the SO? I tried just adding the serializable tag to the member object but it gives me an error that it's not a valid declaration for that type.

    Should I create another container to hold the data? I feel like there's a way to save that data but I'm not well versed on serialization or how that stuff works in detail.

    Thanks again for all the help this has saved so much time and I really really appreciate it!
    Will
     

    Attached Files:

  24. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi again,
    The ReplayScene type is not serializable and you would have to store the data manually if you wanted to save it. It would probably be easier to store the scene name manually as a string and then you can create a ReplayScene instance when you load your window by loading a Unity scene with the matching name, and then calling 'ReplayScene.FromScene' which accepts a 'Scene' parameter. Alternativley, you could store the ReplayIdentity of each RepayObject that is registered in the ReplayScene. You can then find those ReplayObjects by ID on load and create a ReplayScene instance by manually adding replay objects.

    The 'ActiveReplayObjects' property should never return null because it is initialized to a new List in the field initializer. It is also never set to null anywhere else in the code base. If I had to guess, I would suggest that the Unity serialization may be causing issues here as the ReplayScene type is not marked as serializable.
     
  25. lorewap3

    lorewap3

    Joined:
    Jun 24, 2020
    Posts:
    58
    Apologies for the delay in response, I've been trying to figure out how I want to implement this, and still thinking about it. Saving the data manually along with the SO shouldn't be a problem. The hope is that eventually I can get to the point where I can click a load button in the editor window, and it will load the scene and everything needed to replay it while being intelligent enough to work with other replay scenes that've been loaded and will be playing at the same time. And also perhaps an easy mechanism to select characters from a newly built scene and click add them all to a recording.

    Thanks again for your help!
    Will
     
    scottyboy805 likes this.
  26. FIllbiker

    FIllbiker

    Joined:
    Jun 9, 2010
    Posts:
    118
    Hi, sorry for dumb question but I have troubles with implementing simple replay to pause menu.
    1) scene start (BeginRecording) - OK
    2) pause menu and click on Replay button (StopRecording and start replay) - OK
    3) exit replay (Stop Replay and Begin new recording) and go back to pause menu - OK
    4) resume and game continues but it is recorded as new and I need to only resume to previous recording and continue record to have all scene since start recorded.
    I looked into documentation and I can't see anything about what I need.

    I tried to use PauseRecording(recordHandle) instead of StopRecording in step 2) and then ResumeRecording(recordHandle) in step 3)

    Code (CSharp):
    1. public void ShowReplay() {
    2.             if (ReplayManager.IsRecording(recordHandle) == true)
    3.                  ReplayManager.PauseRecording(recordHandle);
    4.  
    5.             // Start playback
    6.             if (ReplayManager.IsReplaying(playbackHandle) == false)
    7.                 playbackHandle = ReplayManager.BeginPlayback(storageTarget, null, true);
    8. }
    9. public void ReplayGoLive() {
    10.             // Stop all playback
    11.             if (ReplayManager.IsReplaying(playbackHandle) == true)
    12.                 ReplayManager.StopPlayback(ref playbackHandle);
    13.             // Resume record
    14.             if (ReplayManager.IsRecording(recordHandle) == false)
    15.                 ReplayManager.ResumeRecording(recordHandle);
    16. }
    but after click on Replay button game normally continues instead of Starting Playback.
     
    Last edited: Jan 17, 2021
  27. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi,
    I am not sure if I understand fully, but pause/resume recording does indeed sound like what you need. The only thing I would add is that you also need to call 'StopRecording' before you call 'BeginPlayback', otherwise you will receive an error that the storage target is being used by another replay operation. Perhaps you could add a condition like this or similar depending on exactly what you want to do:

    Code (CSharp):
    1. public void ShowReplay()
    2. {
    3.     if(ReplayManager.IsRecording(recordHandle) == true)
    4.     {
    5.         if(ReplayManager.GetReplayStorageTarget(recordHandle).Duration < targetDuration)
    6.         {
    7.             ReplayManager.PauseRecording(recordHandle);
    8.         }
    9.         else
    10.         {
    11.             ReplayManager.StopRecording(ref recordHandle);
    12.            
    13.             ReplayManager.BeginPlayback(storageTarget, null, true);          
    14.         }
    15.     }
    16. }
     
  28. FIllbiker

    FIllbiker

    Joined:
    Jun 9, 2010
    Posts:
    118
    thank you. And what "targetDuration" should be?
     
  29. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    No problem.
    In this example, 'targetDuration' would just be a float variable which represents an amount of time in seconds. For example:

    Code (CSharp):
    1. float targetDuration = 5f; // 5 Seconds
    The recording would then end and begin playback once 5 seconds or more of data has been recorded, and the 'ShowReplay' method has been called.

    Of course, you can use any other condition too which better suits your needs if you are not wanting to end recording after a fixed time period.
     
  30. a-t-hellboy

    a-t-hellboy

    Joined:
    Dec 6, 2013
    Posts:
    180
    Is there any ways in the package to capture screenshot from .replay file for thumbnail ?
     
  31. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi,
    There is no built in way to do this so you would need to do that manually. It should not be too difficult to create a thumbnail just using the replay manager API. Here is some example code which will save a screenshot once recording has finished.

    Code (CSharp):
    1. class Example : MonoBehaviour
    2. {
    3.     IEnumerator Start()
    4.     {
    5.         ReplayHandle handle = ReplayManager.BeginRecording();
    6.        
    7.         // Allow some data to be recorded
    8.         yield return new WaitForSeconds(5f); // 5 seconds
    9.        
    10.         ReplayManager.StopRecording(ref handle);
    11.        
    12.        
    13.         // Create a thumbnail
    14.         handle = ReplayManager.BeginPlaybackFrame();
    15.        
    16.         // Select a random replay frame for the thumbnail
    17.         ReplayManager.SetPlaybackTimeNormalized(handle, Random.value);
    18.        
    19.         // Capture a screenshot
    20.         ScreenCapture.CaptureScreenshot("Replay.png");
    21.        
    22.         // Return to game mode
    23.         ReplayManager.StopPlayback(ref handle);
    24.     }
    25. }
     
    a-t-hellboy likes this.
  32. flomaaa

    flomaaa

    Joined:
    Apr 12, 2019
    Posts:
    24
    Hi scotty,

    Hope you are doing well. I'm recording an avatar and finger tracking through your replay system. Everything works smoothly with the HMD (HTC VIVE in my case) but WASD + Mouse to navigate through the scene in the game view seems to not work. This is quite obvious since the HMD blocks keyboard/mouse input, but at the point where I disconnect the HMD, the view is still locked and I can't change the camera perspective. I can see that the camera moves (according to the values in the inspector) but it immediately jumps back to 0,0,0. Do you have any idea what could cause this? It has to be a combination of the leap motion camera + the camera that is then used for the replay but I can't pinpoint the issue.

    cheers!
     
  33. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi,
    Do you see the text 'Free Cam Enabled' in the top right corner of the screen at any point? This will usually mean that you can move the camera around freely using WASD and should only be dislayed while in playback mode. The free cam behaviour is actually implented as part of the ReplayControls script and uses the InputManager to read input. The ReplayControls script will actually create a new camera component when entering playback which is then positioned to the same view perspective as the current rendering camera. You then controls this secondary camera using WASD rather than moving the main game camera which could be undesirable. It may be worth checking that this 'replay camera' is the active rendering camera (which it should be unless another camera becomes active). You can also check that this cameras transform values are updated as you move the camera using WASD. You can find this replay camera on the ReplayControls object, and is only created after you have entered playback mode.
     
  34. flomaaa

    flomaaa

    Joined:
    Apr 12, 2019
    Posts:
    24
    Hi, yes I can indeed see it! The replay disables some of my gameobjects so I cannot see the tracked hands anymore. After enabling them in the Hierarchy the camera jumps to the leap motion camera and doesn't let me navigate anymore. At this point the values of the replay camera (position/rotation) do change when I press the corresponding key but they jump back to 0,0,0 and the view does not change at all. That being said, I've just found the issue and also a workaround. The entire SteamVR object (incl. the leap motion) gets deactivated and enabling after replaying the scene doesn't help, instead, enabling it *before* replaying the recorded scene seems to work and doesn't break the free move camera.
     
  35. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Glad to hear you were able to find a workaround.
    If there are certain components that you do not want to be disabled during playback, then you can add them into the 'Ignore Component Types' collection in the settings window, under 'State Preparation'. This will prevent the replay system from modifing the components when entering and exiting playback mode.

    Let me know if there is anything else.
     
  36. flomaaa

    flomaaa

    Joined:
    Apr 12, 2019
    Posts:
    24
    Cheers! I'm still using the old version because I wrote some own Replay Scripts (Mesh, Text) that I'd like to keep :)
     
    scottyboy805 likes this.
  37. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,817
    G'day! Finally got a chance to check out Ultimate Replay 2 :)

    I'm having a little trouble setting things up though, I have a character that is animated via Animancer Pro - https://assetstore.unity.com/packages/tools/animation/animancer-pro-116514. I don't know much about the inner workings but it uses Playables (I think)...
    Currently I just added transform components to each bone and main rig component, but I haven't had much luck getting anything to play back.

    I'm going right through the manual now but I thought I'd just ask if there's there anything I should know about setting up a character like this?

    P.S. I should mention that my character is added to the scene at runtime so I'm adding the replay transforms then.

    Thanks,
    Pete
     
  38. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,817
    Yeay! I got it working. For some reason it was having trouble if I added the components at runtime. So I've intercepted the FBX on import with the OnPostprocessModel and I'm adding the components there which seems to be working.
    It's really great!!

    I have one other problem though, I can record to a storage file but it won't seem to play it back if I just call the "TestPlayback" method in this script. Strange though because the "TestRecordAndPlayback" works. :confused: Maybe something isn't being initialised?
    Could you have a look at this script and see if there's anything obviously wrong?

    Code (CSharp):
    1.     void Start()
    2.     {
    3.         //Set Storage Path
    4.         applicationPersistentDataPath = Application.persistentDataPath + "/";
    5.         storageFilePath = applicationPersistentDataPath + file + extension;
    6.  
    7.         TestRecordAndPlayback();
    8.         // TestPlayback();
    9.     }
    10.  
    11.     void TestRecordAndPlayback()
    12.     {
    13.         Invoke(nameof(StartRecording), 1);
    14.         Invoke(nameof(StopRecording), 5);
    15.         Invoke(nameof(Play), 6);
    16.     }
    17.  
    18.     void TestPlayback()
    19.     {
    20.         Invoke(nameof(Play), 1);
    21.     }
    22.  
    23.     public void StartRecording()
    24.     {
    25.         storageFile = ReplayFileTarget.CreateReplayFile(storageFilePath);
    26.         recordHandle = ReplayManager.BeginRecording(storageFile, ReplayScene.FromScene(SceneManager.GetSceneByName("Level")), false, true);
    27.     }
    28.  
    29.     public void StopRecording()
    30.     {
    31.         ReplayManager.StopRecording(ref recordHandle);
    32.     }
    33.  
    34.     public void Play()
    35.     {
    36.         storageFile = ReplayFileTarget.ReadReplayFile(storageFilePath);
    37.         playbackHandle = ReplayManager.BeginPlayback(storageFile,ReplayScene.FromScene(SceneManager.GetSceneByName("Level")), true);
    38.     }
    Thanks!
    Pete
     
  39. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi,
    Glad to hear you fixed the initial problem. I am not sure why adding components are runtime would be an issue. The only thing I would say is that you would also need to add a ReplayObject component manually if doing it in-game, as the auto-adding is an editor workflow feature only. In addition to that, you may also need to call 'ReplayObject.RebuildComponentList' to make sure replay components are setup correctly. Here is a quick example:

    Code (CSharp):
    1. void Start(GameObject recordObject)
    2. {
    3.     ReplayObject ro = recordObject.AddComponent<ReplayObject>();
    4.    
    5.     recordObject.AddComponent<ReplayTransform>();
    6.     recordObject.AddComponent<ReplayAnimator>();
    7.    
    8.     ro.RebuildComponentList();
    9. }
    It looks like you found another way though so that may be easier.

    As for your playback issue:
    That code looks fine to me and I cannot see any obvious reason why it would not work as expected. Maybe set a break point and step into the 'FromScene' method call to make sure that it is actually finding and adding the expected game objects from the specified scene name. Are you using replay prefabs that you spawn in when the scene loads? If so, are they registered correctly in the replay settings? It could be that running the record first creates these prefab instances so that they already exist when entering playback mode, if that makes sense? Another thing to try is to make your 'Start' method return 'IEnumerator' and then add the following line to the top of the method, just for testing purposes:

    Code (CSharp):
    1. yield return new WaitForSeconds(0.5f);
    It may be that something isnt quite initialized on the first frame.
     
    petey likes this.
  40. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,817
    Awesome! Thanks for that.
    Just wondering, is there a way to specifically set a replay identity for an object?
    I want to record a replay from one object and play it back on another object like you mentioned in the ghost car example with - bool ReplayObject.CloneReplayObjectIdentity(ReplayObject, ReplayObject)
    But in this case I won't have the original object in the same scene. Is that possible?

    I tried a little experiment but it didn't work :(
    Code (CSharp):
    1.         if (state == RecordState.Record)
    2.         {
    3.             //Prepare Source Object
    4.             ReplayObject sourceReplayObject = sourceObject.GetComponent<ReplayObject>();
    5.             sourceReplayObject.ReplayIdentity = new ReplayIdentity(12345);
    6.             replayScene = new ReplayScene();
    7.             replayScene.AddReplayObject(sourceReplayObject);
    8.          
    9.             yield return new WaitForSeconds(0.1f);
    10.             StartRecording();
    11.          
    12.             yield return new WaitForSeconds(2f);
    13.             StopRecording();
    14.  
    15.         }
    16.         else if (state == RecordState.Playback)
    17.         {
    18.             //Prepare Target Object
    19.             ReplayObject targetReplayObject = targetObject.GetComponent<ReplayObject>();
    20.             targetReplayObject.ReplayIdentity = new ReplayIdentity(12345);
    21.             replayScene = new ReplayScene();
    22.             replayScene.AddReplayObject(targetReplayObject);
    23.          
    24.             yield return new WaitForSeconds(1f);
    25.             Play();
    26.         }
     
  41. lorewap3

    lorewap3

    Joined:
    Jun 24, 2020
    Posts:
    58
    Hi Scotty,

    If you can I'd like some clarification on an error I'm getting:
    Code (CSharp):
    1. InvalidOperationException: File operations cannot be awaited due to the current state of the file streamer: Stream thread is not running
    2. UltimateReplay.Storage.ReplayStreamTarget.WaitForSingleTask (UltimateReplay.Storage.ReplayStreamTaskID taskID) (at Assets/Plugins/Recording Plugins/Ultimate Replay 2.0/Scripts/Storage/Stream/ReplayStreamTarget.cs:892)
    It's occuring right at the last line below that starts the recording:
    Code (CSharp):
    1.  
    2. // Current timestamp
    3. replayTimestamp = DateTime.Now;
    4. screenplay.replayBridges[replayTimestamp] = new List<ReplayBridge>();
    5.  
    6. if (replayHandles == null)
    7.     replayHandles = new List<ReplayHandle>();
    8. else
    9.     replayHandles.Clear();
    10.  
    11. for (int i = 0; i < EditorSceneManager.sceneCount; i++)
    12. {
    13.     Scene currentScene = EditorSceneManager.GetSceneAt(i);
    14.     if (!currentScene.name.Contains(OG.Instance.gameManagerSceneName))
    15.     {
    16.         // Get Replay Scene from current scene
    17.         ReplayScene replayScene = ReplayScene.FromScene(currentScene);
    18.  
    19.         // Create ReplayBridge from current scene
    20.         ReplayBridge replayBridge = ReplayBridge.CreateReplayBridge(currentScene, replayTimestamp);
    21.         screenplay.replayBridges[replayTimestamp].Add(replayBridge);
    22.  
    23.         // Create Storage Target
    24.         ReplayFileTarget replayStorageTarget = ReplayFileTarget.CreateReplayFile(replayBridge.replayFile);
    25.  
    26.         // Begin Recording
    27.         replayHandles.Add(ReplayManager.BeginRecording(replayStorageTarget, replayScene, true, true));
    28.     }
    29. }
    The ReplayBridge is an SO I'm using to store metadata about the recording. As I mentioned about a month ago when you helped me out is my use case is more for content creation. The goal is the ability to load smaller scenes additively, and while each of those recordings start at the same time, they record only objects in their scene.

    Above is code within a MonoBehavior. In the Start function I'm calling the function that does the code above. It iterates through all loaded scenes, and for all but the special 'GameManagers' scene it creates ReplayBridge SO, creates the ReplayStorageTarget, and starts the recording. It adds the returned ReplayHandle to a list so it can stop them all in OnDisable (or when exiting Play mode)

    That's the idea anyway. But this error is confusing and I'm not sure what it means or how to go about solving it. Any advice would be most appreciated!

    Thanks!
    Will
     
  42. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi again,
    Yes, it is possible to manually set the identity of a replay object to be that of another object. The code you posted will not work though because there is more than just the replay object identity that needs to be modified. Changing the replay object identity will cause replay data to be rerouted to that object, but each replay component must also have its identity modified otherwise the data will not match a target component, and will be ignored. You can take a look at the source code for the 'CloneReplayObjectIdentity' method to see how it works:

    Code (CSharp):
    1. public static bool CloneReplayObjectIdentity(ReplayObject cloneFromObject, ReplayObject cloneToObject)
    2.         {
    3.             // Check for same object
    4.             if (cloneFromObject == cloneToObject)
    5.                 return false;
    6.  
    7.             // Replay components must match
    8.             if (cloneFromObject.observedComponents.Count != cloneToObject.observedComponents.Count)
    9.                 return false;
    10.  
    11.             // Clone object id
    12.             cloneToObject.replayIdentity = new ReplayIdentity(cloneFromObject.replayIdentity);
    13.  
    14.             for(int i = 0; i < cloneFromObject.observedComponents.Count; i++)
    15.             {
    16.                 // Check for destroyed components
    17.                 if (cloneToObject.observedComponents[i] == null || cloneFromObject.observedComponents[i] == null)
    18.                     continue;
    19.  
    20.                 // Clone component id
    21.                 cloneToObject.observedComponents[i].ReplayIdentity = new ReplayIdentity(cloneFromObject.observedComponents[i].ReplayIdentity);
    22.             }
    23.  
    24.             return true;
    25.         }
    Notice that each observed replay component also has its identity cloned. For your example code, you would also need to know the identities of all observed components in order to update them as required.

    I hope this helps you.
     
    petey likes this.
  43. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi Will,
    This error means that a background streaming thread required by the storage target is not running and as a result, the file operation (usually fetching data) cannot wait for completion because it would cause an infinite loop. The streaming thread not running error is almost always caused when the background thread crashes for whatever reason, in which case, there should be an additional warning or error message with more detail. Are there any other such messages?
     
  44. lorewap3

    lorewap3

    Joined:
    Jun 24, 2020
    Posts:
    58
    Hi Scotty! There was another error that was showing up "infinite loop encountered. Asset is re-imported multiple times between changes". Something to that effect and listing the first .replay file. I'm not getting that error anymore, though, otherwise I'd paste it here.

    I'm pretty sure I'm not handling managing the threads properly. The ReplayBridge SO had some internal functions to read the storage target and display some basic stats about the ReplayObjects found within. It worked when I finished it a month ago, but with all these editor windows I'm betting they're clobbering each other some how. Anyway, I removed all that code from the SO so it's barebones and just stores

    Code (CSharp):
    1. public SceneAsset scene;
    2. public string replayFile;
    3. public DateTime timestamp;
    4. public string Notes;
    My test case has two loaded scenes (besides GameManager one). From the code in my previous post, the exact point at which the exception occurs is on the creation/start of the 2nd recording, so the 2nd iteration of the loop. (CaversIsland && GrasslandIsland are the two test cases)
    upload_2021-2-6_11-36-2.png
    It creates the 2nd SO, then croaks when starting the 2nd recording.

    As for additional exceptions, I'm not getting the infinite loop one anymore (my assumption is the ReplayBridge was interfering), but am getting
    Code (CSharp):
    1. ObjectDisposedException: Cannot access a disposed object.
    2. Object name: 'File target has been disposed'.
    3. UltimateReplay.Storage.ReplayFileBinaryTarget.CheckDisposed () (at Assets/Plugins/Recording Plugins/Ultimate Replay 2.0/Scripts/Storage/File/ReplayFileBinaryTarget.cs:109)
    when stopping play mode. But no doubt that's being caused by the ReplayFileTarget failing to be created for the 2nd scene:
    Code (CSharp):
    1. void OnDisable()
    2. {
    3.     if (playableDirector != null)
    4.         playableDirector.Stop();
    5.     if (ReplayManager.IsRecordingAny)
    6.     {
    7.         replayHandles.ForEach(u => ReplayManager.StopRecording(ref u));
    8.     }
    9.                
    10. }
    Edit:
    The infinite import loop error came back. Here's the details:
    Code (CSharp):
    1. An infinite import loop has been detected. The following Assets were imported multiple times, but no changes to them have been detected. Please check if any custom code is trying to import them:
    2. Assets/Resources/Replays/CavernsIsland_2021-02-06_113954.replay
    3.  

    I'm still definitely confused. The ReplayBridge SO only saves the .replay filename as a string. The .replay file is only actually created within that loop above, so I'm really confused as to what else is importing it. The error also doens't occur everytime, it seems to be like 20-30% of the time, although it fails to create the .replay pretty consistently.

    I feel like I'm trying to start too many recordings in the same function. Maybe it needs to be an Enumerator function that waits for the ReplayManager to be free again to create the 2nd replay?

    My apologies for such a long post! Thank you so much for your advice Scotty!

    Will
     
  45. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi again,
    I am not sure what would be causing the import error.
    There is nothing in our asset such as custom importers which manages replay files as assets. If you move the replay file outside of the assets folder to a path such as '<projectfolder>/Replays/CavernsIsland_2021-02-06_113954.replay', does this make any difference? .replay files should be treated as TextAssets by Unity but perhaps the .replay extension has a special meaning to Unity and it is treating it as something else?
     
  46. flomaaa

    flomaaa

    Joined:
    Apr 12, 2019
    Posts:
    24
    Hi @scottyboy805, is there a way to trigger something at the end of the playback in the 1.0 version of ultimate replay? e.g., just print something after the playback has reached its end. This seems to be straightforward with the listener in 2.0, but how can this be achieved in 1.0?
     
  47. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi,
    In version 1.0 (The original asset) there is no built in way to do this. Your best bet would be to get the duration of the replay from the storage target and then wait for that amount of time to pass in a coroutine or similar.
    Ultimate Replay 2.0 has the 'AddPlaybackEndListener' method for such things.
     
    Fibonaccov likes this.
  48. flomaaa

    flomaaa

    Joined:
    Apr 12, 2019
    Posts:
    24
    Alright, that's what I thought. Thanks!
     
  49. lorewap3

    lorewap3

    Joined:
    Jun 24, 2020
    Posts:
    58
    Hi @scottyboy805 ,

    I'm sure you were hoping I was gone for good, but no just on vacation :) I'm back trying to wrestle this replay system into submission. I believe I figured out the infinite import issue. Or, at least, I figured out how to make it go away. It was being caused by accessing the ReplayBridge SO directly for getting the replay file string. I modified ReplayControls (my version) to read the SOs in Awake only, and that seemed to alleviate that issue.

    But I'm having some major issues with concurrent replays going on simultaneously. I really don't think it's my code at this point, but rather the seek functionality. Or maybe just more exception catching is necessary. Forgive the huge screenshot but it helps illustrate my point:

    upload_2021-2-19_18-2-31.png

    I don't encounter any problems with a single replay. The issue is when I have two (or more). Both of the replay files are valid. I was able to play them both all the way through, and even scrub to various timestamps. But it's very inconsistent. Most of the time within a few seconds of playing or when I scrub, I get
    Code (CSharp):
    1. IOException: Corrupted data ReadInternal
    2. System.IO.Compression.DeflateStreamNative.CheckResult (System.Int32 result, System.String where) (at <ef151b6abb5d474cb2c1cb8906a8b5a4>:0)
    and
    Code (CSharp):
    1. An exception caused the 'ReplayFileTarget' to fail (file stream thread : 38)
    2. UnityEngine.Debug:LogError(Object)
    3. UltimateReplay.Storage.ReplayStreamTarget:StreamThreadMain() (at Assets/Plugins/Recording Plugins/Ultimate Replay 2.0/Scripts/Storage/Stream/ReplayStreamTarget.Threading.cs:119)
    4. System.Threading.ThreadHelper:ThreadStart()
    5.  
    Sometimes I can even unpause after the exception and it'll continue playing for 10+ seconds before another exception, always either the Corrupted data ReadInternal error or this error:
    Code (CSharp):
    1. InvalidOperationException: File operations cannot be awaited due to the current state of the file streamer: Stream thread is not running
    2. UltimateReplay.Storage.ReplayStreamTarget.WaitForSingleTask (UltimateReplay.Storage.ReplayStreamTaskID taskID) (at Assets/Plugins/Recording Plugins/Ultimate Replay 2.0/Scripts/Storage/Stream/ReplayStreamTarget.cs:892)
    which makes sense as you told me before that of course I'll get this error because the Stream thread crashed on the previous exception.

    It just seems to me that the system is very fault intolerant. While writing this I got the replays to play halfway through, past previous parts when the exception occurred. Maybe I can add some logic to recover when the Corrupted Data error is encountered? Here are my settings I don't think they are extreme:
    upload_2021-2-19_18-14-36.png
    I'm only recording 20 FPS. I have 5 replayobjects in one scene and 3 in the other. I don't feel like I should be taxing the system at all.

    My only guess is that the ReplayManager is having issues with two simultaneous replays going at the same time, or at least when scrubbing is involved. As I said earlier, I can remove either one of those replays and the other will play just perfectly. I can go nuts scrubbing up and down and I have no issues with either. But when it tries to do both at the same time it just fails with an exception 95% of the time.

    I just tested again with single replays and they work fine. I tested with both afterwards, and surprisingly it did work with both, for a little while, even with scrubbing. But about 10 seconds of that and it croaked with the same exceptions mentioned above. I mean I could understand if the ReplayControls script you guys provide is a courtesy and not a reliable replay scrubbing mechanism, but that's exactly what I need. I plan on coding functionality into a timeline to scrub these replays and it needs to be durable enough to handle at least 2 replays, but most likely at least a few more.

    I can provide my ReplayControls script if you'd like. I tried to change as little as possible outside of making all the record/play code into for loops to act on all replays set in the inspector. Or if you can build a test project with a modified ReplayControls (your own version) that can do multiple scenes simultaneously (and scrubs flawlessly) I can use that and work backwards. In general I think my use case doesn't need to be as precise and granular to 60fps as the system was designed for. If it dropped a frame here or there because of limitations in unity, ultimate replay, and how threads work in unity, I can live with that, I just need to get rid of these super annoying exceptions.

    Any advice/assistance you can provide is very much appreciated as always!
    Will
     
  50. scottyboy805

    scottyboy805

    Joined:
    Apr 10, 2013
    Posts:
    1,192
    Hi again,
    We have not seen that particular exception about corrupted data before, so I don't know what could be causing it at the moment. If you have a project that can replicate the exception consistently then we would be interested in taking a look if you wouldn't mind sharing. If so, could you send the project to 'info(at)trivialinteractive.co.uk' and I can look into it asap.Is this using 2 separate replay files or 2 replay instances for the same file out of interest?