diff --git a/ent/class.go b/ent/class.go index 3a92049..7f8357e 100644 --- a/ent/class.go +++ b/ent/class.go @@ -13,10 +13,12 @@ type Class struct { // all other entities for this class use this instance as a prototype baseline *Entity + + fp *fieldPath } func (c *Class) New() *Entity { - return &Entity{Class: c} + return &Entity{Class: c, fields: make(map[string]interface{}, len(c.Fields))} } func (c Class) String() string { diff --git a/ent/dict.go b/ent/dict.go index 547a628..cd3c26f 100644 --- a/ent/dict.go +++ b/ent/dict.go @@ -164,6 +164,10 @@ func (d *Dict) updateBaselines(t *stbl.Table) { } func (d *Dict) syncBaselines() { + if !d.hasClassinfo() { + Debug.Printf("syncBaselines skip: no classInfo yet") + return + } Debug.Printf("syncBaselines start") if d.base == nil { Debug.Printf("syncBaselines failed: reference to baseline string table is nil") diff --git a/ent/entity.go b/ent/entity.go index f209dba..220c227 100644 --- a/ent/entity.go +++ b/ent/entity.go @@ -8,6 +8,7 @@ import ( type Entity struct { *Class + fields map[string]interface{} } func (e *Entity) Read(br bit.Reader) error { @@ -17,9 +18,8 @@ func (e *Entity) Read(br bit.Reader) error { Debug.Printf("entity %v read", e) fp := newFieldPath() - if err := fp.read(br, htree); err != nil { + if err := fp.read(br, htree, e.Class); err != nil { return fmt.Errorf("unable to read entity: %v", err) } - Debug.Printf("fieldpath %v", fp.path()) return nil } diff --git a/ent/field.go b/ent/field.go index 8aad2c2..b5d3f48 100644 --- a/ent/field.go +++ b/ent/field.go @@ -8,16 +8,17 @@ import ( ) type Field struct { - _type Symbol - name Symbol - sendNode Symbol - bits *int - low *float32 - high *float32 - flags *int32 - serializer *Symbol - serializerVersion *int32 - encoder *Symbol + _type Symbol // type of data held by the field + name Symbol // name of the field + sendNode Symbol // not sure what this is + bits *int // number of bits used to encode field? + low *float32 // lower limit of field values + high *float32 // upper limit of field values + flags *int32 // dunno what these flags do + serializer *Symbol // class on which the field was defined + serializerVersion *int32 // version of the class on which the field was defined + class *Class // source class on which the field was originally defined + encoder *Symbol // binary reader } func (f Field) String() string { @@ -71,4 +72,5 @@ func (f *Field) fromProto(flat *dota.ProtoFlattenedSerializerFieldT, t *SymbolTa f.serializerVersion = flat.FieldSerializerVersion // panic if we don't have a send node cause that shit is corrupt yo f.sendNode = t.Symbol(int(*flat.SendNodeSym)) + Debug.Printf("new field: %v", f) } diff --git a/ent/fieldpath.go b/ent/fieldpath.go index 8b7311a..4e5e412 100644 --- a/ent/fieldpath.go +++ b/ent/fieldpath.go @@ -5,13 +5,13 @@ import ( "github.com/jordanorelli/hyperstone/bit" ) -// a fieldpath is a list of integers that is used to walk the type hierarchy to -// identify a given field on a given type. type fieldPath struct { // slice of values, to be reused over and over vals []int // index of the last valid value. e.g., the head of the stack. last int + + history [][]int } func newFieldPath() *fieldPath { @@ -42,17 +42,36 @@ func (f *fieldPath) replaceAll(fn func(v int) int) { // reads the sequence of id values off of the provided bit reader given the // huffman tree of fieldpath ops rooted at the node n -func (f *fieldPath) read(br bit.Reader, n node) error { +func (f *fieldPath) read(br bit.Reader, n node, class *Class) error { f.last = 0 for fn := walk(n, br); fn != nil; fn = walk(n, br) { if err := br.Err(); err != nil { return fmt.Errorf("unable to read fieldpath: reader error: %v", err) } fn(f, br) + Debug.Printf("fieldpath: %v", f.path()) + // Debug.Printf("fieldpath: %v", f.getField(class)) } return nil } +func (f *fieldPath) getField(class *Class) *Field { + if f.last > 0 { + for i := 0; i < f.last; i++ { + if f.vals[i] >= len(class.Fields) { + Info.Fatalf("bad access for field %d on class %v; class has only %d fields", f.vals[i], class, len(class.Fields)) + } + field := class.Fields[f.vals[i]] + if field.class == nil { + Info.Fatalf("class %s field at %d is %v, has no class", class, f.vals[i], field) + } else { + class = class.Fields[f.vals[i]].class + } + } + } + return class.Fields[f.vals[f.last]] +} + // the subslice of valid index values that has been read on the fieldpath func (f *fieldPath) path() []int { return f.vals[:f.last+1] diff --git a/ent/namespace.go b/ent/namespace.go index ad77ea7..efa4f48 100644 --- a/ent/namespace.go +++ b/ent/namespace.go @@ -37,6 +37,10 @@ func (n *Namespace) mergeClassInfo(ci *dota.CDemoClassInfo) { n.idBits = int(math.Floor(math.Log2(float64(len(n.classIds))))) + 1 } +func (n *Namespace) hasClassinfo() bool { + return n.classIds != nil && len(n.classIds) > 0 +} + // merges the send table data found in the replay protobufs. The send table // data contains a specification for an entity type system. func (n *Namespace) mergeSendTables(st *dota.CDemoSendTables) error { @@ -58,6 +62,8 @@ func (n *Namespace) mergeSendTables(st *dota.CDemoSendTables) error { n.SymbolTable = SymbolTable(flat.GetSymbols()) + // the full set of fields that may appear on the classes is read first. + // each class will have a list of fields. fields := make([]Field, len(flat.GetFields())) for i, f := range flat.GetFields() { fields[i].fromProto(f, &n.SymbolTable) @@ -66,15 +72,15 @@ func (n *Namespace) mergeSendTables(st *dota.CDemoSendTables) error { n.classes = make(map[classId]*Class, len(flat.GetSerializers())) n.classesByName = make(map[string]map[int]*Class, len(flat.GetSerializers())) + // each serializer in the source data generates a class. for _, c := range flat.GetSerializers() { name := n.Symbol(int(c.GetSerializerNameSym())) version := int(c.GetSerializerVersion()) + Debug.Printf("new class: %s %v", name, version) class := Class{Name: name, Version: version} class.fromProto(c, fields) - Debug.Printf("new class: %v", class) - id := classId{name: name, version: version} n.classes[id] = &class @@ -85,6 +91,23 @@ func (n *Namespace) mergeSendTables(st *dota.CDemoSendTables) error { } } + // some fields explicitly reference their origin class (P). that is is, if + // a given field F is included in some class C, the field F having an + // origin class P indicates that the class C has the class P as an + // ancestor. since these references are circular, we unpacked the fields + // first, then the classes, and now we re-visit the fields to set their + // origin class pointers, now that the classes exist. + for i := range fields { + f := &fields[i] + if f.serializer != nil { + if f.serializerVersion != nil { + f.class = n.classesByName[f.serializer.String()][int(*f.serializerVersion)] + } else { + f.class = n.NewestClass(f.serializer.String()) + } + } + } + return br.Err() } @@ -96,12 +119,8 @@ func (n *Namespace) Class(name string, version int) *Class { return n.classesByName[name][version] } -func (n *Namespace) ClassByNetId(id int) *Class { - name, ok := n.classIds[id] - if !ok { - Debug.Printf("can't find class name for net id %d", id) - return nil - } +// retrieves the newest version of a class, as referenced by name. +func (n *Namespace) NewestClass(name string) *Class { versions, newest := n.classesByName[name], -1 for v, _ := range versions { if v > newest { @@ -109,8 +128,15 @@ func (n *Namespace) ClassByNetId(id int) *Class { } } if newest == -1 { - Debug.Printf("class %s has no known versions in its version map", name) - return nil + Info.Fatalf("class %s has no known versions in its version map", name) } return versions[newest] } + +func (n *Namespace) ClassByNetId(id int) *Class { + name, ok := n.classIds[id] + if !ok { + Info.Fatalf("can't find class name for net id %d", id) + } + return n.NewestClass(name) +}