Fixed or improved a variety of CPU and RAM issues

This commit is contained in:
Josh Larson
2025-05-25 15:35:48 -05:00
parent 1175485ada
commit e7496db079
7 changed files with 196 additions and 27 deletions
+1
View File
@@ -8,6 +8,7 @@ classes/
.classpath
.project
.kotlin
/.idea/AndroidProjectSystem.xml
# IntelliJ-User Specific
.idea/**/workspace.xml
.idea/**/tasks.xml
@@ -120,9 +120,13 @@ class NavigationPoint(val parent: SWGObject?, val location: Location, val speed:
}
fun from(sourceParent: SWGObject?, source: Location, destinationParent: SWGObject?, destination: Location, speed: Double): List<NavigationPoint> {
assert(sourceParent == null || sourceParent is CellObject) { "invalid source parent" }
assert(destinationParent == null || destinationParent is CellObject) { "invalid destination parent" }
assert(speed > 0) { "speed must be greater than zero, was $speed" }
if (sourceParent == destinationParent) return from(sourceParent, source, destination, speed)
var source = source
assert(sourceParent == null || sourceParent is CellObject)
assert(destinationParent == null || destinationParent is CellObject)
val route = getBuildingRoute(sourceParent as CellObject?, destinationParent as CellObject?, source, destination) ?: return ArrayList()
val points = createIntraBuildingRoute(route, sourceParent, source, speed)
if (route.isNotEmpty()) source = if (destinationParent == null) buildWorldPortalLocation(route[route.size - 1]) else buildPortalLocation(route[route.size - 1])
@@ -145,10 +149,14 @@ class NavigationPoint(val parent: SWGObject?, val location: Location, val speed:
val totalDistance = source.distanceTo(destination)
val path: MutableList<NavigationPoint> = ArrayList()
assert(speed > 0) { "speed must be greater than zero, was $speed" }
assert(totalDistance < 5_000) { "distance between waypoints is too large ($totalDistance)" }
var currentDistance = speed
while (currentDistance < totalDistance) {
path.add(interpolate(parent, source, destination, speed, currentDistance / totalDistance))
currentDistance += speed
assert(path.size < 10_000) { "path length growing too large" }
}
path.add(interpolate(parent, source, destination, speed, 1.0))
return path
@@ -229,7 +237,7 @@ class NavigationPoint(val parent: SWGObject?, val location: Location, val speed:
private fun buildWorldPortalLocation(portal: Portal): Location {
val building = portal.cell1!!.parent
assert(building is BuildingObject)
assert(building is BuildingObject) { "cell parent wasn't a building" }
return Location.builder(buildPortalLocation(portal)).translateLocation(building!!.location).build()
}
@@ -51,15 +51,19 @@ class NpcPatrolMode(obj: AIObject, waypoints: List<ResolvedPatrolWaypoint>) : Np
waypointBuilder.add(waypointBuilder[0])
}
this.waypoints = ArrayList<NavigationPoint>(128)
for (i in 1 until waypointBuilder.size) {
val source = waypointBuilder[i - 1]
val destination = waypointBuilder[i]
this.waypoints.addAll(NavigationPoint.from(source.parent, source.location, destination.parent, destination.location, walkSpeed))
if (destination.delay > 0)
this.waypoints.addAll(NavigationPoint.nop(this.waypoints[this.waypoints.size - 1], destination.delay.toInt() - 1))
if (waypointBuilder.isEmpty()) {
this.waypoints = ArrayList<NavigationPoint>(128)
} else {
this.waypoints = ArrayList<NavigationPoint>(128)
for (i in 1 until waypointBuilder.size) {
val source = waypointBuilder[i - 1]
val destination = waypointBuilder[i]
this.waypoints.addAll(NavigationPoint.from(source.parent, source.location, destination.parent, destination.location, walkSpeed))
if (destination.delay > 0)
this.waypoints.addAll(NavigationPoint.nop(this.waypoints[this.waypoints.size - 1], destination.delay.toInt() - 1))
}
this.waypoints.addAll(NavigationPoint.from(waypointBuilder[waypointBuilder.size - 1].parent, waypointBuilder[waypointBuilder.size - 1].location, waypointBuilder[0].parent, waypointBuilder[0].location, walkSpeed))
}
this.waypoints.addAll(NavigationPoint.from(waypointBuilder[waypointBuilder.size - 1].parent, waypointBuilder[waypointBuilder.size - 1].location, waypointBuilder[0].parent, waypointBuilder[0].location, walkSpeed))
}
override suspend fun onModeStart() {
@@ -39,13 +39,16 @@ import com.projectswg.holocore.resources.support.objects.permissions.AdminPermis
import com.projectswg.holocore.resources.support.objects.swg.creature.CreatureDifficulty
import com.projectswg.holocore.resources.support.objects.swg.custom.AIBehavior
import com.projectswg.holocore.resources.support.objects.swg.custom.AIObject
import java.util.concurrent.ThreadLocalRandom
import kotlin.math.cos
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sin
import kotlin.random.Random
class DynamicMovementObject(var location: Location, val name: String, val baseSpeed: Double = 0.0) {
var heading = ThreadLocalRandom.current().nextDouble() * 2 * Math.PI
private val random = Random(System.currentTimeMillis())
var heading = random.nextDouble() * 2 * Math.PI
private val groupMarker = ObjectCreator.createObjectFromTemplate("object/path_waypoint/shared_path_waypoint_droid.iff")
private val npcs = ArrayList<AIObject>()
private var lastUpdate = System.nanoTime()
@@ -70,7 +73,6 @@ class DynamicMovementObject(var location: Location, val name: String, val baseSp
val bossSpawner = Spawner(simpleSpawnInfo.withDifficulty(CreatureDifficulty.BOSS).build(), groupMarker)
val eliteSpawner = Spawner(simpleSpawnInfo.withDifficulty(CreatureDifficulty.ELITE).build(), groupMarker)
val normalSpawner = Spawner(simpleSpawnInfo.withDifficulty(CreatureDifficulty.NORMAL).build(), groupMarker)
val random = ThreadLocalRandom.current()
if (random.nextDouble() < 0.25)
npcs.add(NPCCreator.createSingleNpc(bossSpawner))
npcs.add(NPCCreator.createSingleNpc(eliteSpawner))
@@ -102,7 +104,7 @@ class DynamicMovementObject(var location: Location, val name: String, val baseSp
.setZ(location.z + radius * sin(angle))
newLocationBuilder.setY(ServerData.terrains.getHeight(newLocationBuilder))
val newLocation = newLocationBuilder.build()
val speed = it.worldLocation.distanceTo(newLocation) / elapsedTime
val speed = min(30.0, max(1.0, it.worldLocation.distanceTo(newLocation) / elapsedTime))
it.moveTo(null, newLocationBuilder.build(), speed)
}
}
@@ -114,7 +116,7 @@ class DynamicMovementObject(var location: Location, val name: String, val baseSp
return
}
val newHeading = heading + Math.PI * (1 + ThreadLocalRandom.current().nextDouble() - 0.5)
val newHeading = heading + Math.PI * (1 + random.nextDouble() - 0.5)
val secondProposed = calculateNextPosition(newHeading, distance)
if (isValidNextPosition(secondProposed)) {
location = secondProposed
@@ -123,7 +125,7 @@ class DynamicMovementObject(var location: Location, val name: String, val baseSp
}
// Brute Force Escape
val randomRotationFromNorth = ThreadLocalRandom.current().nextDouble() * Math.TAU
val randomRotationFromNorth = random.nextDouble() * Math.TAU
for (clockwiseRotation in 0..35) {
val bruteForceHeading = (clockwiseRotation * 10) * Math.PI / 180.0 + randomRotationFromNorth
val proposed = calculateNextPosition(bruteForceHeading, distance)
@@ -135,6 +137,7 @@ class DynamicMovementObject(var location: Location, val name: String, val baseSp
}
// TODO: destroy this object, we got stuck
location = firstProposed
assert(false)
}
@@ -796,17 +796,62 @@ public abstract class SWGObject extends BaselineObject implements Comparable<SWG
public Location getWorldLocation() {
return location.getWorldLocation(this);
}
public double distanceTo(@NotNull SWGObject obj) {
if (parent == obj.getParent())
return getLocation().distanceTo(obj.getLocation());
return getWorldLocation().distanceTo(obj.getWorldLocation());
SWGObject tmp = this;
double selfX = 0.0;
double selfY = 0.0;
double selfZ = 0.0;
while (tmp != null) {
Location loc = tmp.getLocation();
selfX += loc.getX();
selfY += loc.getY();
selfZ += loc.getZ();
tmp = tmp.getParent();
}
tmp = obj;
double otherX = 0.0;
double otherY = 0.0;
double otherZ = 0.0;
while (tmp != null) {
Location loc = tmp.getLocation();
otherX += loc.getX();
otherY += loc.getY();
otherZ += loc.getZ();
tmp = tmp.getParent();
}
selfX -= otherX;
selfY -= otherY;
selfZ -= otherZ;
return Math.sqrt(selfX * selfX + selfY * selfY + selfZ * selfZ);
}
public double flatDistanceTo(@NotNull SWGObject obj) {
if (parent == obj.getParent())
return getLocation().flatDistanceTo(obj.getLocation());
return getWorldLocation().flatDistanceTo(obj.getWorldLocation());
SWGObject tmp = this;
double selfX = 0.0;
double selfZ = 0.0;
while (tmp != null) {
Location loc = tmp.getLocation();
selfX += loc.getX();
selfZ += loc.getZ();
tmp = tmp.getParent();
}
tmp = obj;
double otherX = 0.0;
double otherZ = 0.0;
while (tmp != null) {
Location loc = tmp.getLocation();
otherX += loc.getX();
otherZ += loc.getZ();
tmp = tmp.getParent();
}
selfX -= otherX;
selfZ -= otherZ;
return Math.sqrt(selfX * selfX + selfZ * selfZ);
}
public double getX() {
@@ -0,0 +1,108 @@
/***********************************************************************************
* Copyright (c) 2025 /// Project SWG /// www.projectswg.com *
* *
* ProjectSWG is an emulation project for Star Wars Galaxies founded on *
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
* Our goal is to create one or more emulators which will provide servers for *
* players to continue playing a game similar to the one they used to play. *
* *
* This file is part of Holocore. *
* *
* --------------------------------------------------------------------------------*
* *
* Holocore is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* Holocore is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
***********************************************************************************/
package com.projectswg.holocore.resources.support.data.server_info.loader
import com.projectswg.common.data.location.Location
import com.projectswg.common.data.swgiff.parsers.SWGParser
import com.projectswg.holocore.resources.support.data.server_info.loader.npc.NpcPatrolRouteLoader
import com.projectswg.holocore.resources.support.npc.ai.NavigationPoint
import com.projectswg.holocore.resources.support.npc.spawn.Spawner
import com.projectswg.holocore.services.support.objects.ObjectStorageService
import com.projectswg.holocore.test.runners.TestRunnerNoIntents
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import java.util.concurrent.atomic.AtomicBoolean
class NpcPatrolRouteLoaderTest : TestRunnerNoIntents() {
@Test
fun `test patrol route waypoints`() {
fun checkRouteLeg(sourceWaypoint: Spawner.ResolvedPatrolWaypoint, destinationWaypoint: Spawner.ResolvedPatrolWaypoint) {
val route = NavigationPoint.from(sourceWaypoint.parent, sourceWaypoint.location, destinationWaypoint.parent, destinationWaypoint.location, 1.0)
assert(route.size < 500) { "distance between waypoints is too large (${route.size})" }
assert(sourceWaypoint.location.terrain == destinationWaypoint.location.terrain) { "terrain mismatch along route" }
}
val hasError = AtomicBoolean(false)
ServerData.npcPatrolRoutes.forEach { route ->
try {
val resolvedRoute = route.map { Spawner.ResolvedPatrolWaypoint(it) }
assert(route.isNotEmpty()) { "route is empty" }
for (i in 1 until route.size) {
checkRouteLeg(resolvedRoute[i - 1], resolvedRoute[i])
}
if (route[0].patrolType == NpcPatrolRouteLoader.PatrolType.LOOP) checkRouteLeg(resolvedRoute[route.size - 1], resolvedRoute[0])
} catch (e: AssertionError) {
System.err.println("Patrol group '${route[0].groupId}' error: ${e.message}")
hasError.set(true)
}
}
Assertions.assertFalse(hasError.get())
}
@Test
fun `test NPC to patrol route start`() {
val hasError = AtomicBoolean(false)
ServerData.npcStaticSpawns.spawns.parallelStream().forEach { spawn ->
if (spawn.patrolId.isEmpty() || spawn.patrolId == "0") return@forEach
val spawnerLocation = Location.builder().setTerrain(spawn.terrain).setX(spawn.x).setY(spawn.y).setZ(spawn.z).build()
val route = ServerData.npcPatrolRoutes[spawn.patrolId]
val routeLocation = Location.builder().setTerrain(route[0].terrain).setX(route[0].x).setY(route[0].y).setZ(route[0].z).build()
val distanceToRoute = spawnerLocation.distanceTo(routeLocation)
try {
assert(spawn.buildingId == route[0].buildingId) { "NPC not in same building as route" }
assert(spawn.cellId == route[0].cellId) { "NPC not in same cell as route" }
assert(distanceToRoute < 500) { "Spawner distance to route too large ($distanceToRoute)" }
assert(spawnerLocation.terrain == routeLocation.terrain) { "terrain mismatch along route" }
} catch (e: AssertionError) {
System.err.println("Patrol spawner '${spawn.npcId}' with route '${spawn.patrolId}' error: ${e.message}")
hasError.set(true)
}
}
Assertions.assertFalse(hasError.get())
}
companion object {
private var objectStorageService = ObjectStorageService()
@BeforeAll
@JvmStatic
fun setup() {
SWGParser.setBasePath("serverdata")
objectStorageService.initialize()
}
@AfterAll
@JvmStatic
fun tearDown() {
objectStorageService.terminate()
}
}
}