1
0
Fork 0
mirror of https://github.com/demostf/demo.js synced 2026-06-04 00:54:14 +02:00

more splitting of analyser

This commit is contained in:
Robin Appelman 2017-09-23 18:39:00 +02:00
commit b11cd7eec0
9 changed files with 240 additions and 211 deletions

30
src/Analyser.ts Normal file
View file

@ -0,0 +1,30 @@
import {Parser} from './Parser';
import {Match} from './Data/Match';
import {EventEmitter} from 'events';
import {Header} from './Data/Header';
export class Analyser extends EventEmitter {
private parser: Parser;
private match: Match;
constructor(parser: Parser) {
super();
this.parser = parser;
}
public getHeader(): Header {
return this.parser.getHeader();
}
public getBody(): Match {
if (!this.match) {
this.match = new Match(this.parser.parserState);
for (const packet of this.parser.getPackets()) {
this.match.handlePacket(packet);
this.emit('packet', packet);
}
this.emit('done');
}
return this.match;
}
}

View file

@ -36,7 +36,11 @@ export class Match {
public teamEntityMap: Map<EntityId, Team> = new Map();
public buildings: Map<EntityId, Building> = new Map();
public playerResources: PlayerResource[] = [];
public readonly parserState: ParserState = new ParserState();
public readonly parserState: ParserState;
constructor(parserState: ParserState) {
this.parserState = parserState;
}
public getState() {
const users = {};

View file

@ -13,6 +13,7 @@ import {
} from '../PacketHandler/StringTable';
import {handleGameEventList} from '../PacketHandler/GameEventList';
import {DataTablesMessage, Message, MessageType, StringTablesMessage} from './Message';
import {handlePacketEntitiesForState} from '../PacketHandler/PacketEntities';
export class ParserState {
public version: number = 0;
@ -44,6 +45,9 @@ export class ParserState {
case 'gameEventList':
handleGameEventList(packet, this);
break;
case 'packetEntities':
handlePacketEntitiesForState(packet, this);
break;
}
}

View file

@ -1,6 +1,7 @@
import {BitStream} from 'bit-buffer';
import {Parser} from './Parser';
import {PacketTypeId} from './Data/Packet';
import {Analyser} from './Analyser';
export class Demo {
public static fromNodeBuffer(nodeBuffer) {
@ -31,4 +32,8 @@ export class Demo {
}
return this.parser;
}
public getAnalyser(fastMode: boolean = false) {
return new Analyser(this.getParser(fastMode));
}
}

View file

@ -7,24 +7,30 @@ import {SendProp} from '../Data/SendProp';
import {Vector} from '../Data/Vector';
import {CWeaponMedigun, Weapon} from '../Data/Weapon';
import {TeamNumber} from '../Data/Team';
import {ParserState} from '../Data/ParserState';
export function handlePacketEntities(packet: PacketEntitiesPacket, match: Match) {
for (const removedEntityId of packet.removedEntities) {
match.parserState.entityClasses.delete(removedEntityId);
}
for (const entity of packet.entities) {
saveEntity(entity, match);
handleEntity(entity, match);
}
}
function saveEntity(packetEntity: PacketEntity, match: Match) {
if (packetEntity.pvs === PVS.DELETE) {
match.parserState.entityClasses.delete(packetEntity.entityIndex);
export function handlePacketEntitiesForState(packet: PacketEntitiesPacket, state: ParserState) {
for (const removedEntityId of packet.removedEntities) {
state.entityClasses.delete(removedEntityId);
}
match.parserState.entityClasses.set(packetEntity.entityIndex, packetEntity.serverClass);
for (const entity of packet.entities) {
saveEntity(entity, state);
}
}
function saveEntity(packetEntity: PacketEntity, state: ParserState) {
if (packetEntity.pvs === PVS.DELETE) {
state.entityClasses.delete(packetEntity.entityIndex);
}
state.entityClasses.set(packetEntity.entityIndex, packetEntity.serverClass);
}
function handleEntity(entity: PacketEntity, match: Match) {

View file

@ -1,14 +1,13 @@
import {BitStream} from 'bit-buffer';
import {EventEmitter} from 'events';
import {Header} from './Data/Header';
import {Match} from './Data/Match';
import {ConsoleCmdHandler} from './Parser/Message/ConsoleCmd';
import {DataTableHandler} from './Parser/Message/DataTable';
import {PacketMessageHandler} from './Parser/Message/Packet';
import {StringTableHandler} from './Parser/Message/StringTable';
import {UserCmdHandler} from './Parser/Message/UserCmd';
import {PacketTypeId} from './Data/Packet';
import {Packet, PacketTypeId} from './Data/Packet';
import {Message, MessageHandler, MessageType, PacketMessage} from './Data/Message';
import {ParserState} from './Data/ParserState';
const messageHandlers: Map<MessageType, MessageHandler<Message>> = new Map<MessageType, MessageHandler<Message>>([
[MessageType.Sigon, PacketMessageHandler],
@ -19,39 +18,41 @@ const messageHandlers: Map<MessageType, MessageHandler<Message>> = new Map<Messa
[MessageType.StringTables, StringTableHandler],
]);
export class Parser extends EventEmitter {
public stream: BitStream;
public match: Match;
protected skipPackets: PacketTypeId[];
export class Parser {
public readonly stream: BitStream;
public readonly parserState: ParserState;
private header: Header | null = null;
protected readonly skipPackets: PacketTypeId[];
public viewOrigin: number[][] = [[], []];
public viewAngles: number[][] = [[], []];
constructor(stream: BitStream, skipPackets: PacketTypeId[] = []) {
super();
this.stream = stream;
this.match = new Match();
this.on('packet', this.match.handlePacket.bind(this.match));
this.parserState = new ParserState();
this.skipPackets = skipPackets;
}
public readHeader() {
return this.parseHeader(this.stream);
public getHeader() {
if (!this.header) {
this.header = this.parseHeader(this.stream);
}
return this.header;
}
public parseBody() {
public * getPackets(): Iterable<Packet> {
// ensure that we are past the header
this.getHeader();
const messages = this.getMessages();
for (const message of messages) {
this.handleMessage(message);
yield* this.handleMessage(message);
}
this.emit('done', this.match);
return this.match;
}
private * getMessages(): Iterable<Message> {
let hasNext: boolean = true;
while (hasNext) {
const message = this.readMessage(this.stream, this.match);
const message = this.readMessage(this.stream, this.parserState);
if (!message) {
hasNext = false;
} else {
@ -60,20 +61,12 @@ export class Parser extends EventEmitter {
}
}
public tick() {
const message = this.readMessage(this.stream, this.match);
if (message) {
this.handleMessage(message);
}
return !!message;
}
protected parseMessage(data: BitStream, type: MessageType, tick: number, match: Match): Message {
protected parseMessage(data: BitStream, type: MessageType, tick: number, state: ParserState): Message {
const handler = messageHandlers.get(type);
if (!handler) {
throw new Error(`No handler for message of type ${MessageType[type]}`);
}
return handler.parseMessage(data, tick, match.parserState);
return handler.parseMessage(data, tick, state);
}
protected parseHeader(stream): Header {
@ -92,17 +85,17 @@ export class Parser extends EventEmitter {
};
}
protected handleMessage(message: Message) {
this.match.parserState.handleMessage(message);
protected * handleMessage(message: Message): Iterable<Packet> {
this.parserState.handleMessage(message);
if (message.type === MessageType.Packet) {
for (const packet of (message as PacketMessage).packets) {
this.match.parserState.handlePacket(packet);
this.emit('packet', packet);
this.parserState.handlePacket(packet);
yield packet;
}
}
}
protected readMessage(stream: BitStream, match: Match): Message | false {
protected readMessage(stream: BitStream, state: ParserState): Message | false {
if (stream.bitsLeft < 8) {
return false;
}
@ -143,6 +136,6 @@ export class Parser extends EventEmitter {
const length = stream.readInt32();
const buffer = stream.readBitStream(length * 8);
return this.parseMessage(buffer, type, tick, match);
return this.parseMessage(buffer, type, tick, state);
}
}

View file

@ -6,37 +6,57 @@ import {BitStream} from 'bit-buffer';
import * as split2 from 'split2';
import {createUnzip, createGunzip} from 'zlib';
import {PassThrough} from 'stream';
import {EntityId, PVS} from '../../Data/PacketEntity';
import {SendPropValue} from '../../Data/SendProp';
interface ResultData {
tick: number,
serverClass: string,
id: EntityId,
props: {[propName: string]: SendPropValue},
pvs: PVS
}
function writeEntities(name: string) {
const targetFile = `${__dirname}/../data/${name}_entities.json`;
const source = readFileSync(`${__dirname}/../data/${name}.dem`);
const demo = Demo.fromNodeBuffer(source);
const parser = demo.getParser(false);
parser.readHeader();
const match = parser.match;
const resultData = getResultData(parser.getPackets());
const writeStream = createWriteStream(targetFile, 'utf8');
parser.on('packet', (packet: Packet) => {
for (const result of resultData) {
writeStream.write(JSON.stringify(result) + '\n');
}
writeStream.end();
}
function* getResultData(packets: Iterable<Packet>): IterableIterator<ResultData> {
let tick = 0;
for (const packet of packets) {
if (packet.packetType === 'netTick') {
tick = packet.tick;
}
if (packet.packetType === 'packetEntities') {
for (const entity of packet.entities) {
const entityProps = {};
for (const prop of entity.props) {
entityProps[`${prop.definition.name}`] = prop.value;
}
writeStream.write(JSON.stringify({
tick: match.tick,
yield {
tick: tick,
serverClass: entity.serverClass.name,
id: entity.entityIndex,
props: entityProps,
pvs: entity.pvs
}) + '\n');
};
}
}
});
parser.parseBody();
writeStream.end();
}
}
function testEntities(name: string, entityCount: number) {
@ -45,34 +65,8 @@ function testEntities(name: string, entityCount: number) {
const source = readFileSync(`${__dirname}/../data/${name}.dem`);
const demo = Demo.fromNodeBuffer(source);
const parser = demo.getParser(false);
parser.readHeader();
const match = parser.match;
const resultData: any[] = [];
parser.on('packet', (packet: Packet) => {
if (packet.packetType === 'packetEntities') {
for (const entity of packet.entities) {
const entityProps = {};
for (const prop of entity.props) {
entityProps[`${prop.definition.name}`] = prop.value;
}
resultData.push({
tick: match.tick,
serverClass: entity.serverClass.name,
id: entity.entityIndex,
props: entityProps,
pvs: entity.pvs
});
}
}
});
function parseEntities() {
const message = parser.tick();
if (message && resultData.length === 0) {
parseEntities();
}
}
const resultData = getResultData(parser.getPackets());
const readStream = createReadStream(targetFile);
@ -81,14 +75,10 @@ function testEntities(name: string, entityCount: number) {
readStream
.pipe(createUnzip())
.pipe(split2(JSON.parse)).on('data', (data) => {
if (resultData.length < 1) {
parseEntities();
}
const result = resultData.shift();
assert.deepEqual(data, result, `Failed asserting that packet ${parsed} is the same`);
const result = resultData.next();
assert.deepEqual(data, result.value, `Failed asserting that packet ${parsed} is the same`);
parsed++;
}).on('end', () => {
assert.equal(resultData.length, 0, 'Entities left to be checked');
assert.equal(parsed, entityCount, 'unexpected number of entities');
resolve();

View file

@ -1,17 +1,14 @@
import * as assert from 'assert';
import {readFileSync} from 'fs';
import {Demo} from '../../Demo';
import {Packet} from '../../Data/Packet';
import {BitStream} from 'bit-buffer';
function testDemo(name: string, fastMode: boolean = false) {
const target = JSON.parse(readFileSync(`${__dirname}/../data/${name}.json`, 'utf8'));
const source = readFileSync(`${__dirname}/../data/${name}.dem`);
const demo = Demo.fromNodeBuffer(source);
const parser = demo.getParser(fastMode);
parser.readHeader();
parser.parseBody();
const parsed = parser.match.getState();
const analyser = demo.getAnalyser(fastMode);
const parsed = analyser.getBody().getState();
assert.deepEqual(JSON.parse(JSON.stringify(parsed)), target);
}