diff --git a/pkg/demoinfocs/parser.go b/pkg/demoinfocs/parser.go index 4b25eade..d3613a22 100644 --- a/pkg/demoinfocs/parser.go +++ b/pkg/demoinfocs/parser.go @@ -104,6 +104,7 @@ type parser struct { equipmentTypePerModel map[uint64]common.EquipmentType // Used to retrieve the EquipmentType of grenade projectiles based on models value. Source 2 only. stringTables []createStringTable // Contains all created sendtables, needed when updating them delayedEventHandlers []func() // Contains event handlers that need to be executed at the end of a tick (e.g. flash events because FlashDuration isn't updated before that) + pendingMessagesCache []pendingMessage // Cache for pending messages that need to be dispatched after the current tick } // NetMessageCreator creates additional net-messages to be dispatched to net-message handlers. diff --git a/pkg/demoinfocs/s2_commands.go b/pkg/demoinfocs/s2_commands.go index 8a528f88..c4d67587 100644 --- a/pkg/demoinfocs/s2_commands.go +++ b/pkg/demoinfocs/s2_commands.go @@ -3,7 +3,7 @@ package demoinfocs import ( "bytes" "fmt" - "sort" + "slices" "time" "github.com/pkg/errors" @@ -310,21 +310,21 @@ func (p *parser) handleDemoPacket(pack *msgs2.CDemoPacket) { r := bitread.NewSmallBitReader(bytes.NewReader(b)) - ms := make([]pendingMessage, 0) + p.pendingMessagesCache = p.pendingMessagesCache[:0] for len(b)*8-r.ActualPosition() > 7 { t := int32(r.ReadUBitInt()) size := r.ReadVarInt32() buf := r.ReadBytes(int(size)) - ms = append(ms, pendingMessage{t, buf}) + p.pendingMessagesCache = append(p.pendingMessagesCache, pendingMessage{t, buf}) } - sort.SliceStable(ms, func(i, j int) bool { - return ms[i].priority() < ms[j].priority() // TODO: taken from dotabuff/manta. do we really need this? + slices.SortStableFunc(p.pendingMessagesCache, func(a, b pendingMessage) int { + return a.priority() - b.priority() }) - for _, m := range ms { + for _, m := range p.pendingMessagesCache { var msgCreator NetMessageCreator if m.t < int32(msgs2.SVC_Messages_svc_ServerInfo) { diff --git a/pkg/demoinfocs/sendtables2/entity.go b/pkg/demoinfocs/sendtables2/entity.go index de9c2ecf..e7c33297 100644 --- a/pkg/demoinfocs/sendtables2/entity.go +++ b/pkg/demoinfocs/sendtables2/entity.go @@ -484,12 +484,7 @@ func (p *Parser) OnPacketEntities(m *msgs2.CSVCMsg_PacketEntities) error { p.entityFullPackets++ } - type tuple struct { - ent *Entity - op st.EntityOp - } - - var tuples []tuple + p.tuplesCache = p.tuplesCache[:0] for ; updates > 0; updates-- { var ( @@ -575,10 +570,10 @@ func (p *Parser) OnPacketEntities(m *msgs2.CSVCMsg_PacketEntities) error { } } - tuples = append(tuples, tuple{e, op}) + p.tuplesCache = append(p.tuplesCache, tuple{e, op}) } - for _, t := range tuples { + for _, t := range p.tuplesCache { e := t.ent for _, h := range p.entityHandlers { diff --git a/pkg/demoinfocs/sendtables2/field_decoder.go b/pkg/demoinfocs/sendtables2/field_decoder.go index 8254358f..ca23d779 100644 --- a/pkg/demoinfocs/sendtables2/field_decoder.go +++ b/pkg/demoinfocs/sendtables2/field_decoder.go @@ -260,6 +260,7 @@ func quantizedFactory(f *field) fieldDecoder { } qfd := newQuantizedFloatDecoder(f.bitCount, f.encodeFlags, f.lowValue, f.highValue) + return func(r *reader) interface{} { return qfd.decode(r) } @@ -274,9 +275,11 @@ func vectorFactory(n int) fieldFactory { d := floatFactory(f) return func(r *reader) interface{} { x := make([]float32, n) + for i := 0; i < n; i++ { x[i] = d(r).(float32) } + return x } } @@ -319,7 +322,7 @@ func ammoDecoder(r *reader) interface{} { } func noscaleDecoder(r *reader) interface{} { - return math.Float32frombits(r.readBits(32)) + return math.Float32frombits(r.readLeUint32()) } func runeTimeDecoder(r *reader) interface{} { diff --git a/pkg/demoinfocs/sendtables2/parser.go b/pkg/demoinfocs/sendtables2/parser.go index 37dae207..892b4cdc 100644 --- a/pkg/demoinfocs/sendtables2/parser.go +++ b/pkg/demoinfocs/sendtables2/parser.go @@ -51,6 +51,11 @@ var itemCounts = map[string]int{ "MAX_ABILITY_DRAFT_ABILITIES": 48, } +type tuple struct { + ent *Entity + op st.EntityOp +} + type Parser struct { serializers map[string]*serializer classIdSize uint32 @@ -61,6 +66,7 @@ type Parser struct { entities map[int32]*Entity entityHandlers []st.EntityHandler pathCache []*fieldPath + tuplesCache []tuple } func (p *Parser) ReadEnterPVS(r *bit.BitReader, index int, entities map[int]st.Entity, slot int) st.Entity { diff --git a/pkg/demoinfocs/sendtables2/reader.go b/pkg/demoinfocs/sendtables2/reader.go index f7792435..323660c0 100644 --- a/pkg/demoinfocs/sendtables2/reader.go +++ b/pkg/demoinfocs/sendtables2/reader.go @@ -39,13 +39,15 @@ func (r *reader) remBytes() uint32 { // nextByte reads the next byte from the buffer func (r *reader) nextByte() byte { - r.pos++ - - if r.pos > r.size { + if r.pos >= r.size { _panicf("nextByte: insufficient buffer (%d of %d)", r.pos, r.size) } - return r.buf[r.pos-1] + x := r.buf[r.pos] + + r.pos++ + + return x } // readBits returns the uint32 value for the given number of sequential bits @@ -77,22 +79,31 @@ func (r *reader) readBytes(n uint32) []byte { // Fast path if we're byte aligned if r.bitCount == 0 { r.pos += n + if r.pos > r.size { _panicf("readBytes: insufficient buffer (%d of %d)", r.pos, r.size) } + return r.buf[r.pos-n : r.pos] } buf := make([]byte, n) + for i := uint32(0); i < n; i++ { buf[i] = byte(r.readBits(8)) } + return buf } // readLeUint32 reads an little-endian uint32 func (r *reader) readLeUint32() uint32 { - return binary.LittleEndian.Uint32(r.readBytes(4)) + // Fast path if we're byte aligned + if r.bitCount == 0 { + return binary.LittleEndian.Uint32(r.readBytes(4)) + } + + return r.readBits(32) } // readLeUint64 reads a little-endian uint64