@nativewrappers/fivem
Version:
Native wrappers and utilities for use with FiveM.
226 lines (225 loc) • 6.56 kB
JavaScript
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
import { GlobalData } from "../GlobalData";
class NetworkedMapEventManager {
static {
__name(this, "NetworkedMapEventManager");
}
#syncedCalls = /* @__PURE__ */ new Map();
constructor() {
$SERVER: if (GlobalData.IS_SERVER) {
on("playerDropped", () => {
const src = source;
for (const [_k, map] of this.#syncedCalls) {
map.removeSubscriber(src);
}
});
return;
}
$CLIENT: {
RegisterResourceAsEventHandler(`${GlobalData.CurrentResource}:syncChanges`);
addRawEventListener(`${GlobalData.CurrentResource}:syncChanges`, (msgpack_data) => {
const data = msgpack_unpack(msgpack_data);
const syncName = data[0];
const syncData = data[1];
const map = this.#syncedCalls.get(syncName);
if (!map) {
throw new Error(`Tried to sync changes for a networked map but ${syncName} does't exist.`);
}
map.handleSync(syncData);
});
}
}
addNetworkedMap(map) {
this.#syncedCalls.set(map.SyncName, map);
}
removeNetworkedMap(syncName) {
this.#syncedCalls.delete(syncName);
}
}
const netManager = new NetworkedMapEventManager();
class NetworkedMap extends Map {
static {
__name(this, "NetworkedMap");
}
#syncName;
#queuedChanges = [];
#changeListeners = /* @__PURE__ */ new Map();
#subscribers = /* @__PURE__ */ new Set();
constructor(syncName, initialValue) {
super(initialValue);
this.#syncName = syncName;
GlobalData.NetworkedTicks.push(this);
netManager.addNetworkedMap(this);
$SERVER: {
if (!GlobalData.NetworkTick && GlobalData.IS_SERVER) {
GlobalData.NetworkTick = setTick(() => {
for (const networkedThis of GlobalData.NetworkedTicks) {
networkedThis.networkTick();
}
});
}
}
}
get SyncName() {
return this.#syncName;
}
// handles removing the player from the map whenever they're dropped
onPlayerDropped() {
this.removeSubscriber(source);
}
/*
* Resyncs the entire map to the client, useful for if there's a mismatch in the clients map (when multiple players change things, in cases like inventories)
*
* NOTE: This doesn't check that the player is already subscribed to the map, you should do your own due-diligence to only call this for players already subscribed
*/
resync(source2) {
const packed_data = msgpack_pack([this.#syncName, [[4 /* Init */, this.size === 0 ? [] : Array.from(this)]]]);
TriggerClientEventInternal(
`${GlobalData.CurrentResource}:syncChanges`,
source2,
packed_data,
packed_data.length
);
}
/*
* Adds a new subscriber to the map
*/
addSubscriber(source2) {
this.#subscribers.add(source2);
this.resync(source2);
}
removeSubscriber(sub) {
return this.#subscribers.delete(sub);
}
hasSubscriber(sub) {
return this.#subscribers.has(sub);
}
subscriberCount() {
return this.#subscribers.size;
}
handleSync(data) {
for (const [change_type, key, value, possibly_undefined_subvalue] of data) {
switch (change_type) {
case 1 /* Add */: {
this.set(key, value);
continue;
}
case 2 /* Remove */: {
super.delete(key);
continue;
}
case 3 /* Reset */: {
super.clear();
continue;
}
case 4 /* Init */: {
super.clear();
const key_value = key;
for (const [k, v] of key_value) {
this.set(k, v);
}
continue;
}
case 0 /* SubValueChanged */: {
const data2 = this.get(key);
data2[value] = possibly_undefined_subvalue;
continue;
}
}
}
}
/*
* Listens for the change on the specified key, it will get the resulting
* value on the change
*/
listenForChange(key, fn) {
const listener = this.#changeListeners.get(key);
listener ? listener.push(fn) : this.#changeListeners.set(key, [fn]);
}
#triggerEventForSubscribers(data) {
const packed_data = msgpack_pack([this.#syncName, data]);
for (const sub of this.#subscribers) {
TriggerClientEventInternal(
`${GlobalData.CurrentResource}:syncChanges`,
sub,
packed_data,
packed_data.length
);
}
}
#pushChangeForListener(key, value) {
const listener = this.#changeListeners.get(key);
if (!listener) return;
for (const ln of listener) {
ln(value);
}
}
set(key, value) {
let v = value;
if (value instanceof Object) {
const curMap = this;
const objectChangeHandler = {
get(target, prop, reciever) {
return Reflect.get(target, prop, reciever);
},
set(target, p, newValue, receiver) {
const success = Reflect.set(target, p, newValue, receiver);
if (success) {
curMap.#pushChangeForListener(key, target);
$SERVER: {
curMap.#queuedChanges.push([0 /* SubValueChanged */, key, p, newValue]);
}
}
return success;
}
};
v = new Proxy(v, objectChangeHandler);
}
super.set(key, v);
this.#pushChangeForListener(key, v);
$SERVER: {
this.#queuedChanges.push([1 /* Add */, key, v]);
}
return this;
}
/*
* Resets the map to its default state
*/
clear() {
$CLIENT: if (GlobalData.IS_CLIENT) throw new Error(`Cannot call 'clear' on client`);
this.#queuedChanges = [];
this.#queuedChanges.push([3 /* Reset */]);
super.clear();
}
delete(key) {
$CLIENT: if (GlobalData.IS_CLIENT) throw new Error(`Cannot call 'delete' on client`);
this.#queuedChanges.push([2 /* Remove */, key]);
return super.delete(key);
}
networkTick() {
if (this.#queuedChanges.length !== 0) {
this.#triggerEventForSubscribers(this.#queuedChanges);
this.#queuedChanges = [];
}
}
[Symbol.dispose]() {
this.#subscribers.clear();
this.#changeListeners.clear();
this.#queuedChanges = [];
netManager.removeNetworkedMap(this.#syncName);
GlobalData.NetworkedTicks.filter((v) => v !== this);
}
/**
* Unregisters from the tick handler and removes the event listener
*/
dispose() {
this[Symbol.dispose]();
}
get [Symbol.toStringTag]() {
return "NetworkedMap";
}
}
export {
NetworkedMap
};