Recorder Module - Record any property and attribute of any roblox Instance with ease, and then "playback" recorded data

Welcome to the Instance Recorder Module

If you just want the module marketplace link

Get the module here: https://create.roblox.com/marketplace/asset/3172715879/Recorder-Module

About the Module

What is the Instance Recorder Module?

The Instance Recorder Module is a module that allows you to record… instances. This essentially means is you can record any property or attribute of any instance in the game to later utilize the saved changes in your game for many different effects.

Do you have any examples of possible effects a developer can implement with this module?

Yes, you can make a time reversal effect (as showcased in the example place below) along with other effects, such as a camera recording, where a player can watch back the footage, or to a clone that follows the players movements. Any effect where you could find recording changes in any instance over time useful this module is for you.

How optimized is this module?

The module isn’t as optimized as I would like it to be, but its CPU and memory usage should be better compared to the older version of this module. The CPU and memory usage of the module depends on how many instances, properties and the frequency of calls to the update method, and as such is hard to pinpoint. Changes in the newer version of this module should alleviate issues with memory usage, as you can call update less frequently when recording and still get smooth playback.

How to use the Module

If you used the older version of the module, the changes below will break your game, as such its recommended staying on the older version until you can update your scripts.

The instance recorder module is fairly simple to use, and is really nice for managing data more than it is for something more tangible, the reversing effect in the example place has far more lines of code than the module, but the module helps make the code easier to write and read.

The instance recorder module is ultizing a roblox/lua OOP style, with classes and methods (and not so much properties).
To create a new “recorder” instance which manages everything, you would call the module’s .new() method or just call the module itself.

local recorder = require(3172715879)
local new_recorder = recorder(recorder.InterpolateFrames) -- or recorder.new(recorder.InterpolateFrames)

Frame Modes, numbers and “enums”.

Frame mode "enums" are defined in the same space as the .new() method. So require the module, and do module.InterpolateFrames as an example.
recorder.InterpolateFrames = 0
-- This is the default mode, :Update and other methods return an <INTERP_UPDATE_FRAME>
recorder.FullFrames = 1
-- This is a frame mode where :Update and other methods return a <FULL_UPDATE_FRAME>
recorder.BasicFrames = 2
-- This is a frame mode where :Update and other methods return a <UPDATE_FRAME>

The returned object is a recorder class object which has many different methods.

Basic Getter/Setter methods.

new_recorder:SetFrameMode(number)
-- This method sets the frame mode, which dictates what type of update frames are returned by functions that return an update frame of any type.
new_recorder:GetFrameMode()
-- Returns the frame mode set before.
new_recorder:SetPlaybackSpeed(number)
-- This method sets the speed of playback, can be negative or positive, just not 0.
new_recorder:GetPlaybackSpeed(): number
-- Returns the playbackspeed.
new_recorder:SetCurrentTime(number)
-- This method sets the current time "playback head" in seconds, used during playback to set a start time.
new_recorder:GetCurrentTime(): number
-- Returns the current playback head time in seconds.
new_recorder:SetRecordLength(number)
-- If set to 0, there is no record length, otherwise set to the number of seconds maximum to record, useful to limit memory usage.
new_recorder:GetRecordLength(): number
-- Returns the max number of seconds to record. 0 means there is no limit.
new_recorder:SetShouldOverrideFrames(boolean)
-- Sets whether it should override old frames if it reaches record length, or to stop recording.
new_recorder:GetShouldOverrideFrames(): boolean
-- Returns if the recorder will override old frames.
new_recorder:SetRecordingFrequency(number)
-- Sets the number of seconds between recorded frames. Useful to limit the "FPS" of recording, by denoting the length of time that needs to pass for a new recorded frame to occur.
new_recorder:GetRecordingFrequency(): number
-- Returns the recording frequency.
new_recorder:GetTimeSinceLastRecordedFrame(): number
-- Returns the amount of time elapsed since the last recorded frame. 0 indicates that at the time of calling this function the recorder recorded a frame.
new_recorder:IsPlaying(): boolean
-- Returns if the recorder is currently playing. (Recorder can both be playing and recording.)
new_recorder:IsRecording(): boolean
-- Returns if the recorder is currently recording. (Recorder can both be playing and recording.)
new_recorder:GetInstances(): {[Instance]: {[1]: {[string]: boolean}, [2]: {[string]: boolean}, [3]: {<BASIC_FRAME>}}}
-- More for internal use, but can be useful to get a list of instances the recorder is keeping track of.

