[Released] Ultimate Replay 3.0 - Next generation state based replay system (Killcam, Ghost, +More)

Hey, are you using the beta version still since you posted on the beta thread? There were a few bugs fixed regarding changing replay identities but all should be good now if you are using the store version. If not then could you let me know which Unity version you are using along with some steps to reproduce the problem. The id of all replay components should remain fixed when in edit mode unless there is some conflict.

I will check that out also. Just to be clear, what sort of issues are you seeing with playback direction? Are there some issues with the replay smoothness, or buffering, or anything else to look out for?

Hi.
As I understand it, parameter Instantiate Identity did not work. Everything seems to be working fine after the update.
I manage to start playback in the opposite direction only under the condition of playbackEndBehaviour.LoopPlayback. Is this how it should be? If yes, then there are problems with intercepting the playback.OnPlayBackEnd event. It seems illogical to me.

Strange that the parameter did not seem to work, but glad to hear everything seems to be ok after the update.
For the playback direction issue: Your understanding is correct that it should work no matter what the playback end mode is set to, so it sounds like there could be a bug or something else going on there. I can check that out tomorrow and see if I can get to the bottom of the issue and I will let you know my findings.

Hey, I had some time to look into this and it looks like there is indeed a bug with reverse playback for anything other than LoopPlayback end behaviour. It has been fixed now and will be part of the next update.
The fix was quite simple though so in case you are interested and you need the changes sooner, I will post the changes required to the source code to fix that:
Source Changes 9644912--1371788--Screenshot 2024-02-15 095309.png

Thanks.

Hi.
I think the program has a memory leak problem. I can’t say where or why, but errors and warnings pop up almost constantly:
55 6c 74 69 6d 61 74 65 20 52 65 70 6c 61 79 2f Ultimate Replay/
55 70 64 61 74 65 20 52 65 70 6c 61 79 20 43 6f Update Replay Co
TLS Allocator ALLOC_TEMP_TLS, underlying allocator ALLOC_TEMP_MAIN has unfreed allocations, size 41
…
This happens in Edit Mode if there are objects with Replay Object components. I suspect it has something to do with prefabs, but I don’t know exactly how.

PS. The problems started when the asset was loaded without examples. Ultimate Replay Settings did not contain prefabs (null). The constant errors that I wrote about earlier (Objects are trying to be loaded during a domain backup. This is not allowed as it will lead to undefined behaviour!) disappeared only after deleting the Library folder. But these appeared.

Hey, thanks for reporting this behaviour. Can’t say I have see this personally, but can you let me know which unity version this is and I can do some testing. Just for your information we were finally able to reproduce and fix the loading during domain backup error, and the fix will be included in the next update (3.1.0). Maybe these issues are related so may be worth waiting for the update to see if there is any change. If all is well it should be live before the end of the week.

I tested in Unity 2023.2.9-11

Hi.
I tried to make a cyclic Replay and again found incorrect behavior. You can see the problem if you enable Loop and Replay at the same time in your cube example.
The same mysterious behavior can be seen in code like this:

