Reduced Upstream/Downstream Load (#1052)

* an attempt to space out the player upstream

* some data sterilizing

* when a player is respawning, other players do not send their own update PSM's

* always display players in their proper locations when nearing their view, even if they have been stationary this whole time

* fixing configuration file and warnings
This commit is contained in:
Fate-JH 2023-04-22 00:03:13 -04:00 committed by GitHub
parent 24ee12294a
commit a1cf6c2701
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 150 additions and 36 deletions

View file

@ -183,6 +183,41 @@ game {
}
}
# Limits the dispatch of PlayerStateMessage packets from a session to its client.
# Specifically, a packet will only be dispatched based on whether
# it is to be dispatched over a threshold time after the previous dispatch time.
# The delay between packets is determined by a distance between the observer player and the target player.
# Only affects PlayerStateMessage.
player-draw = {
# Minimum number of players within a given region before scaling down the range.
# Total population - population threshold = overpopulation
population-threshold = 20
# Number of players over threshold before the reduction in range is scaled down.
# Population step / overpopulation = range step factor (integer)
population-step = 5
# Always send packets regarding target players within the given distance.
range-min = 50
# Do not send packets regarding target players beyond this distance. (sq.m)
# Treat this as an absolute render distance.
range-max = 550
# Number of meters reduced from range based on population count over threshold value. (m)
# Range step * range step factor = total distance to remove from actual distance
range-step = 25
# The distances above min-range where different delays are allowed before a successful packet must be dispatched. [m]
# Test distances against entries to find the last one that is not more than the sample distance.
# Use the index of that sample distance from this sequence in the sequence `delays` below.
ranges = [150, 300, 400]
# The absolute time delay before a successful packet must be dispatched regardless of distance. (s)
delay-max = 1000
# The time delays for each distance range before a successful packet must be dispatched. [s]
# The index for an entry in this sequence is expected to be discovered using the `ranges` sequence above.
# Delays between packets may not be as precise as desired
# as the heartbeat of upstream packets are measured in quanta of 250ms usually.
# As a consequence, additional entries with proper time spacing will push back the next proper update considerably
# while additional entries with insufficient time spacing may result in no change in behavior.
delays = [350, 600, 800]
}
doors-can-be-opened-by-med-app-from-this-distance = 5.05
}

View file

@ -1063,6 +1063,9 @@ class AvatarActor(
} else {
performAvatarLogin(avatarId, account.id, replyTo)
}
case Success(_) =>
//TODO this may not be an actual failure, but don't know what to do
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(error = 6))
case Failure(e) =>
log.error(e)("db failure")
sessionActor ! SessionActor.SendResponse(ActionResultMessage.Fail(error = 6))

View file

