Reduce undo memory usage by a third
- Changes to tiles are now stored in a plain object rather than a Map, which it turns out takes up a decent bit more space. - Changes to a tile's type or cell no longer need additional closures to perform the cell movement. - Less impactful, but changes to level properties are now stored as a diff, not as a full set every tic.
This commit is contained in:
parent
63da1ff38c
commit
c900ec80db
69
js/game.js
69
js/game.js
@ -181,9 +181,13 @@ Object.assign(Tile.prototype, {
|
|||||||
wire_tunnel_directions: 0,
|
wire_tunnel_directions: 0,
|
||||||
// Actor defaults
|
// Actor defaults
|
||||||
movement_cooldown: 0,
|
movement_cooldown: 0,
|
||||||
|
movement_speed: 12,
|
||||||
|
previous_cell: null,
|
||||||
is_sliding: false,
|
is_sliding: false,
|
||||||
is_pending_slide: false,
|
is_pending_slide: false,
|
||||||
can_override_slide: false,
|
can_override_slide: false,
|
||||||
|
is_blocked: false,
|
||||||
|
is_pushing: false,
|
||||||
pending_push: null,
|
pending_push: null,
|
||||||
destination_cell: null,
|
destination_cell: null,
|
||||||
is_making_failure_move: false,
|
is_making_failure_move: false,
|
||||||
@ -204,10 +208,8 @@ export class Cell extends Array {
|
|||||||
if (index !== LAYERS.vfx) {
|
if (index !== LAYERS.vfx) {
|
||||||
console.error("ATTEMPTING TO ADD", tile, "TO CELL", this, "WHICH ERASES EXISTING TILE", this[index]);
|
console.error("ATTEMPTING TO ADD", tile, "TO CELL", this, "WHICH ERASES EXISTING TILE", this[index]);
|
||||||
}
|
}
|
||||||
this[index].cell = null;
|
|
||||||
}
|
}
|
||||||
this[index] = tile;
|
this[index] = tile;
|
||||||
tile.cell = this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DO NOT use me to remove a tile permanently, only to move it!
|
// DO NOT use me to remove a tile permanently, only to move it!
|
||||||
@ -218,7 +220,6 @@ export class Cell extends Array {
|
|||||||
throw new Error("Asked to remove tile that doesn't seem to exist");
|
throw new Error("Asked to remove tile that doesn't seem to exist");
|
||||||
|
|
||||||
this[index] = null;
|
this[index] = null;
|
||||||
tile.cell = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get_wired_tile() {
|
get_wired_tile() {
|
||||||
@ -426,6 +427,7 @@ export class Level extends LevelInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
cell._add(tile);
|
cell._add(tile);
|
||||||
|
tile.cell = cell;
|
||||||
|
|
||||||
if (tile.type.connects_to) {
|
if (tile.type.connects_to) {
|
||||||
connectables.push(tile);
|
connectables.push(tile);
|
||||||
@ -830,15 +832,18 @@ export class Level extends LevelInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.undo_enabled) {
|
if (this.undo_enabled) {
|
||||||
|
let previous_record = this.undo_buffer[(this.undo_buffer_index - 2 + UNDO_BUFFER_SIZE) % UNDO_BUFFER_SIZE];
|
||||||
// Store some current level state in the undo entry. (These will often not be modified, but
|
// Store some current level state in the undo entry. (These will often not be modified, but
|
||||||
// they only take a few bytes each so that's fine.)
|
// they only take a few bytes each so that's fine.)
|
||||||
for (let key of [
|
for (let key of [
|
||||||
'_rng1', '_rng2', '_blob_modifier', '_tw_rng', 'force_floor_direction',
|
'_rng1', '_rng2', '_blob_modifier', '_tw_rng', 'force_floor_direction',
|
||||||
'tic_counter', 'frame_offset', 'time_remaining', 'timer_paused',
|
'tic_counter', 'frame_offset', 'time_remaining', 'timer_paused',
|
||||||
'chips_remaining', 'bonus_points', 'state',
|
'chips_remaining', 'bonus_points', 'state', 'ankh_tile',
|
||||||
'player1_move', 'player2_move', 'remaining_players', 'player',
|
'player1_move', 'player2_move', 'remaining_players', 'player',
|
||||||
]) {
|
]) {
|
||||||
this.pending_undo.level_props[key] = this[key];
|
if (! previous_record || previous_record.level_props[key] !== this[key]) {
|
||||||
|
this.pending_undo.level_props[key] = this[key];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2015,7 +2020,7 @@ export class Level extends LevelInterface {
|
|||||||
let original_cell = actor.cell;
|
let original_cell = actor.cell;
|
||||||
// Physically remove the actor first, so that it won't get in the way of e.g. a splash
|
// Physically remove the actor first, so that it won't get in the way of e.g. a splash
|
||||||
// spawned from stepping off of a lilypad
|
// spawned from stepping off of a lilypad
|
||||||
this.remove_tile(actor, true);
|
this.remove_tile(actor);
|
||||||
|
|
||||||
// Announce we're leaving, for the handful of tiles that care about it. Do so from the top
|
// Announce we're leaving, for the handful of tiles that care about it. Do so from the top
|
||||||
// down, specifically so dynamite becomes lit before a lilypad tries to splash
|
// down, specifically so dynamite becomes lit before a lilypad tries to splash
|
||||||
@ -2049,9 +2054,6 @@ export class Level extends LevelInterface {
|
|||||||
|
|
||||||
// Now add the actor back; we have to wait this long because e.g. monsters erase splashes
|
// Now add the actor back; we have to wait this long because e.g. monsters erase splashes
|
||||||
if (goal_cell.get_actor()) {
|
if (goal_cell.get_actor()) {
|
||||||
// FIXME a monster or block killing the player will still move into her cell!!! i don't
|
|
||||||
// know what to do about this, i feel like i tried making monster/player block each
|
|
||||||
// other before and it did not go well. maybe it was an ordering issue though?
|
|
||||||
this.add_tile(actor, original_cell);
|
this.add_tile(actor, original_cell);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2547,8 +2549,15 @@ export class Level extends LevelInterface {
|
|||||||
undo();
|
undo();
|
||||||
}
|
}
|
||||||
for (let [tile, changes] of entry.tile_changes) {
|
for (let [tile, changes] of entry.tile_changes) {
|
||||||
for (let [key, value] of changes) {
|
// If a tile's cell or layer changed, it needs to be removed and then added
|
||||||
tile[key] = value;
|
let do_cell_dance = (Object.hasOwn(changes, 'cell') || (
|
||||||
|
Object.hasOwn(changes, 'type') && tile.type.layer !== changes.type.layer));
|
||||||
|
if (do_cell_dance && tile.cell) {
|
||||||
|
tile.cell._remove(tile);
|
||||||
|
}
|
||||||
|
Object.assign(tile, changes);
|
||||||
|
if (do_cell_dance && tile.cell) {
|
||||||
|
tile.cell._add(tile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let [key, value] of Object.entries(entry.level_props)) {
|
for (let [key, value] of Object.entries(entry.level_props)) {
|
||||||
@ -2577,18 +2586,18 @@ export class Level extends LevelInterface {
|
|||||||
|
|
||||||
let changes = this.pending_undo.tile_changes.get(tile);
|
let changes = this.pending_undo.tile_changes.get(tile);
|
||||||
if (! changes) {
|
if (! changes) {
|
||||||
changes = new Map;
|
changes = {};
|
||||||
this.pending_undo.tile_changes.set(tile, changes);
|
this.pending_undo.tile_changes.set(tile, changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we haven't yet done so, log the original value
|
// If we haven't yet done so, log the original value
|
||||||
if (! changes.has(key)) {
|
if (! Object.hasOwn(changes, key)) {
|
||||||
changes.set(key, tile[key]);
|
changes[key] = tile[key];
|
||||||
}
|
}
|
||||||
// If there's an original value already logged, and it's the value we're about to change
|
// If there's an original value already logged, and it's the value we're about to change
|
||||||
// back to, then delete the change
|
// back to, then delete the change
|
||||||
else if (changes.get(key) === val) {
|
else if (changes[key] === val) {
|
||||||
changes.delete(key);
|
delete changes[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
tile[key] = val;
|
tile[key] = val;
|
||||||
@ -2653,11 +2662,7 @@ export class Level extends LevelInterface {
|
|||||||
|
|
||||||
this.transmute_tile(this.ankh_tile, 'floor');
|
this.transmute_tile(this.ankh_tile, 'floor');
|
||||||
this.spawn_animation(ankh_cell, 'resurrection');
|
this.spawn_animation(ankh_cell, 'resurrection');
|
||||||
let old_tile = this.ankh_tile;
|
|
||||||
this.ankh_tile = null;
|
this.ankh_tile = null;
|
||||||
this._push_pending_undo(() => {
|
|
||||||
this.ankh_tile = old_tile;
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2755,19 +2760,15 @@ export class Level extends LevelInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Tile stuff in particular
|
// Tile stuff in particular
|
||||||
// TODO should add in the right layer? maybe? hard to say what that is when mscc levels might
|
|
||||||
// have things stacked in a weird order though
|
|
||||||
// TODO would be nice to make these not be closures but order matters much more here
|
|
||||||
|
|
||||||
remove_tile(tile) {
|
remove_tile(tile) {
|
||||||
let cell = tile.cell;
|
tile.cell._remove(tile);
|
||||||
cell._remove(tile);
|
this._set_tile_prop(tile, 'cell', null);
|
||||||
this._push_pending_undo(() => cell._add(tile));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
add_tile(tile, cell) {
|
add_tile(tile, cell) {
|
||||||
|
this._set_tile_prop(tile, 'cell', cell);
|
||||||
cell._add(tile);
|
cell._add(tile);
|
||||||
this._push_pending_undo(() => cell._remove(tile));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
add_actor(actor) {
|
add_actor(actor) {
|
||||||
@ -2849,15 +2850,11 @@ export class Level extends LevelInterface {
|
|||||||
let new_type = TILE_TYPES[name];
|
let new_type = TILE_TYPES[name];
|
||||||
if (old_type.layer !== new_type.layer) {
|
if (old_type.layer !== new_type.layer) {
|
||||||
// Move it to the right layer!
|
// Move it to the right layer!
|
||||||
let cell = tile.cell;
|
// (No need to handle undo specially here; undoing the 'type' prop automatically does
|
||||||
cell._remove(tile);
|
// this same remove/add dance)
|
||||||
tile.type = new_type;
|
tile.cell._remove(tile);
|
||||||
cell._add(tile);
|
this._set_tile_prop(tile, 'type', new_type);
|
||||||
this._push_pending_undo(() => {
|
tile.cell._add(tile);
|
||||||
cell._remove(tile);
|
|
||||||
tile.type = old_type;
|
|
||||||
cell._add(tile);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this._set_tile_prop(tile, 'type', new_type);
|
this._set_tile_prop(tile, 'type', new_type);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user