_replayController.OnPlayBackLooped += OnPlayBackLooped;
........
_replayController.Looped = true;
........
private void OnPlayBackLooped()
{
     _replayController.ReplayBeginPlayback();

     switch (_replayController.Direction)
     {
         case UltimateReplay.PlaybackDirection.Forward:
             _replayController.SeekPlayback(1f);
             _replayController.Direction = UltimateReplay.PlaybackDirection.Backward;
             _replayController.SetPlaybackSpeed(1.0f);
             break;
         case UltimateReplay.PlaybackDirection.Backward:
             _replayController.SeekPlayback(0f);
             _replayController.Direction = UltimateReplay.PlaybackDirection.Forward;
             _replayController.SetPlaybackSpeed(1.0f);
             break;
     }

Hey, not sure if it was intended but the code that you posted looks like is starts a new playback operation every time the replay is looped. I may be wrong but I am guessing that ReplayBeginPlayback starts a new replay internally using ReplayManager.BeginPlayback(...)? The replay system will not end playback when the replay is looped so if the code above does what I think it does, it will start a new replay every time the replay reaches the end, so you will end up with multiple replays trying to affect the same replay objects. Maybe I am wrong and I would expect a playback error in such as case, but let me know if that is not what is happening and i can check it out.

That’s right, _dreplaycontroller.Replay Begin Playback(); should be outside the method, but that’s not the problem. After the second cycle, the motion fails when playback starts. Try to enable Loop and Back at the same time in your example about cubes and you will see an error.

PS. By the way, _dreplaycontroller.Replay Begin Playback(); remained after attempts to intercept the On PlayBack End event (not Loop). You are freeing up resources very tightly and restarting the playback becomes difficult. I think there are reasons for this, but it wasn’t immediately obvious to me (I had to read your code).

Thanks for the extra info.
I have done some testing using similar code with backward and looped mode used together but have not been able to generate any error and playback loops to be working just as expected. Not sure if I am understanding the issue but maybe if possible you can share the error message you are getting and also a full code sample that gives the error as that would be a big help (You can email that if willing/able and if that is easier for you: info(at)trivialinteractive.co.uk).
I guess you are trying to create a ping pong effect based on the code you posted? Here is the code I used for testing purposes so maybe you can spot where the differences are:
Test Code

using System.Collections;
using System.Collections.Generic;
using UltimateReplay;
using UltimateReplay.Storage;
using UnityEngine;

public class TestReversePlayback : MonoBehaviour
{
    IEnumerator Start()
    {
        ReplayMemoryStorage mem = new ReplayMemoryStorage();

        ReplayRecordOperation op = ReplayManager.BeginRecording(mem);
        yield return new WaitForSeconds(2);
        op.StopRecording();


        // Start replaying
        ReplayPlaybackOptions options = new ReplayPlaybackOptions
        {
            PlaybackEndBehaviour = PlaybackEndBehaviour.LoopPlayback,
        };
        ReplayPlaybackOperation playback = ReplayManager.BeginPlayback(mem, null, options);

        playback.SeekPlaybackNormalized(1f);
        playback.PlaybackDirection = PlaybackDirection.Backward;


        playback.OnPlaybackLooped.AddListener(() =>
        {
            if(playback.PlaybackDirection == PlaybackDirection.Forward)
            {
                playback.SeekPlaybackNormalized(1f);
                playback.PlaybackDirection = PlaybackDirection.Backward;
            }
            else
            {
                playback.SeekPlaybackNormalized(0f);
                playback.PlaybackDirection = PlaybackDirection.Forward;
            }
        });
    }
}

Thanks for the videos, I can see the issue now.
It looks like the problem only occurs when using file storage so sounds like there is some bug there. Using memory storage it seems to work just as expected with no flickering or strange beahviour. Currently looking into the problem now so I will let you know when there is any progress.

Hi. I also noticed that if the object is inactive at the beginning of recording, then the first 2-2.5 seconds are recorded jerks - they can be seen on the video.

Hey, sorry for the delay, I have only now had chance to look into this.
I spent some time creating a repro project which attempts to replay an object that gets disabled before the replay starts and remains that way for a few frames just to be sure (Also tried many other variations on this). So far I have not had any luck reproducing the jittering shown in the video in a simple repro project.
I am wondering if there could be something specific to the game causing such a behaviour? For example some script or animation component for example that remains enabled during the replay which could potentially be fighting the object position for a few frames? I am not expecting anything like that but it might be worth checking since I have not had any luck getting it to show up in a minimal project. If all appears to be well do you have any more tips for replicating the issue, or perhaps if you have time you could also try to create a minimal repro project that exhibits this behaviour and I will be happy to take a look. It is clear there is an issue somewhere, but it will be very difficult to track down if I cannot reproduce it. Maybe if you are a coder you could try attaching the debugger on you project and step through to see what could be happening? Usual suspects will be things like the ReplayTransform component itself and probably replay snapshot deserialization to see what the data looks like. No worries if not as it requires some understanding of coding and how the asset works internally but just a thought.

I have been trying to get the ghost car to work in my own game. I have basically reused your demo code but changed it to be a time trial instead of a lap. It works if I edit your demo script, but on my own script it is not working. Any idea on how to fix?

The changes I made was taking your OnTriggerEnter and adding most of that to start. I also took out const from [SerializeField] private string prefsKeyBestLap and the other const as I wanted to be able to use different names for different courses.

I am using a copy of the same car. Just the ghost one has no colliders and is it’s own prefab. Below is the error I get.

Stream storage is not ready to accept write calls. You must prepare the storage target for writing first!

Hey, if that is all you changed and you are getting an error about writing then something strange is happening. I will need more info to look into it further so if possible can you DM me with the modified version of the script so I can do some testing on my end. The error seems very strange and normally means you are trying to save data to some replay storage which has not been prepared for recording via a BeginRecording call.

So I went back and used your same script. I have just changed the start of the OnTriggerEnter to use my car and control instead of the demo one. I still have the same error and two others I didn’t see before. Maybe I am not setting up the car for the replay correctly? I have a replay object and replay transform on the parent. A replay transform on each wheel. Same with the ghost car. It works fine with your demo car.

Other errors that show once:
IOException: Sharing violation on path C:\Users*****\Desktop\Games\Unity\Games\Driver New\Driver\current.replay
System.IO.FileStream…ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize, System.Boolean anonymous, System.IO.FileOptions options) (at :0)
System.IO.FileStream…ctor (System.String path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, System.Int32 bufferSize) (at :0)
(wrapper remoting-invoke-with-check) System.IO.FileStream…ctor(string,System.IO.FileMode,System.IO.FileAccess,System.IO.FileShare,int)
System.IO.File.Create (System.String path, System.Int32 bufferSize) (at :0)
System.IO.File.Create (System.String path) (at :0)
UltimateReplay.Storage.ReplayStreamSource_FromFile.OpenForWriting () (at Assets/Ultimate Replay 3.0/Scripts/Runtime/Storage/Stream/Source/ReplayStreamSource_FromFile.cs:72)
UltimateReplay.Storage.ReplayStreamSource.OpenWrite () (at Assets/Ultimate Replay 3.0/Scripts/Runtime/Storage/Stream/Source/ReplayStreamSource.cs:72)
UltimateReplay.Storage.ReplayStreamStorage.PrepareStreamWrite () (at Assets/Ultimate Replay 3.0/Scripts/Runtime/Storage/Stream/ReplayStreamStorage.cs:464)
UltimateReplay.Storage.ReplayStreamStorage.Prepare (UltimateReplay.Storage.ReplayStorageAction mode) (at Assets/Ultimate Replay 3.0/Scripts/Runtime/Storage/Stream/ReplayStreamStorage.cs:350)
UltimateReplay.Storage.ReplayFileStorage.Prepare (UltimateReplay.Storage.ReplayStorageAction mode) (at Assets/Ultimate Replay 3.0/Scripts/Runtime/Storage/File/ReplayFileStorage.cs:243)
UltimateReplay.ReplayRecordOperation.BeginRecording (System.Boolean cleanRecording) (at Assets/Ultimate Replay 3.0/Scripts/Runtime/ReplayRecordOperation.cs:197)
UltimateReplay.ReplayManager.BeginRecording (UltimateReplay.Storage.ReplayStorage storage, UltimateReplay.ReplayScene recordScene, System.Boolean cleanRecording, UltimateReplay.ReplayRecordOptions recordOptions) (at Assets/Ultimate Replay 3.0/Scripts/Runtime/ReplayManager.cs:316)
UltimateReplay.ReplayManager.BeginRecording (UltimateReplay.Storage.ReplayStorage storage, UltimateReplay.ReplayObject recordObject, System.Boolean cleanRecording, UltimateReplay.ReplayRecordOptions recordOptions) (at Assets/Ultimate Replay 3.0/Scripts/Runtime/ReplayManager.cs:252)
UltimateReplay.Demo.GhostCarReplay.OnTriggerEnter (UnityEngine.Collider other) (at Assets/Ultimate Replay 3.0/Demo/ExampleGame/GhostCarExample/Scripts/Replay/GhostCarReplay.cs:160)