Basic Recorder Methods

new_recorder:StartPlaying(boolean?): (<INTERP_UPDATE_FRAME>|<FULL_UPDATE_FRAME>|<UPDATE_FRAME>)?
-- Starts recorder playback, does not stop recording (make sure to call :StopRecording()), optional parameter if set to true, function will return an update frame with the type defined by FrameMode.
new_recorder:StopPlaying(boolean?): (<INTER_UPDATE_FRAME>|<FULL_UPDATE_FRAME>|<UPDATE_FRAME>)?
-- Stops recorder playback, does not start recording (make sure to call :StartRecording()), optional parameter if set to true, will return an update frame with the type defined by FrameMode.
new_recorder:StartRecording()
-- Starts recording on recorder, does not stop playback (make sure to call :StopPlaying()).
new_recorder:StopRecording()
-- Stops recording on the recorder, does not stop playback (make sure to call :StopPlaying()), more closer to PauseRecording, as resuming recording can cause jumps in playback.

Instance Management Methods

new_recorder:AddInstance(Instance, {[string]: boolean}?, {[string]: boolean}?)
-- Adds passed instance to be recorded, 2nd and 3rd parameter denote what to record on the instance, 2nd parameter is properties, and 3rd is attributes. They are optional. The table has string keys which is the property/attribute names, and the boolean value of these keys is whether the value should be interpolated (setting to false can help make certain values you dont want interpolated be bypassed and interpolate other data).
new_recorder:RemoveInstance(Instance)
-- Removes the passed instance from the recorder's tracking.
new_recorder:HasInstance(Instance): boolean
-- Returns true if the instance is being tracked, otherwise false.

Instance Specific Methods

new_recorder:GetFullFrameFromInstance(Instance, number): <FULL_FRAME>?
-- (RETURNED DATA IS SAFE TO CHANGE), returns a <FULL_FRAME> (See below what that is) in the tracked passed instance in the index passed as parameter 2. This operation allocates more memory, but means any data in the table can be modified without messing up tracking.
new_recorder:InterpolateFrameFromInstance(Instance, number, number): <INTERP_FRAME>?
-- (RETURNED DATA IS SAFE TO CHANGE), returns a <INTERP_FRAME> (See below what that is) in the tracked passed instance in the index passed as parameter 2. The 3rd parameter is the "time difference" between this frame and the last frame, used for interpolation (NOT A VALUE FROM 0 - 1, but a value between 0 and the length of the current frame). This operation calls the method above, as such allocates more memory, but that means data from the returned table is safe to modify.
new_recorder:GetFrameFromInstance(Instance, number): <FRAME>?, number, number
-- (RETURNED DATA IS NOT SAFE TO CHANGE), returns a <FRAME> (See below what that is) in the tracked passed instance at the timestamp in seconds passed as the second parameter. This operation returns an INTERNAL datatable, and as such data inside the table should ONLY BE READ, not modified. COPY the table (DEEP COPY), if you want to be able to modify the data. The other returned values is the index of the frame in the frames table, along with the time difference from the passed timestamp and the length of the frame. Used to interpolate the frame.
new_recorder:GetInstanceRecordedLength(Instance): number
-- Returns the seconds of recorded data in the passed tracked instance, or 0 if nothing was recorded.

Instance Specific DataTypes.

ALL DATATYPES HAVE THIS FORMAT ({length: number, props: {[string]: any}, attr: {[string]: any}})
-- <FRAME>: A simple frame data. This frame data may not have every "tracked" property or attribute in the props and attr tables, and as such a programmer should ensure not to access a nil value.
-- <FULL_FRAME>: Same data format as above, but ensures the props and attr tables have every tracked property and attribute stored in them, as such its safer to assume a specific value is in the table, as long as the property or attribute where tracked.
-- <INTERP_FRAME>: Same data format as both above. Similar to a <FULL_FRAME>, where its safe to assume a tracked property or attribute are in the props and attr tables, but will also interpolate the values between this and the next frame.

Module-wide Methods

