1+ // Package sendtablescs2 implements the CS2 send-tables (FlattenedSerializer) parser.
12package sendtablescs2
23
34import (
@@ -17,6 +18,7 @@ type class struct {
1718 classId int32 //nolint:revive
1819 name string
1920 serializer * serializer
21+ polyCount int // size of the per-entity polySerializers slice needed by entities of this class; 0 if no polymorphic pointer fields are reachable
2022 createdHandlers []st.EntityCreatedHandler
2123 fpNameCache * fpNameTreeCache
2224 // fpFlatCache provides O(1) lookup for the common case: depth ≤ 3 and
@@ -56,7 +58,12 @@ func (c *class) collectFieldsEntries(fields []*field, prefix string) []string {
5658 paths := make ([]string , 0 )
5759
5860 for _ , field := range fields {
59- if field .serializer != nil {
61+ if len (field .polyTypes ) > 0 { //nolint:gocritic
62+ // Polymorphic pointer: each candidate serializer contributes its
63+ // own sub-fields. The active type varies per entity, so enumerate
64+ // every alternative (the field's own serializer is polyTypes[0]).
65+ paths = append (paths , c .collectPolyFieldEntries (field , prefix )... )
66+ } else if field .serializer != nil {
6067 subPaths := c .collectFieldsEntries (field .serializer .fields , prefix + field .serializer .name + "." )
6168 paths = append (paths , subPaths ... )
6269 } else {
@@ -67,6 +74,29 @@ func (c *class) collectFieldsEntries(fields []*field, prefix string) []string {
6774 return paths
6875}
6976
77+ // collectPolyFieldEntries enumerates the property entries of every serializer a
78+ // polymorphic pointer field could activate, deduplicated. The active type varies
79+ // per entity, so all alternatives must be listed.
80+ func (c * class ) collectPolyFieldEntries (f * field , prefix string ) []string {
81+ paths := make ([]string , 0 )
82+ seen := make (map [string ]bool )
83+
84+ for _ , ser := range f .polyTypes {
85+ if ser == nil {
86+ continue
87+ }
88+
89+ for _ , sub := range c .collectFieldsEntries (ser .fields , prefix + ser .name + "." ) {
90+ if ! seen [sub ] {
91+ seen [sub ] = true
92+ paths = append (paths , sub )
93+ }
94+ }
95+ }
96+
97+ return paths
98+ }
99+
70100// fpFlatKey encodes a field path as a uint64 for O(1) map lookup.
71101// Returns (key, true) when depth ≤ 3 and all components fit in 14 bits.
72102// Falls back to the tree cache otherwise.
@@ -91,57 +121,65 @@ func fpFlatKey(fp *fieldPath) (uint64, bool) {
91121 return key , true
92122}
93123
94- func ( c * class ) getNameForFieldPath ( fp * fieldPath ) string {
95- if key , ok := fpFlatKey (fp ); ok {
96- if name , hit := c . fpFlatCache [ key ]; hit {
97- return name
98- }
99-
100- name := strings . Join ( c . serializer . getNameForFieldPath ( fp , 0 ), "." )
101- c . fpFlatCache [ key ] = name
124+ //nolint:nestif
125+ func ( c * class ) getNameForFieldPath (fp * fieldPath , ps [] * serializer ) string {
126+ if ps == nil {
127+ // No polymorphic fields: use the shared class-level caches.
128+ if key , ok := fpFlatKey ( fp ); ok {
129+ if name , hit := c . fpFlatCache [ key ]; hit {
130+ return name
131+ }
102132
103- return name
104- }
133+ name := strings . Join ( c . serializer . getNameForFieldPath ( fp , 0 , nil ), "." )
134+ c . fpFlatCache [ key ] = name
105135
106- // Slow path: deep or large-component path — use the pointer tree.
107- currentCacheNode := c . fpNameCache
136+ return name
137+ }
108138
109- for i := 0 ; i <= fp .last ; i ++ {
110- pos := fp .path [i ]
111- if pos >= len (currentCacheNode .next ) {
112- needed := pos + 1
113- if cap (currentCacheNode .next ) >= needed {
114- currentCacheNode .next = currentCacheNode .next [:needed ]
115- } else {
116- newCap := needed * 2
117- if newCap < 8 {
118- newCap = 8
139+ // Slow path: deep or large-component path — use the pointer tree.
140+ currentCacheNode := c .fpNameCache
141+
142+ for i := 0 ; i <= fp .last ; i ++ {
143+ pos := fp .path [i ]
144+ if pos >= len (currentCacheNode .next ) {
145+ needed := pos + 1
146+ if cap (currentCacheNode .next ) >= needed {
147+ currentCacheNode .next = currentCacheNode .next [:needed ]
148+ } else {
149+ newCap := needed * 2
150+ if newCap < 8 {
151+ newCap = 8
152+ }
153+
154+ newNext := make ([]* fpNameTreeCache , needed , newCap )
155+ copy (newNext , currentCacheNode .next )
156+ currentCacheNode .next = newNext
119157 }
158+ }
120159
121- newNext := make ([]* fpNameTreeCache , needed , newCap )
122- copy (newNext , currentCacheNode .next )
123- currentCacheNode .next = newNext
160+ if currentCacheNode .next [pos ] == nil {
161+ currentCacheNode .next [pos ] = & fpNameTreeCache {}
124162 }
125- }
126163
127- if currentCacheNode .next [pos ] == nil {
128- currentCacheNode .next [pos ] = & fpNameTreeCache {}
164+ currentCacheNode = currentCacheNode .next [pos ]
129165 }
130166
131- currentCacheNode = currentCacheNode .next [pos ]
132- }
167+ if currentCacheNode .name == "" {
168+ currentCacheNode .name = strings .Join (c .serializer .getNameForFieldPath (fp , 0 , nil ), "." )
169+ }
133170
134- if currentCacheNode .name == "" {
135- currentCacheNode .name = strings .Join (c .serializer .getNameForFieldPath (fp , 0 ), "." )
171+ return currentCacheNode .name
136172 }
137173
138- return currentCacheNode .name
174+ // Polymorphic entity: skip class-level caches because the active type
175+ // varies per entity and the same field path can resolve to different names.
176+ return strings .Join (c .serializer .getNameForFieldPath (fp , 0 , ps ), "." )
139177}
140178
141- func (c * class ) getFieldPathForName (fp * fieldPath , name string ) bool {
142- return c .serializer .getFieldPathForName (fp , name )
179+ func (c * class ) getFieldPathForName (fp * fieldPath , name string , ps [] * serializer ) bool {
180+ return c .serializer .getFieldPathForName (fp , name , ps )
143181}
144182
145- func (c * class ) getFieldPaths (fp * fieldPath , state * fieldState ) []* fieldPath {
146- return c .serializer .getFieldPaths (fp , state )
183+ func (c * class ) getFieldPaths (fp * fieldPath , state * fieldState , ps [] * serializer ) []* fieldPath {
184+ return c .serializer .getFieldPaths (fp , state , ps )
147185}
0 commit comments