NullReferenceException: Object reference not set to an instance of an object
UltimateReplay.Storage.ReplaySnapshot+ReplayObjectCreatedData.FromReplayObject (System.Single timeStamp, UltimateReplay.ReplayObject obj) (at Assets/Ultimate Replay 3.0/Scripts/Runtime/Storage/ReplaySnapshot.cs:230)
UltimateReplay.ReplayScene.CaptureSnapshot (System.Single timeStamp, System.Int32 sequenceID, UltimateReplay.Storage.ReplayPersistentData persistentData) (at Assets/Ultimate Replay 3.0/Scripts/Runtime/ReplayScene.cs:462)
UltimateReplay.ReplayRecordOperation.ReplayRecordUpdate (System.Single time) (at Assets/Ultimate Replay 3.0/Scripts/Runtime/ReplayRecordOperation.cs:166)
UltimateReplay.ReplayRecordOperation.ReplayTick (System.Single delta) (at Assets/Ultimate Replay 3.0/Scripts/Runtime/ReplayRecordOperation.cs:156)
UltimateReplay.ReplayOperation.ReplayTickUpdate (System.Single deltaTime) (at Assets/Ultimate Replay 3.0/Scripts/Runtime/ReplayOperation.cs:141)
UltimateReplay.ReplayManager.ReplayTick (System.Single deltaTime, UltimateReplay.ReplayUpdateMode updateMode) (at Assets/Ultimate Replay 3.0/Scripts/Runtime/ReplayManager.cs:195)
UltimateReplay.ReplayManager.Update () (at Assets/Ultimate Replay 3.0/Scripts/Runtime/ReplayManager.cs:96)

