mirror of
https://github.com/Terncode/pixel.horse.git
synced 2026-09-25 06:05:52 +02:00
Merge branch 'archive' into archive-update-v0.55.2
This commit is contained in:
@@ -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));
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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()');
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user