Added distributed loading of units to avoid hanging at mission start

This commit is contained in:
dpassoni
2023-03-06 16:10:49 +01:00
parent 9f738950df
commit 53ca062b71
4 changed files with 108 additions and 142 deletions

View File

@@ -7,6 +7,9 @@ import { unitTypes } from "../units/unittypes";
import { BoxSelect } from "./boxselect"; import { BoxSelect } from "./boxselect";
import { ContextMenuOption } from "../@types/dom"; import { ContextMenuOption } from "../@types/dom";
export const IDLE = "IDLE";
export const MOVE_UNIT = "MOVE_UNIT";
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect); L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
export interface ClickEvent { export interface ClickEvent {
@@ -41,7 +44,7 @@ export class Map extends L.Map {
this.setLayer("ArcGIS Satellite"); this.setLayer("ArcGIS Satellite");
/* Init the state machine */ /* Init the state machine */
this.#state = "IDLE"; this.#state = IDLE;
this.#measurePoint = null; this.#measurePoint = null;
this.#measureIcon = new L.Icon({ iconUrl: 'images/pin.png', iconAnchor: [16, 32]}); this.#measureIcon = new L.Icon({ iconUrl: 'images/pin.png', iconAnchor: [16, 32]});
@@ -114,10 +117,10 @@ export class Map extends L.Map {
/* State machine */ /* State machine */
setState(state: string) { setState(state: string) {
this.#state = state; this.#state = state;
if (this.#state === "IDLE") { if (this.#state === IDLE) {
L.DomUtil.removeClass(this.getContainer(),'crosshair-cursor-enabled'); L.DomUtil.removeClass(this.getContainer(),'crosshair-cursor-enabled');
} }
else if (this.#state === "MOVE_UNIT") { else if (this.#state === MOVE_UNIT) {
L.DomUtil.addClass(this.getContainer(),'crosshair-cursor-enabled'); L.DomUtil.addClass(this.getContainer(),'crosshair-cursor-enabled');
} }
document.dispatchEvent(new CustomEvent("mapStateChanged")); document.dispatchEvent(new CustomEvent("mapStateChanged"));
@@ -158,7 +161,7 @@ export class Map extends L.Map {
#onClick(e: any) { #onClick(e: any) {
if (!this.#preventLeftClick) { if (!this.#preventLeftClick) {
this.hideContextMenu(); this.hideContextMenu();
if (this.#state === "IDLE") { if (this.#state === IDLE) {
if (e.originalEvent.ctrlKey) if (e.originalEvent.ctrlKey)
if (!this.#measurePoint) if (!this.#measurePoint)
{ {
@@ -173,8 +176,8 @@ export class Map extends L.Map {
this.removeLayer(this.#measureMarker); this.removeLayer(this.#measureMarker);
} }
} }
else if (this.#state === "MOVE_UNIT") { else if (this.#state === MOVE_UNIT) {
this.setState("IDLE"); this.setState(IDLE);
getUnitsManager().deselectAllUnits(); getUnitsManager().deselectAllUnits();
this.hideContextMenu(); this.hideContextMenu();
} }
@@ -187,9 +190,9 @@ export class Map extends L.Map {
#onContextMenu(e: any) { #onContextMenu(e: any) {
this.hideContextMenu(); this.hideContextMenu();
if (this.#state === "IDLE") { if (this.#state === IDLE) {
var spawnEvent: SpawnEvent = {x: e.originalEvent.x, y: e.originalEvent.y, latlng: e.latlng, airbaseName: null, coalitionID: null}; var spawnEvent: SpawnEvent = {x: e.originalEvent.x, y: e.originalEvent.y, latlng: e.latlng, airbaseName: null, coalitionID: null};
if (this.#state == "IDLE") { if (this.#state == IDLE) {
var options = [ var options = [
{ "tooltip": "Spawn air unit", "src": "spawnAir.png", "callback": () => this.#aircraftSpawnMenu(spawnEvent) }, { "tooltip": "Spawn air unit", "src": "spawnAir.png", "callback": () => this.#aircraftSpawnMenu(spawnEvent) },
{ "tooltip": "Spawn ground unit", "src": "spawnGround.png", "callback": () => this.#groundUnitSpawnMenu(spawnEvent) }, { "tooltip": "Spawn ground unit", "src": "spawnGround.png", "callback": () => this.#groundUnitSpawnMenu(spawnEvent) },
@@ -199,7 +202,7 @@ export class Map extends L.Map {
this.showContextMenu(spawnEvent, "Action", options, false); this.showContextMenu(spawnEvent, "Action", options, false);
} }
} }
else if (this.#state === "MOVE_UNIT") { else if (this.#state === MOVE_UNIT) {
if (!e.originalEvent.ctrlKey) { if (!e.originalEvent.ctrlKey) {
getUnitsManager().selectedUnitsClearDestinations(); getUnitsManager().selectedUnitsClearDestinations();
} }

View File

@@ -3,12 +3,12 @@ import { Slider } from "../controls/slider";
import { Aircraft, AirUnit, GroundUnit, Helicopter, NavyUnit, Unit } from "../units/unit"; import { Aircraft, AirUnit, GroundUnit, Helicopter, NavyUnit, Unit } from "../units/unit";
import { Panel } from "./panel"; import { Panel } from "./panel";
var ROEs: string[] = ["Free", "Designated free", "Designated", "Return", "Hold"]; const ROEs: string[] = ["Free", "Designated free", "Designated", "Return", "Hold"];
var reactionsToThreat: string[] = [ "None", "Passive", "Evade", "Escape", "Abort"]; const reactionsToThreat: string[] = [ "None", "Passive", "Evade", "Escape", "Abort"];
var minSpeedValues: {[key: string]: number} = {Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0}; const minSpeedValues: {[key: string]: number} = {Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0};
var maxSpeedValues: {[key: string]: number} = {Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60}; const maxSpeedValues: {[key: string]: number} = {Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60};
var minAltitudeValues: {[key: string]: number} = {Aircraft: 500, Helicopter: 0, NavyUnit: 0, GroundUnit: 0}; const minAltitudeValues: {[key: string]: number} = {Aircraft: 500, Helicopter: 0, NavyUnit: 0, GroundUnit: 0};
var maxAltitudeValues: {[key: string]: number} = {Aircraft: 50000, Helicopter: 10000, NavyUnit: 60, GroundUnit: 60}; const maxAltitudeValues: {[key: string]: number} = {Aircraft: 50000, Helicopter: 10000, NavyUnit: 60, GroundUnit: 60};
export class UnitControlPanel extends Panel { export class UnitControlPanel extends Panel {
#altitudeSlider: Slider; #altitudeSlider: Slider;
@@ -40,6 +40,9 @@ export class UnitControlPanel extends Panel {
this.getElement().querySelector("#roe-buttons-container")?.append(...this.#optionButtons["ROE"]); this.getElement().querySelector("#roe-buttons-container")?.append(...this.#optionButtons["ROE"]);
this.getElement().querySelector("#reaction-to-threat-buttons-container")?.append(...this.#optionButtons["reactionToThreat"]); this.getElement().querySelector("#reaction-to-threat-buttons-container")?.append(...this.#optionButtons["reactionToThreat"]);
document.addEventListener("unitsSelection", (e: CustomEvent<Unit[]>) => {console.log("Select"); this.show(); this.update(e.detail)});
document.addEventListener("clearSelection", () => {this.hide()});
this.hide(); this.hide();
} }

View File

@@ -49,7 +49,7 @@ export function getUnits(callback: CallableFunction, refresh: boolean = false) {
if (!DEMO) if (!DEMO)
GET(callback, `${UNITS_URI}/${refresh? REFRESH_URI: UPDATE_URI}}`); GET(callback, `${UNITS_URI}/${refresh? REFRESH_URI: UPDATE_URI}}`);
else else
callback(DEMO_UNITS_DATA); callback(refresh? generateRandomUnitsDemoData(1000): {units:{}});
} }
export function addDestination(ID: number, path: any) { export function addDestination(ID: number, path: any) {
@@ -142,91 +142,57 @@ export function setReactionToThreat(ID: number, reactionToThreat: string) {
POST(data, () => { }); POST(data, () => { });
} }
const DEMO_UNITS_DATA = {
units: { function generateRandomUnitsDemoData(unitsNumber: number)
"1": { {
AI: true, var units: any = {};
name: "F-5E", for (let i = 0; i < unitsNumber; i++)
unitName: "Olympus 1-1", {
groupName: "Group 1", units[String(i)] = structuredClone(DEMO_UNIT_DATA);
alive: true, units[String(i)].flightData.latitude += (Math.random() - 0.5) * 0.1;
category: "Aircraft", units[String(i)].flightData.longitude += (Math.random() - 0.5) * 0.1;
flightData: { }
latitude: 37.3, return {"units": units};
longitude: -116, }
altitude: 2000,
heading: 0.5, const DEMO_UNIT_DATA = {
speed: 300 AI: true,
}, name: "F-5E",
missionData: { unitName: "Olympus 1-1",
fuel: 0.5, groupName: "Group 1",
flags: {human: false}, alive: true,
ammo: [], category: "Aircraft",
targets: [], flightData: {
hasTask: true, latitude: 37.3,
coalition: "blue" longitude: -116,
}, altitude: 2000,
formationData: { heading: 0.5,
formation: "Echelon", speed: 300
isLeader: false,
isWingman: false,
leaderID: null,
wingmen: [],
wingmenIDs: []
},
taskData: {
currentTask: "Example task",
activePath: undefined,
targetSpeed: 400,
targetAltitude: 3000
},
optionsData: {
ROE: "None",
reactionToThreat: "None",
}
},
"2": {
AI: true,
name: "F-5E",
unitName: "Olympus 1-1",
groupName: "Group 1",
alive: true,
category: "Aircraft",
flightData: {
latitude: 37.3,
longitude: -115.9,
altitude: 2000,
heading: .5,
speed: 300
},
missionData: {
fuel: 0.5,
flags: {human: false},
ammo: [],
targets: [],
hasTask: true,
coalition: "red"
},
formationData: {
formation: "Echelon",
isLeader: false,
isWingman: false,
leaderID: null,
wingmen: [],
wingmenIDs: []
},
taskData: {
currentTask: "Example task",
activePath: undefined,
targetSpeed: 400,
targetAltitude: 3000
},
optionsData: {
ROE: "None",
reactionToThreat: "None",
}
}
}, },
bullseyes: [], missionData: {
airbases: [] fuel: 0.5,
flags: {human: false},
ammo: [],
targets: [],
hasTask: true,
coalition: "blue"
},
formationData: {
formation: "Echelon",
isLeader: false,
isWingman: false,
leaderID: null,
wingmen: [],
wingmenIDs: []
},
taskData: {
currentTask: "Example task",
activePath: undefined,
targetSpeed: 400,
targetAltitude: 3000
},
optionsData: {
ROE: "None",
reactionToThreat: "None",
}
} }

View File

@@ -2,10 +2,12 @@ import { LatLng, LatLngBounds } from "leaflet";
import { getMap, getUnitControlPanel, getUnitInfoPanel } from ".."; import { getMap, getUnitControlPanel, getUnitInfoPanel } from "..";
import { Unit, GroundUnit } from "./unit"; import { Unit, GroundUnit } from "./unit";
import { cloneUnit } from "../server/server"; import { cloneUnit } from "../server/server";
import { IDLE, MOVE_UNIT } from "../map/map";
export class UnitsManager { export class UnitsManager {
#units: { [ID: number]: Unit }; #units: { [ID: number]: Unit };
#copiedUnits: Unit[]; #copiedUnits: Unit[];
#selectionEventDisabled: boolean = false;
constructor() { constructor() {
this.#units = {}; this.#units = {};
@@ -13,7 +15,7 @@ export class UnitsManager {
document.addEventListener('copy', () => this.copyUnits()); document.addEventListener('copy', () => this.copyUnits());
document.addEventListener('paste', () => this.pasteUnits()); document.addEventListener('paste', () => this.pasteUnits());
document.addEventListener('unitSelection', () => this.onUnitSelection()); document.addEventListener('unitSelection', (e: CustomEvent) => this.onUnitSelection(e.detail));
document.addEventListener('keydown', (event) => this.#onKeyDown(event)); document.addEventListener('keydown', (event) => this.#onKeyDown(event));
} }
@@ -41,13 +43,19 @@ export class UnitsManager {
} }
update(data: UnitsData) { update(data: UnitsData) {
for (let ID in data.units) { Object.keys(data.units)
/* Create the unit if missing from the local array, then update the data. Drawing is handled by leaflet. */ .filter((ID: string) => !(ID in this.#units))
if (!(ID in this.#units)) { .reduce((timeout: number, ID: string) => {
this.addUnit(parseInt(ID), data.units[ID]); setTimeout(() => {
} this.addUnit(parseInt(ID), data.units[ID]);
this.#units[parseInt(ID)].setData(data.units[ID]); this.#units[parseInt(ID)].setData(data.units[ID]);
} }, timeout);
return timeout + 10;
}, 10);
Object.keys(data.units)
.filter((ID: string) => ID in this.#units)
.forEach((ID: string) => this.#units[parseInt(ID)].setData(data.units[ID]));
/* Update the unit info panel */ /* Update the unit info panel */
if (this.getSelectedUnits().length == 1) { if (this.getSelectedUnits().length == 1) {
@@ -67,21 +75,28 @@ export class UnitsManager {
selectUnit(ID: number, deselectAllUnits: boolean = true) selectUnit(ID: number, deselectAllUnits: boolean = true)
{ {
if (deselectAllUnits) if (deselectAllUnits)
this.deselectAllUnits(); this.getSelectedUnits().filter((unit: Unit) => unit.ID !== ID ).forEach((unit: Unit) => unit.setSelected(false));
this.#units[ID]?.setSelected(true); this.#units[ID]?.setSelected(true);
} }
onUnitSelection() { onUnitSelection(unit: Unit) {
if (this.getSelectedUnits().length > 0) { if (this.getSelectedUnits().length > 0) {
getMap().setState("MOVE_UNIT"); getMap().setState(MOVE_UNIT);
document.dispatchEvent(new CustomEvent("unitsSelection", {detail: this.getSelectedUnits()})); /* Disable the firing of the selection event for a certain amount of time. This avoids firing many events if many units are selected */
if (!this.#selectionEventDisabled)
{
setTimeout(() => {
document.dispatchEvent(new CustomEvent("unitsSelection", {detail: this.getSelectedUnits()}));
this.#selectionEventDisabled = false;
}, 300);
this.#selectionEventDisabled = true;
}
} }
else { else {
getMap().setState("IDLE"); getMap().setState(IDLE);
document.dispatchEvent(new CustomEvent("clearSelection")); document.dispatchEvent(new CustomEvent("clearSelection"));
} }
this.#updateUnitControlPanel();
} }
selectFromBounds(bounds: LatLngBounds) selectFromBounds(bounds: LatLngBounds)
@@ -147,7 +162,7 @@ export class UnitsManager {
getSelectedUnitsType () { getSelectedUnitsType () {
return this.getSelectedUnits().map((unit: Unit) => { return this.getSelectedUnits().map((unit: Unit) => {
return unit.constructor.name return unit.constructor.name
}).reduce((a: any, b: any) => { })?.reduce((a: any, b: any) => {
return a == b? a: undefined return a == b? a: undefined
}); });
}; };
@@ -155,7 +170,7 @@ export class UnitsManager {
getSelectedUnitsTargetSpeed () { getSelectedUnitsTargetSpeed () {
return this.getSelectedUnits().map((unit: Unit) => { return this.getSelectedUnits().map((unit: Unit) => {
return unit.getTaskData().targetSpeed return unit.getTaskData().targetSpeed
}).reduce((a: any, b: any) => { })?.reduce((a: any, b: any) => {
return a == b? a: undefined return a == b? a: undefined
}); });
}; };
@@ -163,7 +178,7 @@ export class UnitsManager {
getSelectedUnitsTargetAltitude () { getSelectedUnitsTargetAltitude () {
return this.getSelectedUnits().map((unit: Unit) => { return this.getSelectedUnits().map((unit: Unit) => {
return unit.getTaskData().targetAltitude return unit.getTaskData().targetAltitude
}).reduce((a: any, b: any) => { })?.reduce((a: any, b: any) => {
return a == b? a: undefined return a == b? a: undefined
}); });
}; };
@@ -200,8 +215,6 @@ export class UnitsManager {
{ {
selectedUnits[idx].changeSpeed(speedChange); selectedUnits[idx].changeSpeed(speedChange);
} }
setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail
} }
selectedUnitsChangeAltitude(altitudeChange: string) selectedUnitsChangeAltitude(altitudeChange: string)
@@ -211,8 +224,6 @@ export class UnitsManager {
{ {
selectedUnits[idx].changeAltitude(altitudeChange); selectedUnits[idx].changeAltitude(altitudeChange);
} }
setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail
} }
selectedUnitsSetSpeed(speed: number) selectedUnitsSetSpeed(speed: number)
@@ -240,8 +251,6 @@ export class UnitsManager {
{ {
selectedUnits[idx].setROE(ROE); selectedUnits[idx].setROE(ROE);
} }
setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail
} }
selectedUnitsSetReactionToThreat(reactionToThreat: string) selectedUnitsSetReactionToThreat(reactionToThreat: string)
@@ -251,8 +260,6 @@ export class UnitsManager {
{ {
selectedUnits[idx].setReactionToThreat(reactionToThreat); selectedUnits[idx].setReactionToThreat(reactionToThreat);
} }
setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail
} }
selectedUnitsAttackUnit(ID: number) { selectedUnitsAttackUnit(ID: number) {
@@ -312,16 +319,14 @@ export class UnitsManager {
//console.log("At least 2 units must be selected to create a formation."); //console.log("At least 2 units must be selected to create a formation.");
} }
} }
setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail
} }
selectedUnitsUndoFormation(ID: number | null = null) selectedUnitsUndoFormation()
{ {
for (let leader of this.getSelectedLeaders()) for (let leader of this.getSelectedLeaders())
{ {
leader.setLeader(false); leader.setLeader(false);
} }
setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail
} }
selectedUnitsDelete() selectedUnitsDelete()
@@ -347,17 +352,6 @@ export class UnitsManager {
} }
} }
#updateUnitControlPanel() {
/* Update the unit control panel */
if (this.getSelectedUnits().length > 0) {
getUnitControlPanel()?.show();
getUnitControlPanel()?.update(this.getSelectedLeaders().concat(this.getSelectedSingletons()));
}
else {
getUnitControlPanel()?.hide();
}
}
#onKeyDown(event: KeyboardEvent) #onKeyDown(event: KeyboardEvent)
{ {
if (event.key === "Delete") if (event.key === "Delete")