Then this error happens on repeat.
InvalidOperationException: Stream storage is not ready to accept write calls. You must prepare the storage target for writing first!
UltimateReplay.Storage.ReplayStreamStorage.StoreSnapshot (UltimateReplay.Storage.ReplaySnapshot state) (at Assets/Ultimate Replay 3.0/Scripts/Runtime/Storage/Stream/ReplayStreamStorage.cs:338)
UltimateReplay.Storage.ReplayFileStorage.StoreSnapshot (UltimateReplay.Storage.ReplaySnapshot state) (at Assets/Ultimate Replay 3.0/Scripts/Runtime/Storage/File/ReplayFileStorage.cs:221)
UltimateReplay.ReplayRecordOperation.ReplayRecordUpdate (System.Single time) (at Assets/Ultimate Replay 3.0/Scripts/Runtime/ReplayRecordOperation.cs:169)
UltimateReplay.ReplayRecordOperation.ReplayTick (System.Single delta) (at Assets/Ultimate Replay 3.0/Scripts/Runtime/ReplayRecordOperation.cs:156)
UltimateReplay.ReplayOperation.ReplayTickUpdate (System.Single deltaTime) (at Assets/Ultimate Replay 3.0/Scripts/Runtime/ReplayOperation.cs:141)
UltimateReplay.ReplayManager.ReplayTick (System.Single deltaTime, UltimateReplay.ReplayUpdateMode updateMode) (at Assets/Ultimate Replay 3.0/Scripts/Runtime/ReplayManager.cs:195)
UltimateReplay.ReplayManager.Update () (at Assets/Ultimate Replay 3.0/Scripts/Runtime/ReplayManager.cs:96)

Hey, from the IO exception that you added it looks like the replay file is not getting closed properly, or is possibly already open at the time you are trying to record. It can only really be caused by the replay storage not being disposed when finished with (And not calling StopRecording for example), or another possibility is that you are trying to start replaying when you have not yet stopped recording.
It sounds like you have not modified the script too much, but I will be happy to take a quick look over if you wanted to post the modified version. The file is IO locked though from the sound of it so an editor restart will be required to release that and also will need to get to the bottom of the issue or it will keep locking the file making it inaccessible.

1 Like