hmans / miniplex

A 👩‍💻 developer-friendly entity management system for 🕹 games and similarly demanding applications, based on 🛠 ECS architecture.
MIT License
854 stars 39 forks source link

Serialization and rehydration #305

Open gotexis opened 1 year ago

gotexis commented 1 year ago

Would be good to include a serialization guide like the one in this library: https://github.com/NateTheGreatt/bitECS/blob/master/src/Serialize.js

clibequilibrium commented 7 months ago

Would be good to include a serialization guide like the one in this library: https://github.com/NateTheGreatt/bitECS/blob/master/src/Serialize.js

Hi I played around with it. I use simple JSON.stringify combined with pako delfate

import { World } from 'miniplex';
import { Inflate, deflate, inflate } from 'pako';
import { Entity } from '../components';

interface SerializedWorldData {
    entities: Entity[];
}

class WorldSerializer {
    constructor(public readonly world: World<Entity>) {}

    public toJSON(): SerializedWorldData {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const { entities } = this.world as any;

        return {
            entities: entities
        };
    }

    static fromJSON(json: SerializedWorldData): World<Entity> {
        const instance = new WorldSerializer(new World<Entity>(json.entities));
        return instance.world;
    }
}

/**
 * Serializes the world and chunks the array buffer. Uses pako deflate
 * @param world
 * @param chunkSize
 * @returns array of buffers
 */
export function serializeWorld(world: World<Entity>, chunkSize: number): ArrayBuffer[] {
    const deflated = deflate(JSON.stringify(new WorldSerializer(world)));
    const buffers: ArrayBuffer[] = [];

    for (let i = 0; i < deflated.byteLength; i += chunkSize) {
        const chunk = deflated.slice(i, i + chunkSize);
        buffers.push(chunk);
    }

    return buffers;
}

/**
 * Deserializes the world from chunks. Uses pako inflate from provided chunks
 * @param buffers
 * @param maxEntities
 * @returns world
 */
export function deserializeWorld(buffers: ArrayBuffer[] | undefined): World<Entity> {
    let world: World<Entity>;

    // Use pako to inflate the chunks
    if (buffers && buffers.length !== 0) {
        const inflate = new Inflate({ to: 'string' });

        for (let i = 0; i < buffers.length; i++) {
            const chunk = buffers[i];

            if (i >= buffers.length) {
                inflate.push(chunk, true);
            } else {
                inflate.push(chunk, false);
            }
        }

        if (inflate.err) {
            console.error(inflate.err);
            throw new Error(inflate.msg);
        }

        // Deserialize the world from the inflate buffer
        if (inflate.result) {
            world = WorldSerializer.fromJSON(JSON.parse(inflate.result as string));
        } else {
            throw new Error('Fatal error: Inflate result is null during loading.');
        }
    } else {
        world = new World<Entity>();
    }

    return world;
}

It is optimized for both performance and low memory footprint.