" " " "

javascript – Fixed timestep without cloning game state

Share post:

It’s important to understand that your questions have very little to do with fixed vs variable timestep. Decouple the two things in your mind! Your questions have to do with performance patterns between different languages (C/C++ and Javascript).
Copy-by-value: Structs and Arrays

If I’m wrong and it’s not copied, then it’s strange how he assigns
currentState to previousState and then proceeds to interpolate between
essentially one game object.

Not strange. Just that he’s writing code in C / C++, in which structs are treated as value types, hence assignment is by value, just like say assigning a Number variable / property in Javascript. It’s a massive benefit that I wish we had in JS; there are some future plans for this. For now, we hack.
In Javascript, any object type is assigned by reference, i.e. a pointer to the original value is copied, not the actual value. However, fast by-value copying can be done through using arrays (see below), a common pattern in games and graphics.

Copying whole game state every update is going to put a significant performance cost on me, I’m sure. Is it normal in gamedev for other languages? I’m quite professional with JS, but have no gamedev experience with other languages. Usually copying large objects (and game state is obviously large) is a heavy operation and doing it every update (render or physics) seems bad.

Right, it’s not a requirement, and it will always incur some overhead, but in recent years we’ve realised through the influence of pure functional programming that this is the safest way to operate on state, thereby avoiding painful, hard-to-trace bugs in complex projects. It’s best practice in the professional games industry. However, it’s up to you.
Fast block-copying of data exists in JS these days, much like C’s memcpy: Array.copyWithin() which is also usable on TypedArray (which is even faster). Alternatively for Array, you can guarantee the use of 31-bit SMIs (small integers) in a regular Array, and represent all your data that way. You can google “V8 SMI” for more info on why SMIs are desirable and how to use them.

Currently I’m storing all info inside corresponding objects. My bullets have x and y and this works well. Copying gamestate will require me to split state from objects, and then, on render, go to manually update every moved object coordinates. Instead of “myBullet.x += speed”, i’ll have to do:

Right, and in pure ECS, objects are always treated as pure data, not as typical OOP-style “objects as a combination of data and methods”. I’d advise dropping the classic OOP approach in the context of entities, if you want high performance over many objects. Try this:
const X = 0;
const Y = 1;
const XY_COMPONENTS = 2; //X + Y = 2 components.
const ENTITIES_COUNT = 4;

//if you treat this as interleaved, it is XYXYXYXY, else it is XXXXYYYY — index accordingly!
let positions = new Uint32Array(ENTITIES_COUNT * XY_COMPONENTS);

If using ES6 classes, make your entity-specific methods static (no local member data), and pass into them discrete objects or better yet, indices into the positions array (see below).
Object Pools
Assuming you keep using JS objects rather than a large array of positions:
let stateNew = pool.retrieveNextFreeState();

//update each property, X, Y, etc. on that state in JS style (because we can’t copy a whole struct by value)
copyValues(stateOld, stateNew);

pool.retireOldState(stateOld);

An object pool is used here, otherwise you’re stuck creating new positions every frame, which means unnecessary allocation and GC overheads. Of course this means you need to implement or include (as a library) a performant object pool.
Important to avoid GC (garbage collection): Pools are based on some kind of array, stack or queue (all of which can be represented by JS Array), which usually implies modifying the length of the array you use as your object pool can also incur GC, via either push() / pop() / shift() / unshift(), or by adding new elements via [i], or by setting the length property. To avoid GC, implement this by pre-allocating the pool Array at a certain length and never modify that length; the downside is you must search through the full-sized array for the next free object or index.
You can pre-allocate a large Array (of SMIs) or TypedArray and have all your object data interleaved, like so:
const X = 0;
const Y = 1;
const XY_COMPONENTS = 2; //X + Y = 2 components.
const ENTITIES_COUNT = 4;

//if you treat this as interleaved, it is XYXYXYXY, else it is XXXXYYYY — index accordingly!
let positions = new Uint32Array(ENTITIES_COUNT * XY_COMPONENTS);
let entityIndexNew = pool.retrieveNextFreeStateIndex();

copyValuesByIndex(entityIndexOld, entityIndexNew); //to copy both X and Y for each entity.

So now your object pool will track available indices into this large array of values, rather than tracking JS object references (hopefully that’s clear).
The benefit here is you are not constantly allocating (via either new or something = { … }) and thereby incurring GC at runtime.
Conclusion
If you want the performance-centric approach, apply all the above lessons (array(s) of data + pool of data indices).
If you just want to keep it simple for now and focus on your game logic, either (a) accept the overhead of allocating new object copies every frame or (b) at the very least, store these objects in a simple pool which, even if it does incur some GC overhead through use of push() / pop(), at least avoids you allocating hundreds of new game objects every single frame for making copies.

Related articles

Real Madrid’s Mbappe struggling with thigh injury

Real Madrid's French forward Kylian Mbappe has been diagnosed with an injury to...

A Night With the Best Baseball Team in New York

The Brooklyn Cyclones are leading their minor league division this summer, while New York’s major league teams...