Archive update v0.55.2

This commit is contained in:
Eliot Partridge
2019-10-04 10:23:00 -05:00
parent a9bbd552a3
commit 1971e2c962
25 changed files with 407 additions and 106 deletions
+7
View File
@@ -276,6 +276,13 @@ Tools are accessible at `<base_url>/tools/` (only available in dev mode or when
## Changelog ## Changelog
#### Pony.Town v0.55.2
- Optimized server performance
- Fixed various animation issues
- Fixed issues with kiss animation while turning head
- Added additional server performance stats to the admin panel
- Changed the button on admin panel that removes old events from 10min to 1day
### Pony.Town v0.55.0 ### Pony.Town v0.55.0
- Added Kiss action - Added Kiss action
- Added being able to extinguish torches by sneezing - Added being able to extinguish torches by sneezing
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "pony-town", "name": "pony-town",
"version": "0.55.0", "version": "0.55.2",
"description": "A game of ponies building a town", "description": "A game of ponies building a town",
"main": "pony-town.js", "main": "pony-town.js",
"scripts": { "scripts": {
+1 -1
View File
@@ -39,7 +39,7 @@ const ICON_SIZE = 16;
const headX = -26; const headX = -26;
const headY = -30; const headY = -30;
const headlessBoopFrame = { ...cloneDeep(boop.frames[7]), head: 0 }; const headlessBoopFrame = { ...cloneDeep(boop.frames[7]), head: 0 };
const headlessBoop: BodyAnimation = { name: '', loop: false, fps: 1, frames: [headlessBoopFrame] }; const headlessBoop: BodyAnimation = { name: '', loop: false, fps: 1, frames: [headlessBoopFrame], disableHeadTurnFrames: 0 };
function createPony(coatColor: string, wings = false, horn = false) { function createPony(coatColor: string, wings = false, horn = false) {
const info = createDefaultPony(); const info = createDefaultPony();
+1 -1
View File
@@ -77,7 +77,7 @@ export const CONTRIBUTORS: Contributors[] = [
{ name: 'Deeraw', links: ['https://www.deviantart.com/deerdraw', 'https://twitter.com/TheOnlyDeeraw'] }, { name: 'Deeraw', links: ['https://www.deviantart.com/deerdraw', 'https://twitter.com/TheOnlyDeeraw'] },
{ name: 'Aviivix' }, { name: 'Aviivix' },
{ name: 'SnowFl8keAnge1' }, { name: 'SnowFl8keAnge1' },
{ name: '3aHo3a', links: ['http://twitter.com/imnuclearimwild'] }, { name: '3aHo3a', links: ['https://twitter.com/imnuclearimwild'] },
], ],
}, },
]; ];
+15 -1
View File
@@ -139,7 +139,21 @@ export function boopAction(game: PonyTownGame) {
} }
export function turnHeadAction(game: PonyTownGame) { export function turnHeadAction(game: PonyTownGame) {
if (game.player && game.send(server => server.action(Action.TurnHead))) { if (!game.player) {
return;
}
if (game.player.animator.state) {
const disabledFrames = game.player.animator.state.animation.disableHeadTurnFrames;
if (disabledFrames > 0) {
const disabledTime = disabledFrames / game.player.animator.state.animation.fps;
if (disabledTime >= game.player.animator.time) {
return;
}
}
}
if (game.send(server => server.action(Action.TurnHead))) {
game.player.state = game.player.state ^ EntityState.HeadTurned; game.player.state = game.player.state ^ EntityState.HeadTurned;
game.headTurnedOverride = hasFlag(game.player.state, EntityState.HeadTurned); game.headTurnedOverride = hasFlag(game.player.state, EntityState.HeadTurned);
game.onActionsUpdate.next(); game.onActionsUpdate.next();
+10 -9
View File
@@ -20,7 +20,7 @@ export function createBodyFrame([
} }
export function createBodyAnimation( export function createBodyAnimation(
name: string, fps: number, loop: boolean, frames: number[][], shadowOffsets?: number[][] name: string, fps: number, loop: boolean, frames: number[][], shadowOffsets?: number[][], disableHeadTurnFrames?: number
): Readonly<BodyAnimation> { ): Readonly<BodyAnimation> {
if (shadowOffsets && shadowOffsets.length !== frames.length) { if (shadowOffsets && shadowOffsets.length !== frames.length) {
throw new Error(`Incorrect frame count for shadowOffsets for ${name}, animation frames ${frames.length}, shadow frames ${shadowOffsets.length}`); throw new Error(`Incorrect frame count for shadowOffsets for ${name}, animation frames ${frames.length}, shadow frames ${shadowOffsets.length}`);
@@ -28,7 +28,7 @@ export function createBodyAnimation(
const shadow = shadowOffsets && shadowOffsets.map<BodyShadow>(([frame, offset]) => ({ frame, offset })); const shadow = shadowOffsets && shadowOffsets.map<BodyShadow>(([frame, offset]) => ({ frame, offset }));
return { name, loop, fps, frames: frames.map(createBodyFrame), shadow }; return { name, loop, fps, frames: frames.map(createBodyFrame), shadow, disableHeadTurnFrames: disableHeadTurnFrames || 0 };
} }
export const stand = createBodyAnimation('stand', 24, true, [ export const stand = createBodyAnimation('stand', 24, true, [
@@ -468,7 +468,7 @@ export const kissBody = createBodyAnimation('kiss-body', 24, false, [
...repeat(2, [5, 1, 1, 0, 26, 26, 19, 19, -10, -6, 1, 0, 1, 1, 1, 1, 1, -1, 1, -1]), ...repeat(2, [5, 1, 1, 0, 26, 26, 19, 19, -10, -6, 1, 0, 1, 1, 1, 1, 1, -1, 1, -1]),
[5, 1, 1, 0, 28, 28, 18, 18, -9, -5, 1, 0, 1, 1, 1, 1, 1, -1, 1, -1], [5, 1, 1, 0, 28, 28, 18, 18, -9, -5, 1, 0, 1, 1, 1, 1, 1, -1, 1, -1],
...repeat(2, [2, 1, 1, 0, 1, 1, 1, 1, -1, 0, -1, 1, 1, 0, 1, 0, 1, 0, 1]) ...repeat(2, [2, 1, 1, 0, 1, 1, 1, 1, -1, 0, -1, 1, 1, 0, 1, 0, 1, 0, 1])
]); ], undefined, 12);
export const kissLiftHoofBody = createBodyAnimation('kiss-lift-hoof-body', 24, false, [ export const kissLiftHoofBody = createBodyAnimation('kiss-lift-hoof-body', 24, false, [
...repeat(3, [2, 1, 0, 0, 1, 1, 1, 1, -1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1]), ...repeat(3, [2, 1, 0, 0, 1, 1, 1, 1, -1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1]),
@@ -479,7 +479,7 @@ export const kissLiftHoofBody = createBodyAnimation('kiss-lift-hoof-body', 24, f
...repeat(2, [5, 1, 1, 0, 8, 26, 19, 19, -10, -6, 1, 0, 1, 2, 1, 1, 1, -1, 1, -1]), ...repeat(2, [5, 1, 1, 0, 8, 26, 19, 19, -10, -6, 1, 0, 1, 2, 1, 1, 1, -1, 1, -1]),
[5, 1, 1, 0, 28, 28, 18, 18, -9, -5, 1, 0, 1, 1, 1, 1, 1, -1, 1, -1], [5, 1, 1, 0, 28, 28, 18, 18, -9, -5, 1, 0, 1, 1, 1, 1, 1, -1, 1, -1],
...repeat(2, [2, 1, 1, 0, 1, 1, 1, 1, -1, 0, -1, 1, 1, 0, 1, 0, 1, 0, 1]) ...repeat(2, [2, 1, 1, 0, 1, 1, 1, 1, -1, 0, -1, 1, 1, 0, 1, 0, 1, 0, 1])
]); ], undefined, 12);
export const kissFlyBody = createBodyAnimation('kiss-fly-body', 16, false, [ export const kissFlyBody = createBodyAnimation('kiss-fly-body', 16, false, [
[1, 1, 3, 0, 8, 10, 6, 5, 0, -16], [1, 1, 3, 0, 8, 10, 6, 5, 0, -16],
@@ -514,7 +514,7 @@ export const kissFlyBody = createBodyAnimation('kiss-fly-body', 16, false, [
[1, 1, 12, 0, 9, 10, 4, 4, 0, -17, -1, 0, 0, 0, 0, -1], [1, 1, 12, 0, 9, 10, 4, 4, 0, -17, -1, 0, 0, 0, 0, -1],
[1, 1, 3, 0, 8, 10, 5, 4, 0, -16, 0, 0, 0, 0, 0, -1], [1, 1, 3, 0, 8, 10, 5, 4, 0, -16, 0, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -15] [1, 1, 4, 0, 8, 10, 6, 5, 0, -15]
]); ], undefined, 12);
export const kissFlyBugBody = createBodyAnimation('kiss-fly-bug-body', 24, false, [ export const kissFlyBugBody = createBodyAnimation('kiss-fly-bug-body', 24, false, [
[1, 1, 3, 0, 8, 10, 6, 5, 0, -16], [1, 1, 3, 0, 8, 10, 6, 5, 0, -16],
@@ -557,7 +557,7 @@ export const kissFlyBugBody = createBodyAnimation('kiss-fly-bug-body', 24, false
[1, 1, 4, 0, 9, 10, 4, 4, 0, -17, -1, 0, 0, 0, 0, -1], [1, 1, 4, 0, 9, 10, 4, 4, 0, -17, -1, 0, 0, 0, 0, -1],
[1, 1, 5, 0, 8, 10, 5, 4, 0, -17, 0, 0, 0, 0, 0, -1], [1, 1, 5, 0, 8, 10, 5, 4, 0, -17, 0, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -17] [1, 1, 4, 0, 8, 10, 6, 5, 0, -17]
]); ], undefined, 12);
export const kissLieBody = createBodyAnimation('kiss-lie-body', 24, false, [ export const kissLieBody = createBodyAnimation('kiss-lie-body', 24, false, [
...repeat(3, [14, 1, 0, 2, 38, 38, 26, 26]), ...repeat(3, [14, 1, 0, 2, 38, 38, 26, 26]),
@@ -569,7 +569,7 @@ export const kissLieBody = createBodyAnimation('kiss-lie-body', 24, false, [
[13, 1, 0, 2, 38, 38, 26, 26, 0, 0, -1], [13, 1, 0, 2, 38, 38, 26, 26, 0, 0, -1],
...repeat(2, [14, 1, 0, 2, 38, 38, 26, 26, 0, 0, -1]), ...repeat(2, [14, 1, 0, 2, 38, 38, 26, 26, 0, 0, -1]),
...repeat(2, [14, 1, 0, 2, 38, 38, 26, 26]) ...repeat(2, [14, 1, 0, 2, 38, 38, 26, 26])
], [...repeat(72, [3, 3])]); ], [...repeat(72, [3, 3])], 70);
export const kissSitBody = createBodyAnimation('kiss-sit-body', 24, false, [ export const kissSitBody = createBodyAnimation('kiss-sit-body', 24, false, [
...repeat(3, [8, 1, 2, 2, 34, 34, 25, 25]), ...repeat(3, [8, 1, 2, 2, 34, 34, 25, 25]),
@@ -579,7 +579,7 @@ export const kissSitBody = createBodyAnimation('kiss-sit-body', 24, false, [
...repeat(60, [9, 1, 2, 2, 39, 39, 26, 26, -1, -1, -2, 0, 0, 1, 0, 1, 1, 1, 1, 1]), ...repeat(60, [9, 1, 2, 2, 39, 39, 26, 26, -1, -1, -2, 0, 0, 1, 0, 1, 1, 1, 1, 1]),
...repeat(2, [9, 1, 2, 2, 34, 34, 26, 26, 0, -1, -2, 0, 0, 1, 0, 1, 0, 1, 0, 1]), ...repeat(2, [9, 1, 2, 2, 34, 34, 26, 26, 0, -1, -2, 0, 0, 1, 0, 1, 0, 1, 0, 1]),
...repeat(2, [9, 1, 2, 2, 34, 34, 26, 26, 0, 0, -1]) ...repeat(2, [9, 1, 2, 2, 34, 34, 26, 26, 0, 0, -1])
], [...repeat(72, [0, 6])]); ], [...repeat(72, [0, 6])], 68);
export const kissSwimBody = createBodyAnimation('kiss-swim-body', 8, false, [ export const kissSwimBody = createBodyAnimation('kiss-swim-body', 8, false, [
...repeat(2, [1, 1, 0, 0, 8, 10, 6, 5, 0, 14]), ...repeat(2, [1, 1, 0, 0, 8, 10, 6, 5, 0, 14]),
@@ -596,7 +596,7 @@ export const kissSwimBody = createBodyAnimation('kiss-swim-body', 8, false, [
...repeat(2, [1, 1, 0, 0, 9, 10, 4, 4, -1, 12, -1, 0, 0, 0, 0, -1]), ...repeat(2, [1, 1, 0, 0, 9, 10, 4, 4, -1, 12, -1, 0, 0, 0, 0, -1]),
[1, 1, 0, 0, 8, 10, 4, 4, 0, 13, -1, 0, 0, 0, 0, -1], [1, 1, 0, 0, 8, 10, 4, 4, 0, 13, -1, 0, 0, 0, 0, -1],
[1, 1, 0, 0, 8, 10, 5, 5, 0, 13] [1, 1, 0, 0, 8, 10, 5, 5, 0, 13]
]); ], undefined, 12);
export const kissToTrot = createBodyAnimation('kiss-to-trot-body', 24, false, [ export const kissToTrot = createBodyAnimation('kiss-to-trot-body', 24, false, [
[2, 1, 0, 0, 8, 4, 19, 4, -1, 0, -1, 1, 0, 0, 0, 0, 1, -1], [2, 1, 0, 0, 8, 4, 19, 4, -1, 0, -1, 1, 0, 0, 0, 0, 1, -1],
@@ -626,6 +626,7 @@ export function mergeAnimations(name: string, fps: number, loop: boolean, animat
name, name,
fps, fps,
loop, loop,
disableHeadTurnFrames: Math.min(...animations.map(a => a.disableHeadTurnFrames)),
frames: flatten(animations.map(a => a.frames)), frames: flatten(animations.map(a => a.frames)),
shadow: flatten(animations.map(a => a.shadow || a.frames.map(() => ({ frame: 0, offset: 0 })))), shadow: flatten(animations.map(a => a.shadow || a.frames.map(() => ({ frame: 0, offset: 0 })))),
}; };
+6 -7
View File
@@ -127,20 +127,19 @@ transition(standing, kissing, { exitAfter: 0 });
transition(kissingHoof, standing); transition(kissingHoof, standing);
transition(standing, kissingHoof, { exitAfter: 0 }); transition(standing, kissingHoof, { exitAfter: 0 });
transition(kissingFlying, hovering, { enterTime: 1.1 / 10 }); transition(kissingFlying, hovering, { enterTime: 1.1 / 10 });
transition(hovering, kissingFlying, { exitAfter: 0 }); transition(hovering, kissingFlying, { exitAfter: 0, onlyDirectTo: kissingFlying });
transition(kissingLying, lying);
transition(lying, kissingLying, { exitAfter: 0 });
transition(kissingSitting, sitting);
transition(sitting, kissingSitting, { exitAfter: 0 });
transition(kissing, kissingToTrotting, { exitAfter: 0, onlyDirectTo: trotting }); transition(kissing, kissingToTrotting, { exitAfter: 0, onlyDirectTo: trotting });
transition(kissingToTrotting, trotting); transition(kissingToTrotting, trotting);
transition(kissingToTrotting, standing);
transition(kissingSitting, sittingToTrotting, { exitAfter: 0, onlyDirectTo: trotting }); transition(kissingSitting, sittingToTrotting, { exitAfter: 0, onlyDirectTo: trotting });
transition(sittingToTrotting, standing); transition(kissingSitting, sitting);
transition(sitting, kissingSitting, { exitAfter: 0, onlyDirectTo: kissingSitting });
transition(kissingLying, lyingToTrotting, { exitAfter: 0, onlyDirectTo: trotting }); transition(kissingLying, lyingToTrotting, { exitAfter: 0, onlyDirectTo: trotting });
transition(lyingToTrotting, standing); transition(kissingLying, lying);
transition(lying, kissingLying, { exitAfter: 0, onlyDirectTo: kissingLying });
transition(anyState, trotting, { exitAfter: 0, keepTime: true }); transition(anyState, trotting, { exitAfter: 0, keepTime: true });
transition(anyState, flying, { exitAfter: 0, keepTime: true }); transition(anyState, flying, { exitAfter: 0, keepTime: true });
+33
View File
@@ -132,6 +132,8 @@ export interface InternalApi extends InternalCommonApi {
cancelUpdate(): Promise<void>; cancelUpdate(): Promise<void>;
shutdownServer(value: boolean): Promise<void>; shutdownServer(value: boolean): Promise<void>;
getTimings(): Promise<any[]>; getTimings(): Promise<any[]>;
setTimingEnabled(isEnabled: boolean): Promise<void>;
getWorldPerfStats(): Promise<any>;
teleportTo(adminAccountId: string, targetAccountId: string): Promise<void>; teleportTo(adminAccountId: string, targetAccountId: string): Promise<void>;
} }
@@ -820,6 +822,35 @@ export interface TimingEntry {
name?: string; name?: string;
} }
export interface WorldPerfStats {
lastUpdateTime: number;
maxUpdateTime: number;
minUpdateTime: number;
avgUpdateTime: number;
isSamplingEnabled: boolean;
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;
}
export function defaultWorldPerfStats() {
return { lastUpdateTime: 0, maxUpdateTime: 0, minUpdateTime: 0, avgUpdateTime: 0, isSamplingEnabled: false,
clients: 0, movingEntities: 0, regionsCount: 0, mapsCount: 0, clientQueue: 0, controllersCount: 0,
clientsWithAdds: 0, clientsWithUpdates: 0, clientsWithSays: 0, totalSays: 0, sent: 0, received: 0,
sentPackets: 0, receivedPackets: 0 };
}
export type ModelTypes = export type ModelTypes =
'accounts' | 'auths' | 'origins' | 'ponies' | 'accountAuths' | 'accountOrigins' | 'accountPonies'; 'accounts' | 'auths' | 'origins' | 'ponies' | 'accountAuths' | 'accountOrigins' | 'accountPonies';
@@ -919,5 +950,7 @@ export interface IAdminServerActions {
clearSessions(accountId: string): Promise<void>; clearSessions(accountId: string): Promise<void>;
// other // other
getTimings(serverId: string): Promise<any[]>; getTimings(serverId: string): Promise<any[]>;
setTimingEnabled(serverId: string, isEnabled: boolean): Promise<void>;
getWorldPerfStats(serverId: string): Promise<any>;
teleportTo(accountId: string): Promise<void>; teleportTo(accountId: string): Promise<void>;
} }
-10
View File
@@ -5,16 +5,6 @@ import { getRegionGlobal, isInWaterAt } from './worldMap';
import { tileWidth, tileHeight, PONY_TYPE, REGION_SIZE, REGION_WIDTH, REGION_HEIGHT } from './constants'; import { tileWidth, tileHeight, PONY_TYPE, REGION_SIZE, REGION_WIDTH, REGION_HEIGHT } from './constants';
import { isInTheAir, isFlying } from './entityUtils'; import { isInTheAir, isFlying } from './entityUtils';
let isCollidingCount = 0;
let isCollidingObjectCount = 0;
export function getCollisionStats() {
const stats = { isCollidingCount, isCollidingObjectCount };
isCollidingCount = 0;
isCollidingObjectCount = 0;
return stats;
}
export function isOutsideMap<T>(x: number, y: number, map: IMap<T>): boolean { export function isOutsideMap<T>(x: number, y: number, map: IMap<T>): boolean {
return x < 0 || y < 0 || x >= map.width || y >= map.height; return x < 0 || y < 0 || x >= map.width || y >= map.height;
} }
+15
View File
@@ -1378,6 +1378,7 @@ export interface BodyAnimation {
fps: number; fps: number;
frames: BodyAnimationFrame[]; frames: BodyAnimationFrame[];
shadow?: BodyShadow[]; shadow?: BodyShadow[];
disableHeadTurnFrames: number;
} }
export interface HeadAnimationFrame { export interface HeadAnimationFrame {
@@ -1853,3 +1854,17 @@ export const enum UpdateFlags {
SwitchRegion = 2048, SwitchRegion = 2048,
// max 32768 // max 32768
} }
export let counterNow: () => number;
if (typeof window !== 'undefined') {
counterNow = performance.now;
} else {
const hrtime = process.hrtime;
const getNanoSeconds = () => {
const hr = hrtime();
return hr[0] * 1e9 + hr[1];
};
const nodeLoadTime = getNanoSeconds() - process.uptime() * 1e9;
counterNow = () => (getNanoSeconds() - nodeLoadTime) / 1e6;
}
+1
View File
@@ -839,6 +839,7 @@ function state(
name: '', name: '',
loop: false, loop: false,
fps: 24, fps: 24,
disableHeadTurnFrames: 0,
frames: times(frames, i => ({ frames: times(frames, i => ({
...createBodyFrame([]), ...createBodyFrame([]),
head: (head || ones)[i], head: (head || ones)[i],
@@ -12,9 +12,9 @@ ng-template(#duplicatesPopover)
button.btn.btn-sm.btn-default.ml-1((click)="cleanupDeleted()" title="Cleanup deleted events") button.btn.btn-sm.btn-default.ml-1((click)="cleanupDeleted()" title="Cleanup deleted events")
fa-icon.mr-1([icon]="syncIcon" size="lg") fa-icon.mr-1([icon]="syncIcon" size="lg")
| clear | clear
button.btn.btn-sm.btn-default.ml-1((click)="removeEvents(1000 * 60 * 10)" title="Delete all older than 10 minutes") button.btn.btn-sm.btn-default.ml-1((click)="removeEvents(1000 * 60 * 60 * 24)" title="Delete all older than 1 day")
fa-icon.mr-1([icon]="clockIcon" size="lg") fa-icon.mr-1([icon]="clockIcon" size="lg")
| 10min | 1day
button.btn.btn-sm.btn-default.ml-1((click)="removeEvents(1000 * 3)" title="Delete all events") button.btn.btn-sm.btn-default.ml-1((click)="removeEvents(1000 * 3)" title="Delete all events")
fa-icon.mr-1([icon]="trashIcon" size="lg") fa-icon.mr-1([icon]="trashIcon" size="lg")
| all | all
@@ -1,18 +1,22 @@
div(style="position: relative;") div(style="position: relative;")
div(style="position: absolute; top: -50px; right: 0;") div(style="position: absolute; top: -50px; right: 0;")
b.p-2 {{server}} b.p-2 {{server}}
.btn-group(*ngIf="server") .btn-group(*ngIf="loaded")
button.btn.btn-default((click)="resetZoom()") button.btn.btn-default((click)="resetZoom()")
| Reset zoom | Reset zoom
button.btn.btn-default((click)="fitZoom()") button.btn.btn-default((click)="fitZoom()")
| Fit zoom | Fit zoom
button.btn.btn-default((click)="fullFrameZoom()") button.btn.btn-default((click)="fullFrameZoom()")
| Full frame | Full frame
button.btn.btn-default.ml-1(*ngIf="server" (click)="load(server)") button.btn.btn-default.ml-1(*ngIf="loaded" (click)="load(server)")
| Refresh | Refresh
button.btn.btn-default.ml-1(*ngIf="loaded" (click)="disable()")
| Disable
button.btn.btn-default.ml-1(*ngIf="!loaded && server" (click)="enable()")
| Enable
.btn-group.dropdown.ml-1(dropdown) .btn-group.dropdown.ml-1(dropdown)
button.btn.btn-default.dropdown-toggle(dropdownToggle) button.btn.btn-default.dropdown-toggle(dropdownToggle)
| Load | Select
.dropdown-menu.dropdown-menu-right(*dropdownMenu) .dropdown-menu.dropdown-menu-right(*dropdownMenu)
button.dropdown-item(*ngFor="let s of servers" (click)="load(s)") button.dropdown-item(*ngFor="let s of servers" (click)="load(s)")
| {{s}} | {{s}}
@@ -21,10 +25,87 @@ div(style="position: relative;")
canvas(#canvas (mousemove)="mouseMove($event)" (wheel)="wheel($event)" (agDrag)="drag($event)") canvas(#canvas (mousemove)="mouseMove($event)" (wheel)="wheel($event)" (agDrag)="drag($event)")
div(#tooltip style="display: none; position: absolute; left: 0; top: 0; padding: 2px 6px; background: #111; border-radius: 2px; font-size: 12px; white-space: pre; pointer-events: none;") div(#tooltip style="display: none; position: absolute; left: 0; top: 0; padding: 2px 6px; background: #111; border-radius: 2px; font-size: 12px; white-space: pre; pointer-events: none;")
p
.float-right.text-muted .float-right.text-muted
div total samples: {{timings.length}} div samples: {{timings.length}}
table.table.table-sm(style="font-size: small; width: 1000px;") br
.float-right.text-muted
div sampling: {{worldPerfStats.isSamplingEnabled}}
br
.float-right.text-muted
div avg update: {{worldPerfStats.avgUpdateTime.toFixed(2)}}
br
.float-right.text-muted
div last update: {{worldPerfStats.lastUpdateTime.toFixed(2)}}
br
.float-right.text-muted
div max update: {{worldPerfStats.maxUpdateTime.toFixed(2)}}
br
.float-right.text-muted
div min update: {{worldPerfStats.minUpdateTime.toFixed(2)}}
br
.float-right.text-muted
div moving entities: {{worldPerfStats.movingEntities}}
br
.float-right.text-muted
div controllers: {{worldPerfStats.controllersCount}}
br
.float-right.text-muted
div regions: {{worldPerfStats.regionsCount}}
br
.float-right.text-muted
div maps: {{worldPerfStats.mapsCount}}
br
.float-right.text-muted
div clients: {{worldPerfStats.clients}}
br
.float-right.text-muted
div clients with adds: {{worldPerfStats.clientsWithAdds}}
br
.float-right.text-muted
div clients with updates: {{worldPerfStats.clientsWithUpdates}}
br
.float-right.text-muted
div clients with says: {{worldPerfStats.clientsWithSays}}
br
.float-right.text-muted
div total says: {{worldPerfStats.totalSays}}
br
.float-right.text-muted
div client queue: {{worldPerfStats.clientQueue}}
br
.float-right.text-muted
div client sent: {{(worldPerfStats.sent / 1024).toFixed(2) + 'kb'}}
br
.float-right.text-muted
div client received: {{(worldPerfStats.received / 1024).toFixed(2) + 'kb'}}
br
.float-right.text-muted
div client sent packets: {{worldPerfStats.sentPackets}}
br
.float-right.text-muted
div client received packets: {{worldPerfStats.receivedPackets}}
table.table.table-sm(style="position: relative; bottom : 410px; font-size: small; width: 1000px;")
thead thead
tr tr
th.text-right Self Time th.text-right Self Time
@@ -1,6 +1,6 @@
import { Component, ViewChild, ElementRef } from '@angular/core'; import { Component, ViewChild, ElementRef } from '@angular/core';
import { AdminModel } from '../../../services/adminModel'; import { AdminModel } from '../../../services/adminModel';
import { TimingEntry, TimingEntryType } from '../../../../common/adminInterfaces'; import { TimingEntry, TimingEntryType, WorldPerfStats, defaultWorldPerfStats } from '../../../../common/adminInterfaces';
import { findById, pointInRect, clamp } from '../../../../common/utils'; import { findById, pointInRect, clamp } from '../../../../common/utils';
import { SERVER_FPS } from '../../../../common/constants'; import { SERVER_FPS } from '../../../../common/constants';
import { AgDragEvent } from '../../../shared/directives/agDrag'; import { AgDragEvent } from '../../../shared/directives/agDrag';
@@ -39,6 +39,7 @@ export class AdminReportsPerf {
endTime = 0; endTime = 0;
listing: ListingEntry[] = []; listing: ListingEntry[] = [];
timings: TimingEntry[] = []; timings: TimingEntry[] = [];
worldPerfStats = defaultWorldPerfStats();
private tooltips: Tooltip[] = []; private tooltips: Tooltip[] = [];
private startTimeFrom = 0; private startTimeFrom = 0;
private endTimeFrom = 0; private endTimeFrom = 0;
@@ -53,10 +54,23 @@ export class AdminReportsPerf {
} }
}, 100); }, 100);
} }
setInterval(this.update, frameTime, this);
} }
get servers() { get servers() {
return this.model.state.gameServers.map(s => s.id); return this.model.state.gameServers.map(s => s.id);
} }
disable() {
this.loaded = false;
this.timings = [];
this.listing = [];
this.model.setTimingEnabled(this.server, false);
this.redraw();
}
async enable() {
this.loaded = true;
await this.load(this.server);
}
async load(server: string) { async load(server: string) {
this.server = server; this.server = server;
this.timings = []; this.timings = [];
@@ -72,6 +86,17 @@ export class AdminReportsPerf {
this.redraw(); this.redraw();
} }
async update(self: AdminReportsPerf) {
if (self.server) {
const result = await self.model.getWorldPerfStats(self.server);
if (result) {
self.worldPerfStats = result as WorldPerfStats;
}
}
else {
self.worldPerfStats = defaultWorldPerfStats();
}
}
mouseMove(e: MouseEvent) { mouseMove(e: MouseEvent) {
const rect = this.container.nativeElement.getBoundingClientRect() as DOMRect; const rect = this.container.nativeElement.getBoundingClientRect() as DOMRect;
const x = e.pageX - rect.left; const x = e.pageX - rect.left;
@@ -117,6 +142,10 @@ export class AdminReportsPerf {
} }
} }
resetZoom() { resetZoom() {
if (!this.timings.length) {
return;
}
const firstTime = this.timings[0].time; const firstTime = this.timings[0].time;
const lastTime = this.timings[this.timings.length - 1].time; const lastTime = this.timings[this.timings.length - 1].time;
this.startTime = firstTime - timePadding; this.startTime = firstTime - timePadding;
@@ -125,6 +154,10 @@ export class AdminReportsPerf {
this.lastZoom = 0; this.lastZoom = 0;
} }
fitZoom() { fitZoom() {
if (!this.timings.length) {
return;
}
const firstTime = this.timings[0].time; const firstTime = this.timings[0].time;
const lastTime = this.timings[this.timings.length - 1].time; const lastTime = this.timings[this.timings.length - 1].time;
const totalTime = lastTime - firstTime; const totalTime = lastTime - firstTime;
@@ -134,6 +167,10 @@ export class AdminReportsPerf {
this.lastZoom = 1; this.lastZoom = 1;
} }
fullFrameZoom() { fullFrameZoom() {
if (!this.timings.length) {
return;
}
const firstTime = this.timings[0].time; const firstTime = this.timings[0].time;
const lastTime = firstTime + frameTime; const lastTime = firstTime + frameTime;
this.startTime = firstTime - 2; this.startTime = firstTime - 2;
@@ -245,6 +282,10 @@ export class AdminReportsPerf {
} }
} }
recalcListing() { recalcListing() {
if (!this.timings.length) {
return;
}
interface Entry extends TimingEntry { interface Entry extends TimingEntry {
excludedTime: number; excludedTime: number;
} }
+8
View File
@@ -519,6 +519,14 @@ export class AdminModel {
return this.server.getTimings(server) return this.server.getTimings(server)
.catch(this.handleError); .catch(this.handleError);
} }
setTimingEnabled(server: string, isEnabled: boolean) {
return this.server.setTimingEnabled(server, isEnabled)
.catch(this.handleError);
}
getWorldPerfStats(server: string) {
return this.server.getWorldPerfStats(server)
.catch(this.handleError);
}
teleportTo(accountId: string) { teleportTo(accountId: string) {
return this.server.teleportTo(accountId) return this.server.teleportTo(accountId)
.catch(this.handleError); .catch(this.handleError);
@@ -685,6 +685,7 @@ function toBodyAnimation({ name, loop, fps, frames }: BodyAnimation, full: boole
loop, loop,
fps, fps,
shadow, shadow,
disableHeadTurnFrames: 0,
frames: flatMap(frames, f => repeat(full ? f.duration : 1, { frames: flatMap(frames, f => repeat(full ? f.duration : 1, {
body: f.body, body: f.body,
head: f.head, head: f.head,
@@ -706,7 +707,7 @@ function toBodyAnimation({ name, loop, fps, frames }: BodyAnimation, full: boole
backLegY: switchFarClose ? f.backFarLegY : f.backLegY, backLegY: switchFarClose ? f.backFarLegY : f.backLegY,
backFarLegX: switchFarClose ? f.backLegX : f.backFarLegX, backFarLegX: switchFarClose ? f.backLegX : f.backFarLegX,
backFarLegY: switchFarClose ? f.backLegY : f.backFarLegY, backFarLegY: switchFarClose ? f.backLegY : f.backFarLegY,
})), }))
}; };
} }
+10
View File
@@ -567,6 +567,16 @@ export class AdminServerActions implements IAdminServerActions, SocketServer {
return server.api.getTimings(); return server.api.getTimings();
} }
@Method({ promise: true }) @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) { async teleportTo(accountId: string) {
const adminAccountId = this.account._id.toString(); const adminAccountId = this.account._id.toString();
await forAllGameServers(server => server.api.teleportTo(adminAccountId, accountId)); 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 { UserError } from '../userError';
import { liveSettings } from '../liveSettings'; import { liveSettings } from '../liveSettings';
import { formatDuration, invalidEnum } from '../../common/utils'; import { formatDuration, invalidEnum } from '../../common/utils';
import { timingEntries } from '../timing'; import { timingEntries, setTimingEnabled } from '../timing';
import { toPairs, groupBy } from 'lodash'; import { toPairs, groupBy } from 'lodash';
import { getSizeOfMap } from '../serverMap'; import { getSizeOfMap } from '../serverMap';
import { teleportTo } from '../playerUtils'; import { teleportTo } from '../playerUtils';
import { getWorldPerfStats } from '../worldPerfStats';
export const createAccountChanged = export const createAccountChanged =
(world: World, tokens: TokenService, findAccount: FindAccountSafe) => (world: World, tokens: TokenService, findAccount: FindAccountSafe) =>
@@ -269,6 +270,8 @@ export function createInternalApi(
shutdownServer: createShutdownServer(world, live), shutdownServer: createShutdownServer(world, live),
accountHidden: createHiddenStats(hiding), accountHidden: createHiddenStats(hiding),
getTimings: async () => timingEntries(), getTimings: async () => timingEntries(),
setTimingEnabled: async (isEnabled: boolean) => setTimingEnabled(isEnabled),
getWorldPerfStats: async () => getWorldPerfStats(),
teleportTo: createTeleportTo(world), teleportTo: createTeleportTo(world),
}; };
} }
+15 -13
View File
@@ -31,7 +31,8 @@ import {
import { replaceEmojis } from '../client/emoji'; import { replaceEmojis } from '../client/emoji';
import { expression, parseExpression } from '../common/expressionUtils'; import { expression, parseExpression } from '../common/expressionUtils';
import { 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'; } from '../common/entityUtils';
import { withBorder } from '../common/rect'; import { withBorder } from '../common/rect';
import { isOnlineFriend } from './services/friends'; import { isOnlineFriend } from './services/friends';
@@ -407,8 +408,10 @@ export function useHeldItem(client: IClient) {
} }
} }
export function canPerformAction(client: IClient) { export function canPerformAction(client: IClient, now?: number) {
const now = Date.now(); if (!now) {
now = Date.now();
}
return client.lastExpressionAction < now && client.lastBoopOrKissAction < now; return client.lastExpressionAction < now && client.lastBoopOrKissAction < now;
} }
@@ -420,10 +423,8 @@ export function updateEntityPlayerState(client: IClient, entity: ServerEntity) {
// actions // actions
export function turnHead(client: IClient) { 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); const purpleGrapeTypes = entities.grapesPurple.map(x => x.type);
const greenGrapeTypes = entities.grapesGreen.map(x => x.type); const greenGrapeTypes = entities.grapesGreen.map(x => x.type);
@@ -471,7 +472,7 @@ function boopEntity(client: IClient, rect: Rect, isOnlyBooping: boolean) {
} }
export function boop(client: IClient, now: number) { export function boop(client: IClient, now: number) {
if (canPerformAction(client) && canBoopOrKiss(client.pony)) { if (canPerformAction(client, now) && canBoopOrKiss(client.pony)) {
cancelEntityExpression(client.pony); cancelEntityExpression(client.pony);
sendAction(client.pony, Action.Boop); sendAction(client.pony, Action.Boop);
boopEntity(client, getBoopRect(client.pony), false); boopEntity(client, getBoopRect(client.pony), false);
@@ -480,20 +481,21 @@ export function boop(client: IClient, now: number) {
} }
export function kiss(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); cancelEntityExpression(client.pony);
sendAction(client.pony, Action.Kiss); sendAction(client.pony, Action.Kiss);
boopEntity(client, getkissRect(client.pony), false); boopEntity(client, getkissRect(client.pony), false);
client.lastBoopOrKissAction = now + 3350; client.lastBoopOrKissAction = now + 3400;
} }
} }
export function sneeze(client: IClient) { export function sneeze(client: IClient) {
if (canPerformAction(client)) { const now = Date.now();
if (canPerformAction(client, now)) {
cancelEntityExpression(client.pony); cancelEntityExpression(client.pony);
sendAction(client.pony, Action.Sneeze); sendAction(client.pony, Action.Sneeze);
boopEntity(client, getSneezeRect(client.pony), true); 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) { 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); cancelEntityExpression(client.pony);
sendAction(client.pony, action); 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 newIndex = reverse ? (index === -1 ? tools.length - 1 : index - 1) : ((index + 1) % tools.length);
const tool = tools[newIndex]; const tool = tools[newIndex];
holdItem(client.pony, tool.type); holdItem(client.pony, tool.type);
console.log('isMobile ' + client.isMobile);
const text = (client.isMobile && tool.textMobile) ? tool.textMobile : tool.text; const text = (client.isMobile && tool.textMobile) ? tool.textMobile : tool.text;
saySystem(client, text); saySystem(client, text);
} }
+1
View File
@@ -320,6 +320,7 @@ export class ServerActions implements IServerActions, SocketServer {
let totalEditableEntities = 0; let totalEditableEntities = 0;
// TODO: optimize
for (const region of this.map.regions) { for (const region of this.map.regions) {
for (const entity of region.entities) { for (const entity of region.entities) {
if (hasFlag(entity.state, EntityState.Editable)) { if (hasFlag(entity.state, EntityState.Editable)) {
+37 -22
View File
@@ -1,37 +1,41 @@
import { TimingEntry, TimingEntryType } from '../common/adminInterfaces'; import { TimingEntry, TimingEntryType } from '../common/adminInterfaces';
import { MINUTE } from '../common/constants';
import { counterNow } from '../common/interfaces';
const ENABLED = true; const ENTRIES_LIMIT = 20000;
const ENTRIES_LIMIT = 50000;
const entries: TimingEntry[] = []; let entries: TimingEntry[] = [];
let entriesCount = 0; let entriesCount = 0;
let isProfilingEnabled = false;
let lastFetchTime = Date.now();
let now: () => number; export function getTimingEnabled() {
return isProfilingEnabled;
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;
} }
if (ENABLED) { export function setTimingEnabled(isEnabled: boolean) {
if (isProfilingEnabled === isEnabled) {
return;
}
isProfilingEnabled = isEnabled;
if (isEnabled) {
for (let i = 0; i < ENTRIES_LIMIT; i++) { for (let i = 0; i < ENTRIES_LIMIT; i++) {
entries.push({ type: 0, time: 0, name: undefined }); entries.push({ type: 0, time: 0, name: undefined });
} }
} }
else {
entries = [];
}
}
export function timingStart(name: string) { export function timingStart(name: string) {
if (ENABLED) { if (isProfilingEnabled) {
if (entriesCount < ENTRIES_LIMIT) { if (entriesCount < ENTRIES_LIMIT) {
const entry = entries[entriesCount]; const entry = entries[entriesCount];
entry.type = TimingEntryType.Start; entry.type = TimingEntryType.Start;
entry.time = now(); entry.time = counterNow();
entry.name = name; entry.name = name;
entriesCount++; entriesCount++;
} else { } else {
@@ -41,11 +45,11 @@ export function timingStart(name: string) {
} }
export function timingEnd() { export function timingEnd() {
if (ENABLED) { if (isProfilingEnabled) {
if (entriesCount < ENTRIES_LIMIT) { if (entriesCount < ENTRIES_LIMIT) {
const entry = entries[entriesCount]; const entry = entries[entriesCount];
entry.type = TimingEntryType.End; entry.type = TimingEntryType.End;
entry.time = now(); entry.time = counterNow();
entry.name = undefined; entry.name = undefined;
entriesCount++; entriesCount++;
} else { } else {
@@ -55,11 +59,22 @@ export function timingEnd() {
} }
export function timingReset() { export function timingReset() {
if (ENABLED) {
entriesCount = 0; entriesCount = 0;
} }
}
export function timingEntries() { export function timingEntries() {
lastFetchTime = Date.now();
if (!isProfilingEnabled) {
setTimingEnabled(true);
return [];
}
return entries.slice(0, entriesCount); 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 { getWriterBuffer } from 'ag-sockets';
import { remove, compact } from 'lodash'; import { remove, compact } from 'lodash';
import { import {
TileType, WorldState, Season, Holiday, WorldStateFlags, LeaveReason, NotificationFlags, UpdateFlags, Action, TileType, WorldState, Season, Holiday, WorldStateFlags, LeaveReason, NotificationFlags, UpdateFlags, Action, counterNow,
} from '../common/interfaces'; } from '../common/interfaces';
import { removeItem, distance, clamp, randomPoint, includes, fromNow } from '../common/utils'; import { removeItem, distance, clamp, randomPoint, includes, fromNow } from '../common/utils';
import { HOUR_LENGTH, DAY_LENGTH } from '../common/timeUtils'; import { HOUR_LENGTH, DAY_LENGTH } from '../common/timeUtils';
@@ -31,12 +31,12 @@ import {
import { roundPosition, roundPositionXMidPixel, roundPositionYMidPixel } from '../common/positionUtils'; import { roundPosition, roundPositionXMidPixel, roundPositionYMidPixel } from '../common/positionUtils';
import { logger } from './logger'; import { logger } from './logger';
import { updateCamera, centerCameraOn } from '../common/camera'; import { updateCamera, centerCameraOn } from '../common/camera';
import { timingStart, timingEnd } from './timing'; import { timingStart, timingEnd, timingUpdate } from './timing';
import { getRegionGlobal, getTile } from '../common/worldMap'; import { getRegionGlobal, getTile } from '../common/worldMap';
import { getEntityTypeName } from '../common/entities'; import { getEntityTypeName } from '../common/entities';
import { toFriendOnline, toFriendOffline, FriendsService } from './services/friends'; import { toFriendOnline, toFriendOffline, FriendsService } from './services/friends';
// import { Pool, createPool } from './pool'; // 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 { HidingService } from './services/hiding';
import { generateRegionCollider } from '../common/region'; import { generateRegionCollider } from '../common/region';
import { updateTileIndices } from '../client/tileUtils'; import { updateTileIndices } from '../client/tileUtils';
@@ -44,6 +44,7 @@ import { removeEntityFromRegion } from './serverRegion';
import { createIslandMap } from './maps/islandMap'; import { createIslandMap } from './maps/islandMap';
import { createHouseMap } from './maps/houseMap'; import { createHouseMap } from './maps/houseMap';
import { updateMainMapSeason } from './maps/mainMap'; import { updateMainMapSeason } from './maps/mainMap';
import { updateWorldPerfStats } from './worldPerfStats';
interface MapSwitch { interface MapSwitch {
map: ServerMap; map: ServerMap;
@@ -287,8 +288,16 @@ export class World {
} }
} }
update(delta: number, now: number) { update(delta: number, now: number) {
const statsStart = counterNow();
let movingEntities = 0;
let regionsCount = 0;
let mapsCount = 0;
let controllersCount = 0;
const started = Date.now(); const started = Date.now();
timingUpdate();
timingStart('world.update()'); timingStart('world.update()');
resetEncodeUpdate(); resetEncodeUpdate();
@@ -315,7 +324,10 @@ export class World {
timingStart('update positions'); timingStart('update positions');
for (const map of this.maps) { for (const map of this.maps) {
++mapsCount;
for (const region of map.regions) { for (const region of map.regions) {
++regionsCount;
// TODO: update only moving entities, separate list of movingEntities // TODO: update only moving entities, separate list of movingEntities
for (const entity of region.movables) { for (const entity of region.movables) {
// TODO: make sure timestamp is initialized if entity is moving // TODO: make sure timestamp is initialized if entity is moving
@@ -323,6 +335,7 @@ export class World {
if (delta > 0) { if (delta > 0) {
if (entity.vx !== 0 || entity.vy !== 0) { if (entity.vx !== 0 || entity.vy !== 0) {
++movingEntities;
updatePosition(entity, delta, map); updatePosition(entity, delta, map);
} }
@@ -349,6 +362,7 @@ export class World {
for (const map of this.maps) { for (const map of this.maps) {
for (const controller of map.controllers) { for (const controller of map.controllers) {
++controllersCount;
controller.update(deltaSeconds, nowSeconds); controller.update(deltaSeconds, nowSeconds);
} }
} }
@@ -408,9 +422,7 @@ export class World {
totalSays += saysQueue.length; totalSays += saysQueue.length;
setupTiming(client); setupTiming(client);
timingStart('client.update()');
client.update(unsubscribes, subscribes, updateBuffer, regionUpdates, saysQueue); client.update(unsubscribes, subscribes, updateBuffer, regionUpdates, saysQueue);
timingEnd();
clearTiming(client); clearTiming(client);
resetClientUpdates(client); resetClientUpdates(client);
@@ -426,17 +438,16 @@ export class World {
} }
timingEnd(); timingEnd();
const { isCollidingCount, isCollidingObjectCount } = getCollisionStats(); timingStart(`cleanupOfflineClients`);
timingStart(`adds [${clientsWithAdds}]\n` +
`updates [${clientsWithUpdates}]\n` +
`says [${totalSays} / ${clientsWithSays}]\n` +
`sockets [${this.socketStatsText()}]\n` +
`collisions [${isCollidingObjectCount} / ${isCollidingCount}]`);
this.cleanupOfflineClients(); this.cleanupOfflineClients();
timingEnd(); timingEnd();
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) { sparseUpdate(now: number) {
timingStart('world.sparseUpdate()'); timingStart('world.sparseUpdate()');
@@ -536,11 +547,6 @@ export class World {
timingEnd(); 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() { updatesStats() {
timingStart('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;
}
+4 -4
View File
@@ -20,7 +20,7 @@ const sets: [keyof typeof sprites, number, number, (number[] | null)[]][] = [
['hornsBehind', -1, 1, [null, null, null, null, [3, 5], [3, 7], [3, 9, 9], [3, 9, 13], [3, 9, 13], [3, 7], [3, 11, 5, 5], [3, 9, 5, 5], [3, 9, 5], [3, 7], []]], ['hornsBehind', -1, 1, [null, null, null, null, [3, 5], [3, 7], [3, 9, 9], [3, 9, 13], [3, 9, 13], [3, 7], [3, 11, 5, 5], [3, 9, 5, 5], [3, 9, 5], [3, 7], []]],
['ears', -1, 1, [[3, 7], [3, 9], [3, 9], [3, 5], [3, 7, 7], [3, 7, 9]]], ['ears', -1, 1, [[3, 7], [3, 9], [3, 9], [3, 5], [3, 7, 7], [3, 7, 9]]],
['earsFar', -1, 1, [[3, 7], [3, 9], [3, 9], [3, 5], [3, 7, 7], [3, 7, 9]]], ['earsFar', -1, 1, [[3, 7], [3, 9], [3, 9], [3, 5], [3, 7, 7], [3, 7, 9]]],
['frontLegHooves', 1, 39, [null, [3], [3], [5], [5], [5], [3]]], ['frontLegHooves', 1, 40, [null, [3], [3], [5], [5], [5], [3]]],
['backLegHooves', 1, 27, [null, [3], [3], [5], [3]]], ['backLegHooves', 1, 27, [null, [3], [3], [5], [3]]],
['wings', 1, 13, [null, [3, 5, 9], [5], [3, 5, 9], [3]]], ['wings', 1, 13, [null, [3, 5, 9], [5], [3, 5, 9], [3]]],
['tails', 0, 3, [null, [3, 11, 13, 7, 5, 5], [3, 13, 9, 7, 5, 5], [3, 11, 9, 7, 5, 5], [3, 13, 11, 7, 5, 5], [3, 9, 13, 7, 5, 5], [3, 11, 13, 7, 5, 5, 5], [5, 13, 11, 7, 7], [3, 11, 13, 5, 7, 5], [3, 11, 11, 7, 5, 5, 11], [3, 11, 13, 5], [3, 11, 11, 7, 5, 5], [3, 9, 9, 7, 5], [3, 5, 9, 5], [5, 11, 9, 7, 7], [5, 7], [3, 5, 7], [5, 11, 9, 7, 7], [7, 13, 11, 9, 9], [5, 7], [3, 11, 13, 7, 5, 5], [3, 9, 11, 7, 5, 5], [3, 11, 13, 7, 5, 5], [3, 9, 11, 7, 5, 5], [3, 13, 7], [3, 5, 13, 13, 11, 11], [3, 9, 13, 13, 13, 11], [3, 7, 5, 13, 5], [5, 13, 9, 9, 7, 7], [5, 13, 9, 9, 7, 7], [3, 5, 7]]], ['tails', 0, 3, [null, [3, 11, 13, 7, 5, 5], [3, 13, 9, 7, 5, 5], [3, 11, 9, 7, 5, 5], [3, 13, 11, 7, 5, 5], [3, 9, 13, 7, 5, 5], [3, 11, 13, 7, 5, 5, 5], [5, 13, 11, 7, 7], [3, 11, 13, 5, 7, 5], [3, 11, 11, 7, 5, 5, 11], [3, 11, 13, 5], [3, 11, 11, 7, 5, 5], [3, 9, 9, 7, 5], [3, 5, 9, 5], [5, 11, 9, 7, 7], [5, 7], [3, 5, 7], [5, 11, 9, 7, 7], [7, 13, 11, 9, 9], [5, 7], [3, 11, 13, 7, 5, 5], [3, 9, 11, 7, 5, 5], [3, 11, 13, 7, 5, 5], [3, 9, 11, 7, 5, 5], [3, 13, 7], [3, 5, 13, 13, 11, 11], [3, 9, 13, 13, 13, 11], [3, 7, 5, 13, 5], [5, 13, 9, 9, 7, 7], [5, 13, 9, 9, 7, 7], [3, 5, 7]]],
@@ -29,13 +29,13 @@ const sets: [keyof typeof sprites, number, number, (number[] | null)[]][] = [
['facialHairBehind', -1, 1, [null, [3], [3, 7], null, [3, 7, 9], [3, 5], [3, 7, 9], [3, 7, 17], [3, 17, 7], [3, 13, 7, 7], [3, 13, 7, 9], [3, 13, 7, 9], [3, 13, 7, 9], [5, 13, 9, 13], [5, 13, 9, 13], [3, 17, 7, 11]]], ['facialHairBehind', -1, 1, [null, [3], [3, 7], null, [3, 7, 9], [3, 5], [3, 7, 9], [3, 7, 17], [3, 17, 7], [3, 13, 7, 7], [3, 13, 7, 9], [3, 13, 7, 9], [3, 13, 7, 9], [5, 13, 9, 13], [5, 13, 9, 13], [3, 17, 7, 11]]],
['headAccessoriesBehind', -1, 1, [null, [3, 5], [3, 9, 9, 9], [3, 5, 7, 9, 7], [3, 5], [3, 7], [3, 7, 13], [3, 11, 11], [3, 11, 13], [3, 11, 7, 9, 9], [3, 9, 13], [5, 11, 13], [3, 7], [3, 9, 13], [3, 9], [3, 9], [3, 7], [3, 7], [3, 7, 5, 5], [3, 7, 11]]], ['headAccessoriesBehind', -1, 1, [null, [3, 5], [3, 9, 9, 9], [3, 5, 7, 9, 7], [3, 5], [3, 7], [3, 7, 13], [3, 11, 11], [3, 11, 13], [3, 11, 7, 9, 9], [3, 9, 13], [5, 11, 13], [3, 7], [3, 9, 13], [3, 9], [3, 9], [3, 7], [3, 7], [3, 7, 5, 5], [3, 7, 11]]],
['neckAccessories', 1, 16, [null, [3, 5], [3, 5, 9, 5], [3, 5, 7, 13, 7], [3, 5], [3, 7, 7, 5, 5], [3, 5, 7], [3, 5], [3], [3], [3, 5, 9, 5], [3, 5, 9], [3, 9], [3], [3]]], ['neckAccessories', 1, 16, [null, [3, 5], [3, 5, 9, 5], [3, 5, 7, 13, 7], [3, 5], [3, 7, 7, 5, 5], [3, 5, 7], [3, 5], [3], [3], [3, 5, 9, 5], [3, 5, 9], [3, 9], [3], [3]]],
['frontLegAccessories', 1, 39, [null, [3, 13, 13, 13, 13]]], ['frontLegAccessories', 1, 40, [null, [3, 13, 13, 13, 13]]],
['backLegAccessories', 1, 27, [null, [3, 13, 13, 13, 13]]], ['backLegAccessories', 1, 27, [null, [3, 13, 13, 13, 13]]],
['chestAccessories', 1, 16, [null, [3, 5, 9], [3, 7, 5, 5, 7, 5, 7, 5, 7, 11, 9, 5, 11], [5, 7, 11], [5, 7]]], ['chestAccessories', 1, 16, [null, [3, 5, 9], [3, 7, 5, 5, 7, 5, 7, 5, 7, 11, 9, 5, 11], [5, 7, 11], [5, 7]]],
['backAccessories', 1, 16, [null, [3], [3, 7, 13, 5], [3, 7, 13, 5], [3, 9, 13, 5], [3, 3]]], ['backAccessories', 1, 16, [null, [3], [3, 7, 13, 5], [3, 7, 13, 5], [3, 9, 13, 5], [3, 3]]],
['waistAccessories', 1, 17, [null, [9], [9], [9]]], ['waistAccessories', 1, 17, [null, [9], [9], [9]]],
['earAccessories', -1, 1, [null, [3], [3], [3], [3], [5], [3, 11], [3], [3], [3], [3, 7, 7, 7], [3, 11, 11, 11], [3]]], ['earAccessories', -1, 1, [null, [3], [3], [3], [3], [5], [3, 11], [3], [3], [3], [3, 7, 7, 7], [3, 11, 11, 11], [3], []]],
['earAccessoriesBehind', -1, 1, [null, null, null, null, null, null, null, null, [3], null, [3, 7, 7, 7], [3, 11, 11, 11], []]], ['earAccessoriesBehind', -1, 1, [null, null, null, null, null, null, null, null, [3], null, [3, 7, 7, 7], [3, 11, 11, 11], [], []]],
['extraAccessories', -1, 1, [[11], [5], [5], [7], [9], [13], [5], [11], [9], [11, 11], [9], [9, 9], [7], [9], null, null, [13], [13]]], ['extraAccessories', -1, 1, [[11], [5], [5], [7], [9], [13], [5], [11], [9], [11, 11], [9], [9, 9], [7], [9], null, null, [13], [13]]],
['extraAccessoriesBehind', -1, 1, [[11], [5], null, [7], [9], null, null, null, null, null, null, null, [7], [9], [5], [5], [13], []]], ['extraAccessoriesBehind', -1, 1, [[11], [5], null, [7], [9], null, null, null, null, null, null, null, [7], [9], [5], [5], [13], []]],
]; ];
+12 -5
View File
@@ -114,6 +114,7 @@ describe('playerUtils', () => {
account, account,
character, character,
ip: '', ip: '',
isMobile: false,
map, map,
isSwitchingMap: false, isSwitchingMap: false,
pony, pony,
@@ -132,7 +133,7 @@ describe('playerUtils', () => {
safeX: 10, safeX: 10,
safeY: 20, safeY: 20,
lastPacket: 123, lastPacket: 123,
lastBoopAction: 0, lastBoopOrKissAction: 0,
lastExpressionAction: 0, lastExpressionAction: 0,
lastX: 10, lastX: 10,
lastY: 20, lastY: 20,
@@ -591,7 +592,7 @@ describe('playerUtils', () => {
boop(client, 100); boop(client, 100);
expect(client.lastBoopOrKissAction).equal(100 + 500); expect(client.lastBoopOrKissAction).equal(100 + 850);
}); });
it('executes boop on found entity', () => { it('executes boop on found entity', () => {
@@ -615,6 +616,7 @@ describe('playerUtils', () => {
boop(client, 0); boop(client, 0);
assert.notCalled(stubBoop); assert.notCalled(stubBoop);
expect(client.pony.region!.entityUpdates.length).eql(0);
}); });
it('does nothing if cannot perform action', () => { it('does nothing if cannot perform action', () => {
@@ -622,7 +624,7 @@ describe('playerUtils', () => {
boop(client, 0); boop(client, 0);
expect(client.pony.region!.entityUpdates).eql([]); expect(client.pony.region!.entityUpdates.length).eql(0);
}); });
it('does nothing if moving', () => { it('does nothing if moving', () => {
@@ -630,7 +632,7 @@ describe('playerUtils', () => {
boop(client, 0); boop(client, 0);
expect(client.pony.region!.entityUpdates).eql([]); expect(client.pony.region!.entityUpdates.length).eql(0);
}); });
}); });
@@ -663,6 +665,8 @@ describe('playerUtils', () => {
it('updates entity flag to standing', () => { it('updates entity flag to standing', () => {
client.pony.state = EntityState.PonySitting; client.pony.state = EntityState.PonySitting;
client.lastBoopOrKissAction = 0;
client.lastExpressionAction = 0;
stand(client); stand(client);
@@ -882,6 +886,8 @@ describe('playerUtils', () => {
}); });
it('sends given action', () => { it('sends given action', () => {
client.lastExpressionAction = 0;
client.lastBoopOrKissAction = 0;
expressionAction(client, Action.Yawn); expressionAction(client, Action.Yawn);
expect(client.pony.region!.entityUpdates).eql([ expect(client.pony.region!.entityUpdates).eql([
@@ -896,7 +902,7 @@ describe('playerUtils', () => {
}); });
it('does nothing if cannot perform action', () => { it('does nothing if cannot perform action', () => {
client.lastExpressionAction = Date.now() + 1000; client.lastExpressionAction = Date.now() + 2500;
expressionAction(client, Action.Yawn); expressionAction(client, Action.Yawn);
@@ -913,6 +919,7 @@ describe('playerUtils', () => {
it('updates last expression action', () => { it('updates last expression action', () => {
client.lastExpressionAction = 0; client.lastExpressionAction = 0;
client.lastBoopOrKissAction = 0;
expressionAction(client, Action.Yawn); expressionAction(client, Action.Yawn);