new_recorder:DeleteFramesAfter(number)
-- Deletes the frames after the passed timestamp in seconds, useful to delete frames for "flawless" recording after playback, or to wipe all data (passed timestamp is 0).
new_recorder:GetFullFrame(number): <FULL_UPDATE_FRAME>
-- Returns a <FULL_UPDATE_FRAME> at the passed timestamp in seconds. Read below on what a <FULL_UPDATE_FRAME> is.
new_recorder:InterpolateFrame(number): <INTERP_UPDATE_FRAME>
-- Returns a <INTERP_UPDATE_FRAME> at the passed timestamp in seconds. Read below on what a <INTERP_UPDATE_FRAME> is.
new_recorder:GetFrame(number): <UPDATE_FRAME>
-- Returns a <UPDATE_FRAME> at the passed timestamp in seconds. Read below on what a <UPDATE_FRAME> is.
new_recorder:GetRecordedLength(): number
-- Returns the number of seconds recorded. It does this by checking for any tracked instance and getting the recorded length of that. Returns 0 if there is no recorded data.

new_recorder:Update(number): (<UPDATE_FRAME>|<FULL_UPDATE_FRAME>|<INTERP_UPDATE_FRAME>)?
-- Updates the recorder. If the recorder is playing, itll return an update frame for use in whatever you need. The update frame type is based on the "mode" passed to .new, and the :SetFrameMode methods. Default's to <INTERP_UPDATE_FRAME>.

Module-wide DataTypes.

ALL DATATYPES BELOW HAVE THE SAME FORMAT. ({[Instance]: {frame = (<FRAME> or <FULL_FRAME> or <INTERP_FRAME>), props = {[string]: boolean}, attr = {[string]: boolean}}})
<UPDATE_FRAME>: Simple update frame. This essentially uses <FRAME> datatype for the data.
<FULL_UPDATE_FRAME>: Same as above, but uses a <FULL_FRAME> datatype for data, and ensures all data is safe to modify in the return (so even the instance tracked props and attr tables)
<INTERP_UPDATE_FRAME>: Same as above, but uses a <INTERP_FRAME> datatype for data, and ensures all data is safe to modify in the return (so even the instance tracked props and attr tables)

This is all the current methods, but changes can happen, and will happen. Best to import the module into your game instead of requiring it via the marketplace id.

How to get the module

Get the module from the roblox marketplace here, https://create.roblox.com/marketplace/asset/3172715879/Recorder-Module

Update Log

18/01/2024 3:00 PM - New V2 Update, added interpolation, API breaking changes.
18/01/2024 6:00 PM - Added a method to denote the type of <UPDATE_FRAME>s returned from :Update, :StopPlaying and :StartPlaying. Fixed small bugs and issues with interpolation. Bug added (though really a side effect of the WRONG way I used before) where if the recording update frequency is too low, itll cause the rotating platform to rotate incorrectly, just increase update frequency (changed in example place to be .25 seconds instead of 1 second).
22/01/2024 10:00 PM - Added new API to denote a “recording frequency” which is essentially a way to limit the recording “FPS” by denoting how much “time” in seconds needs to pass for it to attempt to record a new frame. This can help boost performance and limit memory even more, while making it easier to manage varying framerates for recording and playing (essentially your update loop can be identical no matter the recorder object’s state, thus allowing smooth playback where needed, and better memory/performance when recording). Simple API added to the documentation underneath basic getter/setter methods. By default the update frequency is 0, which means essentially every call to update will lead to a new recorded frame, you set the value in seconds to limit this.
22/01/2024 10:00 PM - Bonus implementation, interpolation will attempt to interpolate the last recorded frame (the frame last recorded) with current value properties to help smoothen playback with slower recording frequencies. There are still issues with this, specifically around the fact that this method does not account for the fact that the object may’ve moved between this and the last frame, meaning for length of the recording frequency, it could have weird logarithmic-based <INTERP_FRAME>s. I may add a toggle to disable this behavior in the future, or remove it entirely. A way to combat this is to call an undocumented method, :RecordAFrame(delta), the reason this method is undocumented is due to future breaking changes this method could happen, and the fact that it may not really be useful for a lot of things. This method is essentially :Update(delta) but ignores recording frequency and state.

Examples

Example place below, uses the same place showcased in the video.
InstanceRecorderExample.rbxl (63.0 KB)
Another Example place, where the player is also reversed.
InstanceModuleExample_PlayerReversed.rbxl (63.2 KB)
Video:

