From 9d0e2239e49d502a94bfb4fe8f18d010c1234299 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Wed, 21 Jun 2023 20:05:41 +0200 Subject: [PATCH] Added new method for handling data --- client/src/constants/constants.ts | 2 + .../src/controls/coalitionareacontextmenu.ts | 5 +-- client/src/map/coalitionarea.ts | 7 ++-- client/src/map/map.ts | 41 ++++++++++++------- client/src/server/server.ts | 27 ++++++------ client/src/units/unitsmanager.ts | 6 +-- scripts/OlympusCommand.lua | 4 +- src/core/include/commands.h | 6 +-- src/core/include/server.h | 4 ++ src/core/include/unit.h | 2 +- src/core/include/unitsmanager.h | 6 ++- src/core/src/core.cpp | 41 ++++++++++++++++--- src/core/src/scheduler.cpp | 2 +- src/core/src/server.cpp | 8 +++- src/core/src/unit.cpp | 32 +++++++-------- src/core/src/unitsmanager.cpp | 37 ++++++++--------- 16 files changed, 137 insertions(+), 93 deletions(-) diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts index a230e17d..4fabffcf 100644 --- a/client/src/constants/constants.ts +++ b/client/src/constants/constants.ts @@ -111,3 +111,5 @@ export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area"; export const COALITIONAREA_INTERACT = "Interact with Coalition Areas" export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"]; export const visibilityControlsTootlips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"]; + +export const IADSRoles: {[key: string]: number}= {"AAA": 0.8, "MANPADS": 0.3, "SAM Sites": 0.1, "Radar": 0.05}; \ No newline at end of file diff --git a/client/src/controls/coalitionareacontextmenu.ts b/client/src/controls/coalitionareacontextmenu.ts index 03b67d90..dcb9bc23 100644 --- a/client/src/controls/coalitionareacontextmenu.ts +++ b/client/src/controls/coalitionareacontextmenu.ts @@ -1,12 +1,11 @@ import { getMap, getUnitsManager } from ".."; +import { IADSRoles } from "../constants/constants"; import { CoalitionArea } from "../map/coalitionarea"; import { ContextMenu } from "./contextmenu"; import { Dropdown } from "./dropdown"; import { Slider } from "./slider"; import { Switch } from "./switch"; -const unitRole = ["AAA", "MANPADS", "SAM Sites", "Radar"]; - export class CoalitionAreaContextMenu extends ContextMenu { #coalitionSwitch: Switch; #coalitionArea: CoalitionArea | null = null; @@ -57,7 +56,7 @@ export class CoalitionAreaContextMenu extends ContextMenu { }) /* Create the checkboxes to select the unit roles */ - this.#iadsRoleDropdown.setOptionsElements(unitRole.map((unitRole: string) => { + this.#iadsRoleDropdown.setOptionsElements(Object.keys(IADSRoles).map((unitRole: string) => { var div = document.createElement("div"); div.classList.add("ol-checkbox"); var label = document.createElement("label"); diff --git a/client/src/map/coalitionarea.ts b/client/src/map/coalitionarea.ts index 2f44d9f5..2e2ab0fe 100644 --- a/client/src/map/coalitionarea.ts +++ b/client/src/map/coalitionarea.ts @@ -66,11 +66,10 @@ export class CoalitionArea extends Polygon { this.setOpacity(interactive? 1: 0.5); this.options.interactive = interactive; - if (interactive) { + if (interactive) DomUtil.addClass(this.getElement() as HTMLElement, 'leaflet-interactive'); - } else { - DomUtil.removeClass(this.getElement() as HTMLElement, 'leaflet-interactive'); - } + else + DomUtil.removeClass(this.getElement() as HTMLElement, 'leaflet-interactive'); } addTemporaryLatLng(latlng: LatLng) { diff --git a/client/src/map/map.ts b/client/src/map/map.ts index 864f4783..fc4e0cc7 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -396,18 +396,18 @@ export class Map extends L.Map { removeTemporaryMarker(latlng: L.LatLng) { // TODO something more refined than this - var d: number | null = null; + var dist: number | null = null; var closest: L.Marker | null = null; var i: number = 0; this.#temporaryMarkers.forEach((marker: L.Marker, idx: number) => { var t = latlng.distanceTo(marker.getLatLng()); - if (d == null || t < d) { - d = t; + if (dist == null || t < dist) { + dist = t; closest = marker; i = idx; } }); - if (closest) { + if (closest && dist != null && dist < 100) { this.removeLayer(closest); this.#temporaryMarkers.splice(i, 1); } @@ -582,29 +582,40 @@ export class Map extends L.Map { document.getElementById(this.#ID)?.classList.add("hidden-cursor"); } - #showDestinationCursors() { + #showDestinationCursors(singleCursor: boolean) { /* Don't create the cursors if there already are the correct number of them available */ - if (getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length != this.#destinationPreviewCursors.length) { + if (singleCursor || getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length != this.#destinationPreviewCursors.length) { /* Reset the cursors to start from a clean condition */ this.#hideDestinationCursors(); if (getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length > 0) { - /* Create the cursors. If a group is selected only one cursor is shown for it, because you can't control single units in a group */ - this.#destinationPreviewCursors = getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).map((unit: Unit) => { + if (singleCursor) { var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false }); marker.addTo(this); - return marker; - }) + this.#destinationPreviewCursors = [marker]; + } + else { + /* Create the cursors. If a group is selected only one cursor is shown for it, because you can't control single units in a group */ + this.#destinationPreviewCursors = getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).map((unit: Unit) => { + var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false }); + marker.addTo(this); + return marker; + }); + } } } } #updateDestinationCursors(e: any) { const groupLatLng = this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : this.getMouseCoordinates(); - Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(groupLatLng, this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => { - if (idx < this.#destinationPreviewCursors.length) - this.#destinationPreviewCursors[idx].setLatLng(e.originalEvent.shiftKey ? latlng : this.getMouseCoordinates()); - }) + if (this.#destinationPreviewCursors.length == 1) + this.#destinationPreviewCursors[0].setLatLng(this.getMouseCoordinates()); + else { + Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(groupLatLng, this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => { + if (idx < this.#destinationPreviewCursors.length) + this.#destinationPreviewCursors[idx].setLatLng(e.originalEvent.shiftKey ? latlng : this.getMouseCoordinates()); + }) + }; } #hideDestinationCursors() { @@ -660,7 +671,7 @@ export class Map extends L.Map { /* Show the active cursor depending on the active state */ if (this.#state === IDLE || this.#state === COALITIONAREA_INTERACT) this.#showDefaultCursor(); - else if (this.#state === MOVE_UNIT) this.#showDestinationCursors(); + else if (this.#state === MOVE_UNIT) this.#showDestinationCursors(!e.originalEvent.shiftKey); else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) this.#showTargetCursor(); else if (this.#state === COALITIONAREA_DRAW_POLYGON) this.#showDrawingCursor(); } diff --git a/client/src/server/server.ts b/client/src/server/server.ts index fd71dba2..f3ea1afd 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -321,22 +321,19 @@ export function requestUpdate() { export function requestRefresh() { /* Main refresh rate = 5000ms. */ - getUnits((data: UnitsData) => { - if (!getPaused()) { - getUnitsManager()?.update(data); - getAirbases((data: AirbasesData) => getMissionData()?.update(data)); - getBullseye((data: BullseyesData) => getMissionData()?.update(data)); - getMission((data: any) => { - getMissionData()?.update(data) - }); - - // Update the list of existing units - getUnitDataTable()?.update(); - + + if (!getPaused()) { + getAirbases((data: AirbasesData) => getMissionData()?.update(data)); + getBullseye((data: BullseyesData) => getMissionData()?.update(data)); + getMission((data: any) => { checkSessionHash(data.sessionHash); - } - }, true); - window.setTimeout(() => requestRefresh(), 5000); + getMissionData()?.update(data) + }); + + // Update the list of existing units + getUnitDataTable()?.update(); + } + window.setTimeout(() => requestRefresh(), 1000); } export function checkSessionHash(newSessionHash: string) { diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index b45cf120..35a7afcb 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -6,7 +6,7 @@ import { deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, import { CoalitionArea } from "../map/coalitionarea"; import { Airbase } from "../missionhandler/airbase"; import { groundUnitsDatabase } from "./groundunitsdatabase"; -import { IDLE, MOVE_UNIT } from "../constants/constants"; +import { IADSRoles, IDLE, MOVE_UNIT } from "../constants/constants"; export class UnitsManager { #units: { [ID: number]: Unit }; @@ -516,9 +516,9 @@ export class UnitsManager { if (distance > maxDistance) maxDistance = distance; }); - const probability = Math.pow(1 - minDistance / 50e3, 5); + const role = activeRoles[Math.floor(Math.random() * activeRoles.length)]; + const probability = Math.pow(1 - minDistance / 50e3, 5) * IADSRoles[role]; if (Math.random() < probability){ - const role = activeRoles[Math.floor(Math.random() * activeRoles.length)]; const unitBlueprint = randomUnitBlueprintByRole(groundUnitsDatabase, role); const spawnOptions = {role: role, latlng: latlng, name: unitBlueprint.name, coalition: coalitionArea.getCoalition(), immediate: true}; spawnGroundUnit(spawnOptions); diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index 34d74cb5..50772ef6 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -1,6 +1,6 @@ local version = "v0.3.0-alpha" -local debug = true +local debug = false Olympus.unitCounter = 1 Olympus.payloadRegistry = {} @@ -343,7 +343,7 @@ function Olympus.spawnGroundUnit(coalition, unitType, lat, lng) units = unitTable, country = countryID, category = 'vehicle', - name = "Olympus-" .. Olympus.unitCounter, + name = "Ground-" .. Olympus.unitCounter, } mist.dynAdd(vars) Olympus.unitCounter = Olympus.unitCounter + 1 diff --git a/src/core/include/commands.h b/src/core/include/commands.h index b3668f32..1cd8aae4 100644 --- a/src/core/include/commands.h +++ b/src/core/include/commands.h @@ -5,7 +5,7 @@ #include "logger.h" namespace CommandPriority { - enum CommandPriorities { LOW, MEDIUM, HIGH }; + enum CommandPriorities { LOW, MEDIUM, HIGH, IMMEDIATE }; }; namespace SetCommandType { @@ -151,7 +151,7 @@ public: location(location), immediate(immediate) { - priority = CommandPriority::LOW; + priority = immediate? CommandPriority::IMMEDIATE: CommandPriority::LOW; }; virtual wstring getString(lua_State* L); virtual int getLoad() { return 100 * !immediate; } @@ -175,7 +175,7 @@ public: airbaseName(airbaseName), immediate(immediate) { - priority = CommandPriority::LOW; + priority = immediate ? CommandPriority::IMMEDIATE : CommandPriority::LOW; }; virtual wstring getString(lua_State* L); virtual int getLoad() { return 100 * !immediate; } diff --git a/src/core/include/server.h b/src/core/include/server.h index ec08d264..cb735442 100644 --- a/src/core/include/server.h +++ b/src/core/include/server.h @@ -15,6 +15,8 @@ public: void start(lua_State* L); void stop(lua_State* L); + json::value& getUpdateJson() { return updateJson; } + json::value& getRefreshJson() { return refreshJson; } private: std::thread* serverThread; @@ -27,6 +29,8 @@ private: void task(); atomic runListener; + json::value updateJson; + json::value refreshJson; wstring password = L""; }; diff --git a/src/core/include/unit.h b/src/core/include/unit.h index 817beb29..196e89b1 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -65,7 +65,7 @@ public: void setDefaults(bool force = false); int getID() { return ID; } void runAILoop(); - void updateExportData(json::value json); + void updateExportData(json::value json, double dt = 0); void updateMissionData(json::value json); json::value getData(long long time, bool getAll = false); virtual wstring getCategory() { return L"No category"; }; diff --git a/src/core/include/unitsmanager.h b/src/core/include/unitsmanager.h index 871c9e74..0a0e138b 100644 --- a/src/core/include/unitsmanager.h +++ b/src/core/include/unitsmanager.h @@ -10,16 +10,18 @@ public: UnitsManager(lua_State* L); ~UnitsManager(); + map& getUnits() { return units; }; Unit* getUnit(int ID); bool isUnitInGroup(Unit* unit); bool isUnitGroupLeader(Unit* unit); Unit* getGroupLeader(int ID); Unit* getGroupLeader(Unit* unit); vector getGroupMembers(wstring groupName); - void updateExportData(lua_State* L); + void updateExportData(lua_State* L, double dt = 0); void updateMissionData(json::value missionData); void runAILoop(); - void getData(json::value& answer, long long time); + void getUnitData(json::value& answer, long long time); + void appendUnitData(int ID, json::value& answer, long long time); void deleteUnit(int ID, bool explosion); void acquireControl(int ID); diff --git a/src/core/src/core.cpp b/src/core/src/core.cpp index 38e490f2..aab6d0ac 100644 --- a/src/core/src/core.cpp +++ b/src/core/src/core.cpp @@ -6,6 +6,8 @@ #include "scheduler.h" #include "scriptLoader.h" #include "luatools.h" +#include +using namespace std::chrono; auto before = std::chrono::system_clock::now(); UnitsManager* unitsManager = nullptr; @@ -17,6 +19,10 @@ json::value mission; mutex mutexLock; bool initialized = false; string sessionHash; +int lastUpdateIndex = 0; +int frameCounter = 0; +double frameRate = 30; +long long lastUpdateTime = 0; /* Called when DCS simulation stops. All singleton instances are deleted. */ extern "C" DllExport int coreDeinit(lua_State* L) @@ -61,21 +67,44 @@ extern "C" DllExport int coreFrame(lua_State* L) /* Lock for thread safety */ lock_guard guard(mutexLock); + frameCounter++; + const std::chrono::duration duration = std::chrono::system_clock::now() - before; - /* TODO make intervals editable */ - if (duration.count() > UPDATE_TIME_INTERVAL) + if (unitsManager != nullptr) { + // TODO put in a function + vector IDs; + for (auto iter = unitsManager->getUnits().begin(); iter != unitsManager->getUnits().end(); ++iter) + IDs.push_back(iter->first); + + int updateChunk = 20; + int finalUpdateIndex = lastUpdateIndex + updateChunk; + + /* Get all the new data (with some margin) */ + while (lastUpdateIndex < unitsManager->getUnits().size() && lastUpdateIndex <= finalUpdateIndex) + unitsManager->appendUnitData(IDs[lastUpdateIndex++], server->getUpdateJson(), lastUpdateTime - 1000); + } + + if (duration.count() > UPDATE_TIME_INTERVAL && lastUpdateIndex == unitsManager->getUnits().size()) { + milliseconds ms = duration_cast(system_clock::now().time_since_epoch()); + lastUpdateTime = ms.count(); + frameRate = frameCounter / duration.count(); + frameCounter = 0; + if (unitsManager != nullptr) - { - unitsManager->updateExportData(L); - unitsManager->runAILoop(); - } + unitsManager->updateExportData(L, duration.count()); before = std::chrono::system_clock::now(); + + /* Restart the update counter */ + lastUpdateIndex = 0; } if (scheduler != nullptr) scheduler->execute(L); + + if (duration.count() > UPDATE_TIME_INTERVAL && unitsManager != nullptr) + unitsManager->runAILoop(); return(0); } diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp index 2b55058e..76d3ffcd 100644 --- a/src/core/src/scheduler.cpp +++ b/src/core/src/scheduler.cpp @@ -32,7 +32,7 @@ void Scheduler::execute(lua_State* L) return; } - int priority = CommandPriority::HIGH; + int priority = CommandPriority::IMMEDIATE; while (priority >= CommandPriority::LOW) { for (auto command : commands) diff --git a/src/core/src/server.cpp b/src/core/src/server.cpp index a2b01978..621f0e43 100644 --- a/src/core/src/server.cpp +++ b/src/core/src/server.cpp @@ -36,7 +36,8 @@ Server::Server(lua_State* L): serverThread(nullptr), runListener(true) { - + refreshJson = json::value::object(); + updateJson = json::value::object(); } void Server::start(lua_State* L) @@ -93,7 +94,10 @@ void Server::handle_get(http_request request) time = 0; } } - unitsManager->getData(answer, time); + if (time == 0) + unitsManager->getUnitData(answer, 0); + else + answer[L"units"] = updateJson; } else if (path[0] == LOGS_URI) { diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index 4b18efc7..cf7ba257 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -92,31 +92,31 @@ void Unit::addMeasure(wstring key, json::value value) } void Unit::runAILoop() { - /* If the unit is alive and it is not a human, run the AI Loop that performs the requested commands and instructions (moving, attacking, etc) */ - const bool isUnitControlledByOlympus = getControlled(); - const bool isUnitAlive = getAlive(); - const bool isUnitLeader = unitsManager->isUnitGroupLeader(this); - const bool isUnitLeaderOfAGroupWithOtherUnits = unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this); - const bool isUnitHuman = getFlags()[L"Human"].as_bool(); + /* If the unit is alive, controlled and it is not a human, run the AI Loop that performs the requested commands and instructions (moving, attacking, etc) */ + if (!getControlled()) return; + if (!unitsManager->isUnitGroupLeader(this)) return; + if (getFlags()[L"Human"].as_bool()) return; // Keep running the AI loop even if the unit is dead if it is the leader of a group which has other members in it - if (isUnitControlledByOlympus && (isUnitAlive || isUnitLeaderOfAGroupWithOtherUnits) && isUnitLeader && !isUnitHuman) - { - if (checkTaskFailed() && state != State::IDLE && State::LAND) - setState(State::IDLE); + const bool isUnitAlive = getAlive(); + const bool isUnitLeaderOfAGroupWithOtherUnits = unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this); + if (!(isUnitAlive || isUnitLeaderOfAGroupWithOtherUnits)) return; + + if (checkTaskFailed() && state != State::IDLE && State::LAND) + setState(State::IDLE); - AIloop(); - } + AIloop(); } -void Unit::updateExportData(json::value json) +void Unit::updateExportData(json::value json, double dt) { /* Compute speed (loGetWorldObjects does not provide speed, we compute it for better performance instead of relying on many lua calls) */ if (oldPosition != NULL) { double dist = 0; Geodesic::WGS84().Inverse(latitude, longitude, oldPosition.lat, oldPosition.lng, dist); - setSpeed(getSpeed() * 0.95 + (dist / UPDATE_TIME_INTERVAL) * 0.05); + if (dt > 0) + setSpeed(getSpeed() * 0.95 + (dist / dt) * 0.05); } oldPosition = Coords(latitude, longitude, altitude); @@ -160,7 +160,7 @@ void Unit::updateMissionData(json::value json) setHasTask(json[L"hasTask"].as_bool()); } -json::value Unit::getData(long long time, bool sendAll) +json::value Unit::getData(long long time, bool getAll) { auto json = json::value::object(); @@ -178,7 +178,7 @@ json::value Unit::getData(long long time, bool sendAll) if (json[L"baseData"].size() == 0) json.erase(L"baseData"); - if (alive || sendAll) { + if (alive || getAll) { /********** Flight data **********/ json[L"flightData"] = json::value::object(); for (auto key : { L"latitude", L"longitude", L"altitude", L"speed", L"heading" }) diff --git a/src/core/src/unitsmanager.cpp b/src/core/src/unitsmanager.cpp index 2c683858..4d9ed681 100644 --- a/src/core/src/unitsmanager.cpp +++ b/src/core/src/unitsmanager.cpp @@ -59,18 +59,11 @@ Unit* UnitsManager::getGroupLeader(Unit* unit) if (unit != nullptr) { wstring groupName = unit->getGroupName(); - /* Get the unit IDs in order */ - std::vector keys; - for (auto const& p : units) - keys.push_back(p.first); - sort(keys.begin(), keys.end()); - /* Find the first unit that has the same groupName */ - for (auto const& tempID : keys) + for (auto const& p : units) { - Unit* tempUnit = getUnit(tempID); - if (tempUnit != nullptr && tempUnit->getGroupName().compare(groupName) == 0) - return tempUnit; + if (p.second->getGroupName().compare(groupName) == 0) + return p.second; } } return nullptr; @@ -93,7 +86,7 @@ Unit* UnitsManager::getGroupLeader(int ID) return getGroupLeader(unit); } -void UnitsManager::updateExportData(lua_State* L) +void UnitsManager::updateExportData(lua_State* L, double dt) { map unitJSONs = getAllUnits(L); @@ -132,15 +125,13 @@ void UnitsManager::updateExportData(lua_State* L) else { /* Update the unit if present*/ if (units.count(ID) != 0) - units[ID]->updateExportData(p.second); + units[ID]->updateExportData(p.second, dt); } } /* Set the units that are not present in the JSON as dead (probably have been destroyed) */ for (auto const& unit : units) - { unit.second->setAlive(unitJSONs.find(unit.first) != unitJSONs.end()); - } } void UnitsManager::updateMissionData(json::value missionData) @@ -150,32 +141,38 @@ void UnitsManager::updateMissionData(json::value missionData) { int ID = p.first; if (missionData.has_field(to_wstring(ID))) - { p.second->updateMissionData(missionData[to_wstring(ID)]); - } } } void UnitsManager::runAILoop() { /* Run the AI Loop on all units */ for (auto const& unit : units) - { unit.second->runAILoop(); - } } -void UnitsManager::getData(json::value& answer, long long time) +void UnitsManager::getUnitData(json::value& answer, long long time) { auto unitsJson = json::value::object(); for (auto const& p : units) { auto unitJson = p.second->getData(time); if (unitJson.size() > 0) - unitsJson[to_wstring(p.first)] = p.second->getData(time); + unitsJson[to_wstring(p.first)] = unitJson; } answer[L"units"] = unitsJson; } +void UnitsManager::appendUnitData(int ID, json::value& answer, long long time) +{ + Unit* unit = getUnit(ID); + if (unit != nullptr) { + auto unitJson = unit->getData(time); + if (unitJson.size() > 0) + answer[to_wstring(ID)] = unitJson; + } +} + void UnitsManager::deleteUnit(int ID, bool explosion) { if (getUnit(ID) != nullptr)