@ -70,34 +70,84 @@ class SessionAvatarHandlers(
jumpThrust,
isCloaking,
spectating,
_
canSeeReallyFar
) if isNotSameTarget =>
val now = System.currentTimeMillis()
val (location, time, distanceSq): (Vector3, Long, Float) = if (spectating) {
val r2 = 2 + hidingPlayerRandomizer.nextInt(4000).toFloat
(Vector3(r2, r2, 1f), 0L, 0f)
} else {
val before = lastSeenStreamMessage(guid.guid).time
val dist = Vector3.DistanceSquared(player.Position, pos)
(pos, now - before, dist)
val pstateToSave = pstate.copy(timestamp = 0)
val (lastMsg, lastTime, lastPosition, wasSpectating, wasVisible) = lastSeenStreamMessage(guid.guid) match {
case SessionAvatarHandlers.LastUpstream(Some(msg), visible, time) => (Some(msg), time, msg.pos, msg.spectator, visible)
case _ => (None, 0L, Vector3.Zero, false, false)
}
if (distanceSq < 302500 || time > 5000) { // Render distance seems to be approx 525m. Reduce update rate at ~550m to be safe
val drawConfig = Config.app.game.playerDraw
val maxRange = drawConfig.rangeMax * drawConfig.rangeMax //sq.m
val ourPosition = player.Position
val currentDistance = Vector3.DistanceSquared(ourPosition, pos) //sq.m
val inVisibleRange = currentDistance <= maxRange
val wasInVisibleRange = Vector3.DistanceSquared(lastPosition, pos) <= maxRange
val comingIntoVisibleRange = inVisibleRange && !wasInVisibleRange
val now = System.currentTimeMillis() //ms
val durationSince = now - lastTime //ms
val released = player.isReleased
if (!released &&
!spectating &&
(comingIntoVisibleRange || (inVisibleRange && !lastMsg.contains(pstateToSave)))) {
lazy val targetDelay = {
val populationOver = math.max(
0,
continent.blockMap.sector(ourPosition, range=drawConfig.rangeMax.toFloat).livePlayerList.size - drawConfig.populationThreshold
)
val distanceAdjustment = math.pow(populationOver / drawConfig.populationStep * drawConfig.rangeStep, 2) //sq.m
val adjustedDistance = currentDistance + distanceAdjustment //sq.m
drawConfig.ranges.lastIndexWhere { dist => adjustedDistance > dist * dist } match {
case -1 => 1
case index => drawConfig.delays(index)
}
} //ms
if (comingIntoVisibleRange ||
canSeeReallyFar ||
currentDistance < drawConfig.rangeMin * drawConfig.rangeMin ||
durationSince > drawConfig.delayMax ||
sessionData.canSeeReallyFar ||
durationSince > targetDelay) {
//must draw
sendResponse(
PlayerStateMessage(
guid,
pos,
vel,
yaw,
pitch,
yawUpper,
timestamp = 0, //is this okay?
isCrouching,
isJumping,
jumpThrust,
isCloaking
)
)
lastSeenStreamMessage(guid.guid) = SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=true, now)
} else {
lastSeenStreamMessage(guid.guid) = SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, lastTime)
}
} else if (
(!spectating && wasInVisibleRange && !inVisibleRange) ||
(inVisibleRange && !wasSpectating && spectating) ||
(wasVisible && released)
) {
//must hide
val lat = (1 + hidingPlayerRandomizer.nextInt(continent.map.scale.height.toInt)).toFloat
sendResponse(
PlayerStateMessage(
guid,
location,
vel,
yaw,
pitch,
yawUpper,
timestamp = 0,
isCrouching,
isJumping,
jumpThrust,
isCloaking
Vector3(1f, lat, 1f),
vel=None,
facingYaw=0f,
facingPitch=0f,
facingYawUpper=0f,
timestamp=0, //is this okay?
is_cloaked = isCloaking
)
)
lastSeenStreamMessage(guid.guid) = SessionAvatarHandlers.LastUpstream(Some(pstate), now)
lastSeenStreamMessage(guid.guid) = SessionAvatarHandlers.LastUpstream(Some(pstateToSave), visible=false, now)
}
case AvatarResponse.ObjectHeld(slot, _)
@ -500,9 +550,9 @@ class SessionAvatarHandlers(
}
object SessionAvatarHandlers {
private[support] case class LastUpstream(msg: Option[AvatarResponse.PlayerState], time: Long)
private[support] case class LastUpstream(msg: Option[AvatarResponse.PlayerState], visible: Boolean, time: Long)
private[support] def blankUpstreamMessages(n: Int): Array[LastUpstream] = {
Array.fill[SessionAvatarHandlers.LastUpstream](n)(elem = LastUpstream(None, 0L))
Array.fill[SessionAvatarHandlers.LastUpstream](n)(elem = LastUpstream(None, visible=false, 0L))
}
}

View file

@ -2711,19 +2711,28 @@ class SessionData(
}
def canSeeReallyFar: Boolean = {
findEquipment().exists {
case weapon: Tool
if weapon.Size == EquipmentSize.Rifle &&
(weapon.Projectile ne GlobalDefinitions.no_projectile) &&
player.Crouching &&
player.avatar
.implants
.exists { p =>
p.collect { implant => implant.definition.implantType == ImplantType.RangeMagnifier && implant.initialized }.nonEmpty
} =>
true
case item =>
item.Definition == GlobalDefinitions.bolt_driver || item.Definition == GlobalDefinitions.heavy_sniper
shooting.FindContainedWeapon match {
case (Some(_: Vehicle), weapons) if weapons.nonEmpty =>
player.avatar
.implants
.exists { p =>
p.collect { implant => implant.definition.implantType == ImplantType.RangeMagnifier && implant.active }.nonEmpty
}
case (Some(_: Player), weapons) if weapons.nonEmpty =>
val wep = weapons.head
wep.Definition == GlobalDefinitions.bolt_driver ||
wep.Definition == GlobalDefinitions.heavy_sniper ||
(
(wep.Projectile ne GlobalDefinitions.no_projectile) &&
player.Crouching &&
player.avatar
.implants
.exists { p =>
p.collect { implant => implant.definition.implantType == ImplantType.RangeMagnifier && implant.active }.nonEmpty
}
)
case _ =>
false
}
}

View file

@ -290,6 +290,8 @@ class InterstellarClusterService(context: ActorContext[InterstellarClusterServic
cavernRotation.foreach {
rotation => rotation ! rotationMsg
}
case _ => ()
}
this
}

View file

@ -158,6 +158,7 @@ case class GameConfig(
warpGates: WarpGateConfig,
cavernRotation: CavernRotationConfig,
savedMsg: SavedMessageEvents,
playerDraw: PlayerStateDrawSettings,
doorsCanBeOpenedByMedAppFromThisDistance: Float
)
@ -220,3 +221,17 @@ case class SavedMessageTimings(
fixed: Long,
variable: Long
)
case class PlayerStateDrawSettings(
populationThreshold: Int,
populationStep: Int,
rangeMin: Int,
rangeMax: Int,
rangeStep: Int,
ranges: Seq[Int],
delayMax: Long,
delays: Seq[Long]
) {
assert(ranges.nonEmpty)
assert(ranges.size == delays.size)
}