57 Likes

Holy crap this is amazing, imagine reversing an explosion with this module, Like you see an explosion effect fling parts everywhere and then it just starts reversing.

However I can see a module like this sadly getting problematic and in the way of a game’s actual programming depending on it’s structure.

Good job.

8 Likes

Thats why the recorder module gives you, the developer a lot of the control… all it does is record the part everytime you “update”, and while playing just gives back the recorded data.

YOU as a developer can do whatever you please with the data, such as working around some systems in your game, etc.

I have created a rewind script that is very similar, but because of such it wouldnt work in every game… because it modified the instances itself, this recorder module allows you full control, all it does is manage the data for you.

AKA the recorder module does not touch the instance (other than to read its properties everytime update is called. Its the developers job to do so.

3 Likes

I guess you bring up a fair point.

1 Like

Though the memory usage of the module can be quite high, especially if you have it update every frame.
This is the only major downside I can see with the module, as perhaps the memory usage is too high for some people’s games. Of course you don’t have to update every frame, you can update every second, or longer. The playback may feel more choppy as a result though, and you as a developer may have to account for this by using physics constraints (such as the example place, which uses an alignposition and alignorientation to move the player and the part when reversing), or tweenservice, or really any amount interpolation.

Of course the module wont create a new “frame” if the data in the previous update call is the same as the instance this update call, but for stuff that are constantly moving or changing, such as the example place, this isn’t really helpful, because the data will always be different every update call.

You can set a max record time, which will help a little, but memory usage will still be quite an unknown variable.

The default max record time is 10 seconds, and I think the example place sets it to 15 seconds. Override frames will delete old data to continue recording, but basically means only 15 seconds of previous data will be recorded. If you disable overriding of old frames, it will just stop recording new frames once it reaches the max record time. Its developer’s choice of which method they like.
Might also be best to keep the amount of properties that are recorded to the minimum, the most popular property will no doubt be CFrame, but you can use any other property, but the more properties used the more memory that is used to keep track of them. I may have to look into ways to compress the data somehow, while still being performant CPU wise, but at its current state, memory usage is still a major concern, keeping a 10 second record time is probably the sweet spot.

3 Likes

It’s funny how the Update is supposed to be Self-explanatory but has the longest explanation! :smile: Also, this is pretty cool. I am definitely going to try this module soon.

1 Like

Most of the explanation is of under the hood things, though it is helpful to understand that the Update Method does return a “total frame” when the recorder is playing, and returns nothing for everything else. That is the jist, under the hood it does all the recording, etc.

1 Like

The recorder seems to have trouble replaying jumping (the player starts floating), asides from that, this module is really helpful and saved me a lot of time!
Also, is it possible to record player animations?

Edit: The recorder also breaks when you slow down the playback speed

1 Like

The reason for this is because its moving the player using the mover constraints, AlignPosition and AlignOrientation. These don’t update the “CFrame” of the player to the right position instantly, and as such is delayed… this is not entirely solvable, unless you want to just update the CFrame directly, which will cause other issues, this is an example place afterall, so its intent isn’t meant to be used in a final game, the module itself doesn’t actually do anything but store a big array of “frame data”.

About recording the player’s animations themselves? You could record every motor6D in the player, and during playback set their Motor6D’s, or find the animation that is being played somehow and have the recorder record it? Of course it would have to be a property, so it might not be entirely helpful. The recorder module can record any instance, and any property, and will return that data each call to update during playback. You can make a clone of the player, record each parts of the player, and set the cframe of the parts onto the clone, and hide the actual player, though that is fairly hacky. The example place did not do this, because I don’t actually have an idea for actually recording the player fully and optimally, and I don’t intend to even reverse the player in my actual game I made the module for.

About the slowing down of playback speed breaking it, this depends on how much you slow it down, as due to floating point inaccuracies, eventually a small enough number would cause the playback speed to be essentially 0. The example place actually “tweens” the playback speed, so if you edited the script and noticed its not moving at the speed you want, its probably because you also didn’t edit the tween, or remove the tween. (it tweens the speed to give the slow reverse into the normal reverse effect)

The tween is done here in the script, near the bottom of RecordTest

2 Likes

The name is ominous af but AMAZING MODULE! OH MY GOD!

2 Likes

This looks pretty cool!

Would there be a way to exclude the Player from the recording? So it rewinds everything BUT the player?

That is something I specifically programmed into the example place. The module itself allows you to denote what you want “recorded”.

This means you can exclude the player… much more easily then you could actually record them. This module is not “rewinding” per-se, but just recording all properties of a part over a period of time. The rewind was me showcasing a possibility with the module, but you can do almost anything with the data it records… and choose what it records (it records nothing by default, you have to specify the parts to record… I may work on implementing models aswell, although its probably better to record a few parts in the model, and not the whole model due to the memory usage of this module). You don’t even have to record parts, you can record ANY instance, and ANY property(s), and the module will handle it the best it can. All it does is save the data, what you do with the data is up to you as a developer.

1 Like

A new Recorder Module update has been released, this new update has huge API breaking changes, but adds a new feature! Recorder Module V2 is also able to be more lightweight on memory due to this new feature, and CPU usage ideally should be a little bit better. The new feature in question is LERPing/Interpolation! Essentially this will help smoothen out playback “frames” by interpolating inbetween frames, lets say playback speed is slower, well with interpolation itll still playback real smooth due to the interpolated frames. Interpolation works on numbers, CFrame, Vector2 and 3, Color3, and UDim2s, any other value will go back to the original module’s behavior.

Also another feature added is support for attributes, you can now record attributes on an object and itll interpolate it for the supported values, as well as just recording them.

BUT you may ask, how does this lower memory? well instead of updating every frame when doing recording (you should update every frame for playback for smoother playback) you can update every, say, half a second or more and itll still playback smoothly. It does not save interpolated frames in memory, just the ones it saves every call to update while recording.

Sadly interpolation may add more CPU usage, but should still run excellently. I plan to add an option in the future to return interpolated frames, “fullframes” (which are frames where every property that is recorded is filled, even if it wasnt saved), or just the basic frames it saves, the last of which will have the best performance, but at a detriment to playback (especially if at slower speeds). This isnt currently in the module, but is planned. You can get the different types of data via methods though, just update will return interpolated frames (along with StartPlaying and StopPlaying).

Currently the module in marketplace is updated, but I will update the main post to showcase the new changes aswell.

(along with changing documentation on the main post to match the API changes).

New Update, small changes to how frames are stored internally.
Now you can change how the :Update returns its update frames. Via frame mode, passed when you first create a new recorder object, or to the methods :SetFrameMode. Frame modes have an “ENUM” inside of the module scope, (so same table as .new()) and you can figure out what those are in the main post. Due to the changes, longer update frequencies while recording can cause bugs for fast moving things, as LERP seems to do the fastest/closest way to point B from point A, where a lot of missing data between frames could cause the issue. Suggest making the frequency of update for recording be every .25 seconds, or more depending on the speed of recorded objects. Making recording updates every frame will work too, and interpolation will still help smooth it out on slower playbacks, but if you are just doing normal playback, or faster, its better to not use interpolation (just for the memory and CPU savings using something like the BasicFrames mode would do)

Currently the example place has a small issue when spamming the f button (this was an issue in previous versions of the recorder module’s example place aswell) where the platforms when you rewind normally after previously spamming f, will move slowly and/or weirdly during the parts where f was spammed.

I do not know why this is an issue, and its rather difficult to debug the process, but I will be trying to fix this issue. If the recording update frequency is every frame, it should work fine, its when using slower updates for recording that cause the issue.

Still can’t figure out why this is an issue, for now prevented the user’s ability to spam in my local copy, may update the main post with the changes later. Essentially the user can use the ability, hold for as long as they want, let go, etc. But as soon as they let go they have to wait a second before using the ability again, this seems to solve the weird issues with inconsistencies and such due to spamming.

Seems to me like it was recording the spam rewinding.

Since it records and rewinds every instance, it was recording itself spamming the rewinding, then replaying it glitchy, causing the issues.

That is my guess. :man_shrugging:t3::upside_down_face:

That is what I assume it was doing aswell, probably has to do with the inconsistencies with how the physics movers work. I in the end just decided to make it so rewinding has a cooldown of a second to prevent spamming. You can rewind and stop rewinding, then have to wait a second to do it again. It works fine, and if UI were added to display that this is happening, most players will consider it a gameplay “feature” i suppose.

IF you make a game (such as my idea, which is an obby where you can rewind stuff) you would have to account for the delay between being able to rewind, which can limit some obby aspects.

I’ve tried many things to figure out a way to fix the issue and allow the original behavior of being able to spam, but all of them had the issue, or showed issues. It seems like it also deletes old frames even if the recording length hasn’t reached the limit (by default 10 seconds), which is weird but can make a little bit of sense. If someone can fix up the example place and get spamming to work better, I would push the module change (if any) to the module, but what the module can currently do is good enough for me and i’d assume most people, especially with the new interpolation it does to help smoothen out playback.

1 Like

Sorry to bump this again, but I rewrote this module a third time, and this time I added something called “timelines”. Basically timelines allow you to record different points in “time”, by setting the record_head member of the recorder object to any timestamp you want. This does mean memory usage is higher, but you can limit the number of saved timelines (by default the max is 10), and timeline record length, which is like the record length in older versions of the module, but per timeline. This rewrite introduces API breaking changes, but should be even better.

I also added easing_style and easing_direction tweening to the interpolated values, so now the module can “interpolate” the values with different styles of tweens, using TweenService:GetValue.

Frames are also now stored as a linked list, this helps make deleting older frames faster, which makes a timeline record length that more performant. Its a doubly linked list, so each frame also stores the previous and next frame in the list. That is mostly underlying details which dont matter to you as a developer all too much, especially since in any mode other than RefFrames mode, those values for prev and next are wiped, to make cloning frame data more efficient. If you intend to use this data in some way, RefFrames mode is desired, You will have to implement frame interpolation yourself in this mode if you want it though… but methods such as :FillFrame, and :CloneFrame, should make it slightly easier (CloneFrame just does a deep table copy underneath, and FillFrame acts like the previous version’s FullFrame object, where all properties and attributes are filled in “correctly”

I intend to update the post and push the massive changes to the module on marketplace fairly soon, as I am currently debugging the module (but it is effectively finished)… Expect a new topic edit to showcase changes.

The new timelines feature also helps create a way to record data alongside previous data, for games such as Time Travel mechanics… (I mainly did this change for such an idea actually, its why they are called timelines), and the script is now in --!strict mode, with better defined types… there is 2 warnings from roblox’s typechecker about getting an instance’s properties dynamically can cause errors, which can effectively be ignored if you are using the module correctly… but even then, to be extra safe, I wrapped the accesses to properties in pcalls, so bad code should not kill a huge script…

1 Like

Ok this new version of the module is taking longer than I expected to ensure it works without bugs. This new version still doesnt solve my issues with spamming rewinding breaking the rewind, (and actually can cause more bugs, as rewinding and “replaying” creates a new timeline, and I set max timelines to ten, so older data when spamming will be deleted, which is a drastic change to older versions), basically spamming makes the portion of where the user spammed rewind slower than the rest.

Currently the module from my tests have not introduced too many new issues, other than those found due to configuration settings, and of course how the new timelines system works. I am not fully confident in the new module yet to push changes to the marketplace module, and changing the topic to describe the new API changes, and need some help testing it… I will be uploading both the new example place which uses the new module (which is named MainModule, in the Modules folder inside of ReplicatedStorage, the old version of the module is there too, but isnt used, except for a disabled script in player local scripts).

Those more experience I encourage reading the module’s code to figure out the API, along with seeing how the example place uses it for now, if you intend to test the module itself, and less the example place (which is something I would like testing more on). The API isnt final for when the new version does officially release, especially as configuring the recorder requires manual touching of the recorder class object members, which I don’t intend to be the final version’s way of configuring the recorder object.

Example Place:
RecorderModuleExample.rbxl (65.2 KB)
Module .lua code, basically just copy and paste it into a modulescript:
RecorderV3.lua (14.4 KB)
Thanks for your patience… I intend to push a small bugfixes update to the current module version, especially since while looking through its code again, there were quite a few interesting bugs (those who modified the override frames option may notice one such bug… it will break the recorder) for the time being while I still test this new version of the module. Currrent code using the current version of the module might want to try out this new module if they intend to push an update to the new module version in the future, as while the API is not stable yet, I don’t intend to change it too drastically when any kinks are solved (if any).

2 Likes