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
#### 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
- Added Kiss action
- Added being able to extinguish torches by sneezing
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "pony-town",
"version": "0.55.0",
"version": "0.55.2",
"description": "A game of ponies building a town",
"main": "pony-town.js",
"scripts": {
+1 -1
View File
@@ -39,7 +39,7 @@ const ICON_SIZE = 16;
const headX = -26;
const headY = -30;
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) {
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: 'Aviivix' },
{ 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) {
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.headTurnedOverride = hasFlag(game.player.state, EntityState.HeadTurned);
game.onActionsUpdate.next();
+10 -9
View File
@@ -20,7 +20,7 @@ export function createBodyFrame([
}
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> {
if (shadowOffsets && shadowOffsets.length !== frames.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 }));
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, [
@@ -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]),
[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])
]);
], undefined, 12);
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]),
@@ -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]),
[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])
]);
], undefined, 12);
export const kissFlyBody = createBodyAnimation('kiss-fly-body', 16, false, [
[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, 3, 0, 8, 10, 5, 4, 0, -16, 0, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -15]
]);
], undefined, 12);
export const kissFlyBugBody = createBodyAnimation('kiss-fly-bug-body', 24, false, [
[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, 5, 0, 8, 10, 5, 4, 0, -17, 0, 0, 0, 0, 0, -1],
[1, 1, 4, 0, 8, 10, 6, 5, 0, -17]
]);
], undefined, 12);
export const kissLieBody = createBodyAnimation('kiss-lie-body', 24, false, [
...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],
...repeat(2, [14, 1, 0, 2, 38, 38, 26, 26, 0, 0, -1]),
...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, [
...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(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(72, [0, 6])]);
], [...repeat(72, [0, 6])], 68);
export const kissSwimBody = createBodyAnimation('kiss-swim-body', 8, false, [
...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]),
[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]
]);
], undefined, 12);
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],
@@ -626,6 +626,7 @@ export function mergeAnimations(name: string, fps: number, loop: boolean, animat
name,
fps,
loop,
disableHeadTurnFrames: Math.min(...animations.map(a => a.disableHeadTurnFrames)),
frames: flatten(animations.map(a => a.frames)),
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(standing, kissingHoof, { exitAfter: 0 });
transition(kissingFlying, hovering, { enterTime: 1.1 / 10 });
transition(hovering, kissingFlying, { exitAfter: 0 });
transition(kissingLying, lying);
transition(lying, kissingLying, { exitAfter: 0 });
transition(kissingSitting, sitting);
transition(sitting, kissingSitting, { exitAfter: 0 });
transition(hovering, kissingFlying, { exitAfter: 0, onlyDirectTo: kissingFlying });
transition(kissing, kissingToTrotting, { exitAfter: 0, onlyDirectTo: trotting });
transition(kissingToTrotting, trotting);
transition(kissingToTrotting, standing);
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(lyingToTrotting, standing);
transition(kissingLying, lying);
transition(lying, kissingLying, { exitAfter: 0, onlyDirectTo: kissingLying });
transition(anyState, trotting, { 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>;
shutdownServer(value: boolean): Promise<void>;
getTimings(): Promise<any[]>;
setTimingEnabled(isEnabled: boolean): Promise<void>;
getWorldPerfStats(): Promise<any>;
teleportTo(adminAccountId: string, targetAccountId: string): Promise<void>;
}
@@ -820,6 +822,35 @@ export interface TimingEntry {
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 =
'accounts' | 'auths' | 'origins' | 'ponies' | 'accountAuths' | 'accountOrigins' | 'accountPonies';
@@ -919,5 +950,7 @@ export interface IAdminServerActions {
clearSessions(accountId: string): Promise<void>;
// other
getTimings(serverId: string): Promise<any[]>;
setTimingEnabled(serverId: string, isEnabled: boolean): Promise<void>;
getWorldPerfStats(serverId: string): Promise<any>;
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 { 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 {
return x < 0 || y < 0 || x >= map.width || y >= map.height;
}
+15
View File
@@ -1378,6 +1378,7 @@ export interface BodyAnimation {
fps: number;
frames: BodyAnimationFrame[];
shadow?: BodyShadow[];
disableHeadTurnFrames: number;
}
export interface HeadAnimationFrame {
@@ -1853,3 +1854,17 @@ export const enum UpdateFlags {
SwitchRegion = 2048,
// 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: '',
loop: false,
fps: 24,
disableHeadTurnFrames: 0,
frames: times(frames, i => ({
...createBodyFrame([]),
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")
fa-icon.mr-1([icon]="syncIcon" size="lg")
| 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")
| 10min
| 1day
button.btn.btn-sm.btn-default.ml-1((click)="removeEvents(1000 * 3)" title="Delete all events")
fa-icon.mr-1([icon]="trashIcon" size="lg")
| all
@@ -1,18 +1,22 @@
div(style="position: relative;")
div(style="position: absolute; top: -50px; right: 0;")
b.p-2 {{server}}
.btn-group(*ngIf="server")
.btn-group(*ngIf="loaded")
button.btn.btn-default((click)="resetZoom()")
| Reset zoom
button.btn.btn-default((click)="fitZoom()")
| Fit zoom
button.btn.btn-default((click)="fullFrameZoom()")
| 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
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)
button.btn.btn-default.dropdown-toggle(dropdownToggle)
| Load
| Select
.dropdown-menu.dropdown-menu-right(*dropdownMenu)
button.dropdown-item(*ngFor="let s of servers" (click)="load(s)")
| {{s}}
@@ -21,10 +25,87 @@ div(style="position: relative;")
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;")
p
.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
tr
th.text-right Self Time
@@ -1,6 +1,6 @@
import { Component, ViewChild, ElementRef } from '@angular/core';
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 { SERVER_FPS } from '../../../../common/constants';
import { AgDragEvent } from '../../../shared/directives/agDrag';
@@ -39,6 +39,7 @@ export class AdminReportsPerf {
endTime = 0;
listing: ListingEntry[] = [];
timings: TimingEntry[] = [];
worldPerfStats = defaultWorldPerfStats();
private tooltips: Tooltip[] = [];
private startTimeFrom = 0;
private endTimeFrom = 0;
@@ -53,10 +54,23 @@ export class AdminReportsPerf {
}
}, 100);
}
setInterval(this.update, frameTime, this);
}
get servers() {
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) {
this.server = server;
this.timings = [];
@@ -72,6 +86,17 @@ export class AdminReportsPerf {
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) {
const rect = this.container.nativeElement.getBoundingClientRect() as DOMRect;
const x = e.pageX - rect.left;
@@ -117,6 +142,10 @@ export class AdminReportsPerf {
}
}
resetZoom() {
if (!this.timings.length) {
return;
}
const firstTime = this.timings[0].time;
const lastTime = this.timings[this.timings.length - 1].time;
this.startTime = firstTime - timePadding;
@@ -125,6 +154,10 @@ export class AdminReportsPerf {
this.lastZoom = 0;
}
fitZoom() {
if (!this.timings.length) {
return;
}
const firstTime = this.timings[0].time;
const lastTime = this.timings[this.timings.length - 1].time;
const totalTime = lastTime - firstTime;
@@ -134,6 +167,10 @@ export class AdminReportsPerf {
this.lastZoom = 1;
}
fullFrameZoom() {
if (!this.timings.length) {
return;
}
const firstTime = this.timings[0].time;
const lastTime = firstTime + frameTime;
this.startTime = firstTime - 2;
@@ -245,6 +282,10 @@ export class AdminReportsPerf {
}
}
recalcListing() {
if (!this.timings.length) {
return;
}
interface Entry extends TimingEntry {
excludedTime: number;
}
+8
View File
@@ -519,6 +519,14 @@ export class AdminModel {
return this.server.getTimings(server)
.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) {
return this.server.teleportTo(accountId)
.catch(this.handleError);
@@ -685,6 +685,7 @@ function toBodyAnimation({ name, loop, fps, frames }: BodyAnimation, full: boole
loop,
fps,
shadow,
disableHeadTurnFrames: 0,
frames: flatMap(frames, f => repeat(full ? f.duration : 1, {
body: f.body,
head: f.head,
@@ -706,7 +707,7 @@ function toBodyAnimation({ name, loop, fps, frames }: BodyAnimation, full: boole
backLegY: switchFarClose ? f.backFarLegY : f.backLegY,
backFarLegX: switchFarClose ? f.backLegX : f.backFarLegX,
backFarLegY: switchFarClose ? f.backLegY : f.backFarLegY,
})),
}))
};
}
+10
View File
@@ -567,6 +567,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),
};
}
+15 -13
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);
}
}
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)) {
+37 -22
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) {
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;
}
}
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;
}
+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], []]],
['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]]],
['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]]],
['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]]],
@@ -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]]],
['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]]],
['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]]],
['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]]],
['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]]],
['earAccessoriesBehind', -1, 1, [null, null, null, null, null, null, null, null, [3], null, [3, 7, 7, 7], [3, 11, 11, 11], []]],
['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], [], []]],
['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], []]],
];
+12 -5
View File
@@ -114,6 +114,7 @@ describe('playerUtils', () => {
account,
character,
ip: '',
isMobile: false,
map,
isSwitchingMap: false,
pony,
@@ -132,7 +133,7 @@ describe('playerUtils', () => {
safeX: 10,
safeY: 20,
lastPacket: 123,
lastBoopAction: 0,
lastBoopOrKissAction: 0,
lastExpressionAction: 0,
lastX: 10,
lastY: 20,
@@ -591,7 +592,7 @@ describe('playerUtils', () => {
boop(client, 100);
expect(client.lastBoopOrKissAction).equal(100 + 500);
expect(client.lastBoopOrKissAction).equal(100 + 850);
});
it('executes boop on found entity', () => {
@@ -615,6 +616,7 @@ describe('playerUtils', () => {
boop(client, 0);
assert.notCalled(stubBoop);
expect(client.pony.region!.entityUpdates.length).eql(0);
});
it('does nothing if cannot perform action', () => {
@@ -622,7 +624,7 @@ describe('playerUtils', () => {
boop(client, 0);
expect(client.pony.region!.entityUpdates).eql([]);
expect(client.pony.region!.entityUpdates.length).eql(0);
});
it('does nothing if moving', () => {
@@ -630,7 +632,7 @@ describe('playerUtils', () => {
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', () => {
client.pony.state = EntityState.PonySitting;
client.lastBoopOrKissAction = 0;
client.lastExpressionAction = 0;
stand(client);
@@ -882,6 +886,8 @@ describe('playerUtils', () => {
});
it('sends given action', () => {
client.lastExpressionAction = 0;
client.lastBoopOrKissAction = 0;
expressionAction(client, Action.Yawn);
expect(client.pony.region!.entityUpdates).eql([
@@ -896,7 +902,7 @@ describe('playerUtils', () => {
});
it('does nothing if cannot perform action', () => {
client.lastExpressionAction = Date.now() + 1000;
client.lastExpressionAction = Date.now() + 2500;
expressionAction(client, Action.Yawn);
@@ -913,6 +919,7 @@ describe('playerUtils', () => {
it('updates last expression action', () => {
client.lastExpressionAction = 0;
client.lastBoopOrKissAction = 0;
expressionAction(client, Action.Yawn);