Merge branch 'archive' into archive-update-v0.55.2

This commit is contained in:
Eliot Partridge
2019-10-04 10:31:47 -05:00
24 changed files with 406 additions and 105 deletions
+10
View File
@@ -547,6 +547,16 @@ export class AdminServerActions implements IAdminServerActions, SocketServer {
return server.api.getTimings();
}
@Method({ promise: true })
async setTimingEnabled(serverId: string, isEnabled: boolean) {
const server = getServer(serverId);
server.api.setTimingEnabled(isEnabled);
}
@Method({ promise: true })
async getWorldPerfStats(serverId: string) {
const server = getServer(serverId);
return server.api.getWorldPerfStats();
}
@Method({ promise: true })
async teleportTo(accountId: string) {
const adminAccountId = this.account._id.toString();
await forAllGameServers(server => server.api.teleportTo(adminAccountId, accountId));
+4 -1
View File
@@ -15,10 +15,11 @@ import { createReloadSettings } from './internal-common';
import { UserError } from '../userError';
import { liveSettings } from '../liveSettings';
import { formatDuration, invalidEnum } from '../../common/utils';
import { timingEntries } from '../timing';
import { timingEntries, setTimingEnabled } from '../timing';
import { toPairs, groupBy } from 'lodash';
import { getSizeOfMap } from '../serverMap';
import { teleportTo } from '../playerUtils';
import { getWorldPerfStats } from '../worldPerfStats';
export const createAccountChanged =
(world: World, tokens: TokenService, findAccount: FindAccountSafe) =>
@@ -269,6 +270,8 @@ export function createInternalApi(
shutdownServer: createShutdownServer(world, live),
accountHidden: createHiddenStats(hiding),
getTimings: async () => timingEntries(),
setTimingEnabled: async (isEnabled: boolean) => setTimingEnabled(isEnabled),
getWorldPerfStats: async () => getWorldPerfStats(),
teleportTo: createTeleportTo(world),
};
}
+16 -14
View File
@@ -31,7 +31,8 @@ import {
import { replaceEmojis } from '../client/emoji';
import { expression, parseExpression } from '../common/expressionUtils';
import {
canBoopOrKiss, isPonySitting, isPonyStanding, getBoopRect, canStand, isPonyFlying, setPonyState, canSit, canLie, getkissRect, getSneezeRect
canBoopOrKiss, isPonySitting, isPonyStanding, getBoopRect, canStand, isPonyFlying, setPonyState, canSit,
canLie, getkissRect, getSneezeRect
} from '../common/entityUtils';
import { withBorder } from '../common/rect';
import { isOnlineFriend } from './services/friends';
@@ -407,8 +408,10 @@ export function useHeldItem(client: IClient) {
}
}
export function canPerformAction(client: IClient) {
const now = Date.now();
export function canPerformAction(client: IClient, now?: number) {
if (!now) {
now = Date.now();
}
return client.lastExpressionAction < now && client.lastBoopOrKissAction < now;
}
@@ -420,9 +423,7 @@ export function updateEntityPlayerState(client: IClient, entity: ServerEntity) {
// actions
export function turnHead(client: IClient) {
if (canPerformAction(client)) {
updateEntityState(client.pony, client.pony.state ^ EntityState.HeadTurned);
}
updateEntityState(client.pony, client.pony.state ^ EntityState.HeadTurned);
}
const purpleGrapeTypes = entities.grapesPurple.map(x => x.type);
@@ -471,7 +472,7 @@ function boopEntity(client: IClient, rect: Rect, isOnlyBooping: boolean) {
}
export function boop(client: IClient, now: number) {
if (canPerformAction(client) && canBoopOrKiss(client.pony)) {
if (canPerformAction(client, now) && canBoopOrKiss(client.pony)) {
cancelEntityExpression(client.pony);
sendAction(client.pony, Action.Boop);
boopEntity(client, getBoopRect(client.pony), false);
@@ -480,20 +481,21 @@ export function boop(client: IClient, now: number) {
}
export function kiss(client: IClient, now: number) {
if (canPerformAction(client) && canBoopOrKiss(client.pony)) {
if (canPerformAction(client, now) && canBoopOrKiss(client.pony)) {
cancelEntityExpression(client.pony);
sendAction(client.pony, Action.Kiss);
boopEntity(client, getkissRect(client.pony), false);
client.lastBoopOrKissAction = now + 3350;
client.lastBoopOrKissAction = now + 3400;
}
}
export function sneeze(client: IClient) {
if (canPerformAction(client)) {
const now = Date.now();
if (canPerformAction(client, now)) {
cancelEntityExpression(client.pony);
sendAction(client.pony, Action.Sneeze);
boopEntity(client, getSneezeRect(client.pony), true);
client.lastExpressionAction = Date.now() + 750;
client.lastExpressionAction = now + 750;
}
}
@@ -557,10 +559,11 @@ export function fly(client: IClient) {
}
export function expressionAction(client: IClient, action: Action) {
if (canPerformAction(client) && isExpressionAction(action)) {
const now = Date.now();
if (canPerformAction(client, now) && isExpressionAction(action)) {
cancelEntityExpression(client.pony);
sendAction(client.pony, action);
client.lastExpressionAction = Date.now() + 750;
client.lastExpressionAction = now + 750;
}
}
@@ -836,7 +839,6 @@ export function switchTool(client: IClient, reverse: boolean) {
const newIndex = reverse ? (index === -1 ? tools.length - 1 : index - 1) : ((index + 1) % tools.length);
const tool = tools[newIndex];
holdItem(client.pony, tool.type);
// console.log('isMobile ' + client.isMobile);
const text = (client.isMobile && tool.textMobile) ? tool.textMobile : tool.text;
saySystem(client, text);
}
+1
View File
@@ -320,6 +320,7 @@ export class ServerActions implements IServerActions, SocketServer {
let totalEditableEntities = 0;
// TODO: optimize
for (const region of this.map.regions) {
for (const entity of region.entities) {
if (hasFlag(entity.state, EntityState.Editable)) {
+40 -25
View File
@@ -1,37 +1,41 @@
import { TimingEntry, TimingEntryType } from '../common/adminInterfaces';
import { MINUTE } from '../common/constants';
import { counterNow } from '../common/interfaces';
const ENABLED = true;
const ENTRIES_LIMIT = 50000;
const ENTRIES_LIMIT = 20000;
const entries: TimingEntry[] = [];
let entries: TimingEntry[] = [];
let entriesCount = 0;
let isProfilingEnabled = false;
let lastFetchTime = Date.now();
let now: () => number;
if (typeof window !== 'undefined') {
now = performance.now;
} else {
const hrtime = process.hrtime;
const getNanoSeconds = () => {
const hr = hrtime();
return hr[0] * 1e9 + hr[1];
};
const nodeLoadTime = getNanoSeconds() - process.uptime() * 1e9;
now = () => (getNanoSeconds() - nodeLoadTime) / 1e6;
export function getTimingEnabled() {
return isProfilingEnabled;
}
if (ENABLED) {
for (let i = 0; i < ENTRIES_LIMIT; i++) {
entries.push({ type: 0, time: 0, name: undefined });
export function setTimingEnabled(isEnabled: boolean) {
if (isProfilingEnabled === isEnabled) {
return;
}
isProfilingEnabled = isEnabled;
if (isEnabled) {
for (let i = 0; i < ENTRIES_LIMIT; i++) {
entries.push({ type: 0, time: 0, name: undefined });
}
}
else {
entries = [];
}
}
export function timingStart(name: string) {
if (ENABLED) {
if (isProfilingEnabled) {
if (entriesCount < ENTRIES_LIMIT) {
const entry = entries[entriesCount];
entry.type = TimingEntryType.Start;
entry.time = now();
entry.time = counterNow();
entry.name = name;
entriesCount++;
} else {
@@ -41,11 +45,11 @@ export function timingStart(name: string) {
}
export function timingEnd() {
if (ENABLED) {
if (isProfilingEnabled) {
if (entriesCount < ENTRIES_LIMIT) {
const entry = entries[entriesCount];
entry.type = TimingEntryType.End;
entry.time = now();
entry.time = counterNow();
entry.name = undefined;
entriesCount++;
} else {
@@ -55,11 +59,22 @@ export function timingEnd() {
}
export function timingReset() {
if (ENABLED) {
entriesCount = 0;
}
entriesCount = 0;
}
export function timingEntries() {
lastFetchTime = Date.now();
if (!isProfilingEnabled) {
setTimingEnabled(true);
return [];
}
return entries.slice(0, entriesCount);
}
export function timingUpdate() {
if (isProfilingEnabled) {
if (Date.now() - lastFetchTime > MINUTE) {
setTimingEnabled(false);
}
}
}
+23 -17
View File
@@ -1,7 +1,7 @@
import { getWriterBuffer } from 'ag-sockets';
import { remove, compact } from 'lodash';
import {
TileType, WorldState, Season, Holiday, WorldStateFlags, LeaveReason, NotificationFlags, UpdateFlags, Action,
TileType, WorldState, Season, Holiday, WorldStateFlags, LeaveReason, NotificationFlags, UpdateFlags, Action, counterNow,
} from '../common/interfaces';
import { removeItem, distance, clamp, randomPoint, includes, fromNow } from '../common/utils';
import { HOUR_LENGTH, DAY_LENGTH } from '../common/timeUtils';
@@ -31,12 +31,12 @@ import {
import { roundPosition, roundPositionXMidPixel, roundPositionYMidPixel } from '../common/positionUtils';
import { logger } from './logger';
import { updateCamera, centerCameraOn } from '../common/camera';
import { timingStart, timingEnd } from './timing';
import { timingStart, timingEnd, timingUpdate } from './timing';
import { getRegionGlobal, getTile } from '../common/worldMap';
import { getEntityTypeName } from '../common/entities';
import { toFriendOnline, toFriendOffline, FriendsService } from './services/friends';
// import { Pool, createPool } from './pool';
import { isStaticCollision, getCollisionStats, fixCollision, updatePosition } from '../common/collision';
import { isStaticCollision, fixCollision, updatePosition } from '../common/collision';
import { HidingService } from './services/hiding';
import { generateRegionCollider } from '../common/region';
import { updateTileIndices } from '../client/tileUtils';
@@ -44,6 +44,7 @@ import { removeEntityFromRegion } from './serverRegion';
import { createIslandMap } from './maps/islandMap';
import { createHouseMap } from './maps/houseMap';
import { updateMainMapSeason } from './maps/mainMap';
import { updateWorldPerfStats } from './worldPerfStats';
interface MapSwitch {
map: ServerMap;
@@ -287,8 +288,16 @@ export class World {
}
}
update(delta: number, now: number) {
const statsStart = counterNow();
let movingEntities = 0;
let regionsCount = 0;
let mapsCount = 0;
let controllersCount = 0;
const started = Date.now();
timingUpdate();
timingStart('world.update()');
resetEncodeUpdate();
@@ -315,7 +324,10 @@ export class World {
timingStart('update positions');
for (const map of this.maps) {
++mapsCount;
for (const region of map.regions) {
++regionsCount;
// TODO: update only moving entities, separate list of movingEntities
for (const entity of region.movables) {
// TODO: make sure timestamp is initialized if entity is moving
@@ -323,6 +335,7 @@ export class World {
if (delta > 0) {
if (entity.vx !== 0 || entity.vy !== 0) {
++movingEntities;
updatePosition(entity, delta, map);
}
@@ -349,6 +362,7 @@ export class World {
for (const map of this.maps) {
for (const controller of map.controllers) {
++controllersCount;
controller.update(deltaSeconds, nowSeconds);
}
}
@@ -408,9 +422,7 @@ export class World {
totalSays += saysQueue.length;
setupTiming(client);
timingStart('client.update()');
client.update(unsubscribes, subscribes, updateBuffer, regionUpdates, saysQueue);
timingEnd();
clearTiming(client);
resetClientUpdates(client);
@@ -426,17 +438,16 @@ export class World {
}
timingEnd();
const { isCollidingCount, isCollidingObjectCount } = getCollisionStats();
timingStart(`adds [${clientsWithAdds}]\n` +
`updates [${clientsWithUpdates}]\n` +
`says [${totalSays} / ${clientsWithSays}]\n` +
`sockets [${this.socketStatsText()}]\n` +
`collisions [${isCollidingObjectCount} / ${isCollidingCount}]`);
timingStart(`cleanupOfflineClients`);
this.cleanupOfflineClients();
timingEnd();
timingEnd();
const { sent, received, sentPackets, receivedPackets } = this.socketStats.stats();
updateWorldPerfStats(statsStart, this.clients.length, movingEntities, regionsCount, mapsCount, this.joinQueue.length,
controllersCount, clientsWithAdds, clientsWithUpdates, clientsWithSays, totalSays,
sent, received, sentPackets, receivedPackets);
}
sparseUpdate(now: number) {
timingStart('world.sparseUpdate()');
@@ -536,11 +547,6 @@ export class World {
timingEnd();
}
private socketStatsText() {
const { sent, received, sentPackets, receivedPackets } = this.socketStats.stats();
return `sent: ${(sent / 1024).toFixed(2)} kb (${sentPackets}), ` +
`recv: ${(received / 1024).toFixed(2)} kb (${receivedPackets})`;
}
updatesStats() {
timingStart('updatesStats()');
+66
View File
@@ -0,0 +1,66 @@
import { defaultWorldPerfStats } from '../common/adminInterfaces';
import { counterNow } from '../common/interfaces';
import { getTimingEnabled } from './timing';
const FRAME_HISTORY_SIZE = 25;
let stats = defaultWorldPerfStats();
const frameHistory: number[] = [];
frameHistory.length = FRAME_HISTORY_SIZE;
frameHistory.fill(-1);
let frameHistoryPtr = 0;
function addFrameToHistory(time: number) {
frameHistory[frameHistoryPtr++] = time;
if (frameHistoryPtr === FRAME_HISTORY_SIZE) {
frameHistoryPtr = 0;
}
}
export function updateWorldPerfStats(
frameStart: number, clients: number, movingEntities: number, regionsCount: number, mapsCount: number, clientQueue: number,
controllersCount: number, clientsWithAdds: number, clientsWithUpdates: number, clientsWithSays: number, totalSays: number,
sent: number, received: number, sentPackets: number, receivedPackets: number) {
const time = counterNow() - frameStart;
stats.lastUpdateTime = time;
addFrameToHistory(time);
stats.isSamplingEnabled = getTimingEnabled();
stats.clients = clients;
stats.movingEntities = movingEntities;
stats.regionsCount = regionsCount;
stats.mapsCount = mapsCount;
stats.clientQueue = clientQueue;
stats.controllersCount = controllersCount;
stats.clientsWithAdds = clientsWithAdds;
stats.clientsWithUpdates = clientsWithUpdates;
stats.clientsWithSays = clientsWithSays;
stats.totalSays = totalSays;
stats.sent = sent;
stats.received = received;
stats.sentPackets = sentPackets;
stats.receivedPackets = receivedPackets;
let avg = 0, nonZero = 0;
stats.maxUpdateTime = Number.MIN_VALUE;
stats.minUpdateTime = Number.MAX_VALUE;
for (const f of frameHistory) {
if (f < 0) {
continue;
}
if (f < stats.minUpdateTime) {
stats.minUpdateTime = f;
}
if (f > stats.maxUpdateTime) {
stats.maxUpdateTime = f;
}
avg += f;
++nonZero;
}
stats.avgUpdateTime = avg / nonZero;
}
export function getWorldPerfStats() {
return stats;
}