Open rom1504 opened 3 years ago
https://github.com/PrismarineJS/mineflayer/issues/334#issuecomment-670987395 this comment is describing a lot of tasks to do
@TheDudeFromCI @Karang I made a draft for this design there ^
Working on the first draft for this in https://github.com/PrismarineJS/prismarine-design/pull/2
Should be ready soon, but still lots more information to add before it's ready for a review and merge.
a distribution using bounding volume hierarchy (octree, kd-tree ...) works when the entities are spread on the world but what if there is 1 M entities in a small region ? Is there a distributed strategy that uses both location partitioning and another partitioning strategy ? What about the transfer of computing entities when an entity move from a region to another ? I.e. creep is in octant 1 and move on the map, he then has to be handled by octant 2, so you have to have a communication between these octants/nodes couldn't it cause latency/problems ?
@louis030195 yes handling scaling of many entities in one chunk is much harder (but yes you could just distribute by region + uuid % 10) yes there would need to be a strategy to transfer entities between regions ^ this is all about doing a distributed server though, not really needed as a first step
For example, two groups of players (from two continents) are in same Minecraft world, with 24 chunk render distance. They can build blocks yet see other group, latency must be minimized for both groups of players.
Traditional server
Distributed server
A monorepo to help keep everything maintained and updated.
No more prismarine-*
packages - just a single prismarinejs@
prefix for everything else.
mineflayer
or flying-squid
prismarinejs@reality
prismarinejs@network
(described later)prismarinejs@world
prismarinejs@chunk
prismarinejs@block
prismarinejs@biome
fromBuffer
/toBuffer
methods are using MessagePack (for web and cross-server interaction)fromJSON
/toJSON
methods are intended for debugging/logging purposes only
Inspired by Roblox's Reality Engine - the physics engine that distributes computational work across connected clients and servers, algorithmically assigning and managing simulation zones.
An module for intelligent load distribution across processes and/or remote instances and latency minimization.
declare class RealityManager extends EventEmitter {
idleProcesses: number = 1;
maxProcesses: number = 4;
allowReconnect: boolean = true; // Keep reconnecting to dead servers
maxInstances: number = 50; // Nobody will ever need over 50 connections
_instances: Set<RealityInstance>;
/// Events
// When region assigned to this instance
// `data` parameter contains a Buffer of region
on<T = {}>(
event: 'assign',
handler: Function<x: number, z: number, data: Buffer>
): this;
// When this instance was been told to reclaim a region
// Callback function must be called with `true` if region can be reclaimed, `false` otherwise
on<T = {}>(
event: 'reclaim_request',
handler: Function<x: number, z: number, priority: number, callback: function<boolean>>
): this;
/// Soft transfer of regions
// This method assigns a region to this instance if it isn't assigned yet.
// Otherwise returns an instance assigned to that region.
useRegion(x: number, z: number): Promise<RealityInstance | null>;
// Call this if region is not needed anymore
// This will pass a region to other instance and returns it.
// Otherwise method returns null and region needs to be saved.
freeRegion(x: number, z: number): Promise<RealityInstance | null>;
/// Hard transfer of regions
// This will forcefully assign/reclaim region for exclusive modification
assignRegion(x: number, z: number, inst: RealityInstance): Promise<void>;
reclaimRegion(x: number, z: number, inst: RealityInstance): Promise<void>;
}
declare class RealityInstance {
socket: Socket; // IPC, TCP, QUIC? No matter though
isLocal: boolean; // Do not apply latency measurements
isAlive: boolean; // Is process/server sending stats
_clientLatencyStats: number[]; // Aka ping
_serverLatencyStats: number[]; // Aka TPS
proxifyPlayer(client: Socket): Promise<Socket>; // Proxy client to instance
deproxifyPlayer(client: Socket): Promise<Socket>; // Stop proxying client to instance
}
// A provider class that handles serialization of a chunk across versions.
declare class ChunkProvider {
version: string;
_locks: WeakSet<Chunk>;
fromBuffer(data: Buffer, version?: string = this.version): Block;
toBuffer(block: Block, version?: string = this.version): Buffer;
fromNotch(data: Buffer, version?: string = this.version): Chunk;
toNotch(chunk: Chunk, version?: string = this.version): Buffer;
fromJSON(data: string, version?: string = this.version): Chunk;
toJSON(chunk: Chunk, version?: string = this.version): string;
// Diff chunks per-block
diffChunks (old: Chunk, new: Chunk): Iterator<[position: Vec3, old: Block, new: Block]>;
// Update chunk from part of serialized chunk
fromNotchDiff (old: Chunk, new: Buffer, bitMap: number = 0xFFFF, hasSkyLight: boolean = true, hasBiomes: boolean = true): Chunk;
// Serialize diff of chunks per-section instead of full chunk if possible
toNotchDiff (old: Chunk, new: Chunk): [ new: Buffer, bitMap: number, hasSkyLight: boolean, hasBiomes: boolean ];
};
declare interface ChunkHandler {
toJSON(): string;
fromJSON(data: string): Chunk;
// Using MessagePack for serialization (for web and cross-server interaction)
toBuffer(): Buffer;
fromBuffer(data: Buffer): Chunk;
fromNotch(data: Buffer, bitMap: number = 0xFFFF, hasSkyLight: boolean = true, hasBiomes: boolean = true): Chunk;
toNotch(bitMap: number = 0xFFFF, includeSkyLight: boolean = true, includeBiomes: boolean = true): Buffer;
}
declare class ChunkHandler_PC1_16 implementing ChunkHandler;
declare class ChunkHandler_PC1_15 implementing ChunkHandler;
declare class ChunkHandler_PC1_14 implementing ChunkHandler;
declare class ChunkHandler_PC1_13 implementing ChunkHandler;
declare class ChunkHandler_PC1_09 implementing ChunkHandler;
declare class ChunkHandler_PC1_08 implementing ChunkHandler;
declare class ChunkHandler_PE1_00 implementing ChunkHandler;
declare class ChunkHandler_PE0_14 implementing ChunkHandler;
// An abstraction of chunk.
// (Actual data and most operations can be sent over network!)
declare class Chunk {
id: Vec2;
hasSkyLight: boolean;
hasBiomes: boolean;
_rawSkyLight: Buffer;
_rawBiome: Buffer;
_provider: WeakRef<ChunkProvider>;
// For unparralleable/atomic operations on chunks
// Lock *will* prevent chunk from saving or updating
get isLocked() : Promise<boolean>;
wait(): Promise<void>;
lock(): Promise<void>;
unlock(): Promise<void>;
getBlock(pos: Vec3): Promise<Block>;
setBlock(pos: Vec3, block: Block): Promise<void>;
// Aliases to chunk accessors, position can be relative to chunk/world
getBiome(pos: Vec3): Promise<Biome>;
setBiome(pos: Vec3, biome: Biome): Promise<void>;
getBlockLight(pos: Vec3): Promise<number>;
setBlockLight(pos: Vec3, light: number): Promise<void>;
getSkyLight(pos: Vec3): Promise<number>;
setSkyLight(pos: Vec3, light: number): Promise<void>;
};
// A provider class that handles block metadata caching and block serialization across versions.
declare class BlockProvider {
version: string;
fromBuffer(data: Buffer, version?: string = this.version): Block;
toBuffer(block: Block, version?: string = this.version): Buffer;
fromJSON(data: string, version?: string = this.version): Block;
toJSON(block: Block, version?: string = this.version): string;
_metadataCache: WeakMap<Block, object>;
getMetadata(id: number): Promise<object>;
};
// Block bounding box
declare enum BoundingBoxEnum {
Empty,
Full,
Half,
Stairs,
Liquid
};
// An object for storing block state
declare class BlockState extends Object {
toJSON(): string;
static fromJSON(data: string): BlockState;
toNBT(): Buffer;
static fromNBT(data: Buffer): BlockState;
// Using MessagePack for serialization (for web and cross-server interaction)
toBuffer(): Buffer;
static fromBuffer(data: Buffer): BlockState;
};
// An abstraction of a block (aka struct). Stores basic block data, weak references and empty `BlockState`.
declare class Block {
id: number;
position: Vec3;
// Chunk data (lazy-loaded)
_chunk: WeakRef<Chunk>;
get biome(): Promise<Biome>;
set biome(value?: Biome): Promise<void>;
get light(): Promise<?number>;
set light(value?: number): Promise<void>;
get skyLight(): Promise<?number>;
set skyLight(value?: number): Promise<void>;
// State and its aliases (lazy-loaded)
_state: BlockState;
get state(): Promise<BlockState>;
get displayName(): Promise<string>;
set displayName(value?: string): Promise<void>;
get signText(): Promise<?string>;
set signText(value?: string): Promise<void>;
// Metadata (lazy-loaded)
_provider: WeakRef<BlockProvider>;
get name(): Promise<string>;
get hardness(): Promise<number>;
get boundingBox(): Promise<BoundingBoxEnum>;
get diggable(): Promise<boolean>;
get material(): Promise<?string>;
get transparent(): Promise<boolean>;
get harvestTools(): Promise<?{ [k: string]: number }>;
get drops(): Promise<?Array<{ minCount?: number, maxCount?: number, drop: number | { id: number, metadata: number } }>>;
// Methods using metadata
canHarvest(heldItemType: number | null): Promise<boolean>;
digTime(heldItemType: number | null, inWater: boolean, inMidair: boolean, enchantments?: Enchantment[], effects?: Effect[]): Promise<number>;
};
declare class BiomeProvider {
version: string;
getBiome(id: number, version?: string = this.version): Promise<Biome>;
_metadataCache: WeakMap<Block, object>;
getMetadata(id: number): Promise<object>;
}
declare class Biome {
id: number;
name: string;
color?: number;
displayName?: string;
rainfall: number;
temperature: number;
height?: number | null;
}
Interesting ideas. I disagree with the monorepo part (because it encourages tight coupling between packages, ie discourage really independent packages) What you described looks a lot like the current design of PrismarineJS.
What I want to introduce (and described above) is a state store which is prismarine-world. That will make it possible to not have monolithic minecraft servers but instead a bunch of small services handling various things. That will also make it possible to distribute them however is required.
This is already ongoing btw but will need some more work to finish it. See https://github.com/PrismarineJS/mineflayer/issues/334#issuecomment-670987395
Just to clarify: this issue is already done. The work that needs to be done before we can move further on this is extending prismarine-world. Once that's done and it's integrated in mineflayer and flying-squid for almost all data, we'll have more information on next steps.
if we ever need some data to experiment with a distributed world design, here is a 100k x 100k (107Gb compressed) slice of 2b2t's world https://www.reddit.com/r/2b2t/comments/dzvq67/presenting_the_2b2torg_100k_mapping_project_world/
https://github.com/PrismarineJS/flying-squid/issues/392 https://github.com/PrismarineJS/mineflayer/issues/334
Basic idea is pworld containing 100% of the state pworld server providing data to pworld client pviewer using pworld client to render data pworld being using in flying-squid and mineflayer
mineflayer and flying-squid being stateless transformers on top of a pworld and network