eq2go/internal/cps/compiler.go

217 lines
5.3 KiB
Go

package cps
import (
"fmt"
"sort"
)
// Compiler compiles packet definitions into optimized structures
type Compiler struct {
packets map[string]*PacketDef
}
// NewCompiler creates a compiler with parsed packet definitions
func NewCompiler(packets map[string]*PacketDef) *Compiler {
return &Compiler{packets: packets}
}
// Compile compiles a packet structure for a specific client version
func (c *Compiler) Compile(packetName string, clientVersion int) (*CompiledStruct, error) {
packet, exists := c.packets[packetName]
if !exists {
return nil, fmt.Errorf("packet %s not found", packetName)
}
// Find best matching version (highest <= clientVersion)
var bestVersion int = -1
for version := range packet.Versions {
if version <= clientVersion && version > bestVersion {
bestVersion = version
}
}
if bestVersion == -1 {
return nil, fmt.Errorf("no compatible version found for %s (client version %d)", packetName, clientVersion)
}
// Build cascaded field list
fields, fieldOrder, err := c.buildCascadedFields(packet, bestVersion)
if err != nil {
return nil, err
}
// Resolve positions and create final structure
compiledFields, totalSize := c.resolvePositions(fields, fieldOrder)
return &CompiledStruct{
Name: packetName,
Version: bestVersion,
Fields: compiledFields,
Size: totalSize,
}, nil
}
// buildCascadedFields builds the final field list by cascading from v1 to target version
func (c *Compiler) buildCascadedFields(packet *PacketDef, targetVersion int) (map[string]*FieldDef, []string, error) {
result := make(map[string]*FieldDef)
var allFieldOrder []string
// Get all versions up to target, sorted
var versions []int
for version := range packet.Versions {
if version <= targetVersion {
versions = append(versions, version)
}
}
sort.Ints(versions)
// Apply each version in order
for _, version := range versions {
versionDef := packet.Versions[version]
// Add new fields in their original order
for _, fieldName := range versionDef.FieldOrder {
fieldDef := versionDef.Fields[fieldName]
if fieldDef.Remove {
delete(result, fieldName)
// Remove from order list
for i, name := range allFieldOrder {
if name == fieldName {
allFieldOrder = append(allFieldOrder[:i], allFieldOrder[i+1:]...)
break
}
}
} else {
// Create copy to avoid modifying original
newField := *fieldDef
// If field exists, preserve position if not explicitly set
if existing, exists := result[fieldName]; exists {
if newField.Position == -1 && existing.Position >= 0 {
newField.Position = existing.Position
}
if newField.After == "" && existing.After != "" {
newField.After = existing.After
}
if newField.Before == "" && existing.Before != "" {
newField.Before = existing.Before
}
} else {
// New field - add to order
allFieldOrder = append(allFieldOrder, fieldName)
}
result[fieldName] = &newField
}
}
}
return result, allFieldOrder, nil
}
// resolvePositions resolves field positions and creates ordered field list
func (c *Compiler) resolvePositions(fields map[string]*FieldDef, originalOrder []string) ([]*CompiledField, int) {
var compiledFields []*CompiledField
var fieldOrder []string
// Handle explicitly positioned fields first
positioned := make(map[int]string)
var maxPos int
for name, field := range fields {
if field.Position >= 0 {
positioned[field.Position] = name
if field.Position > maxPos {
maxPos = field.Position
}
}
}
// Build initial order from positioned fields
for i := 0; i <= maxPos; i++ {
if name, exists := positioned[i]; exists {
fieldOrder = append(fieldOrder, name)
}
}
// Handle @after/@before positioning
for _, name := range originalOrder {
field := fields[name]
if field.Position >= 0 {
continue // Already handled
}
if field.After != "" {
// Insert after referenced field
for i, existing := range fieldOrder {
if existing == field.After {
fieldOrder = append(fieldOrder[:i+1], append([]string{name}, fieldOrder[i+1:]...)...)
break
}
}
} else if field.Before != "" {
// Insert before referenced field
for i, existing := range fieldOrder {
if existing == field.Before {
fieldOrder = append(fieldOrder[:i], append([]string{name}, fieldOrder[i:]...)...)
break
}
}
} else {
// Append in original order
fieldOrder = append(fieldOrder, name)
}
}
// Create compiled fields with calculated offsets
offset := 0
dynamic := false
for _, name := range fieldOrder {
field := fields[name]
compiledField := &CompiledField{
Name: name,
Type: field.Type,
Size: field.Size,
Offset: offset,
Dynamic: field.Size == -1,
}
compiledFields = append(compiledFields, compiledField)
// Calculate offset for next field
if field.Size == -1 {
dynamic = true
} else if !dynamic {
fieldSize := c.getTypeSize(field.Type)
if field.Size > 0 {
fieldSize *= field.Size
}
offset += fieldSize
}
}
totalSize := offset
if dynamic {
totalSize = -1
}
return compiledFields, totalSize
}
// getTypeSize returns the byte size of a data type
func (c *Compiler) getTypeSize(t EQ2Type) int {
switch t {
case TypeInt8:
return 1
case TypeInt16:
return 2
case TypeInt32, TypeFloat32, TypeColor:
return 4
case TypeInt64:
return 8
default:
return -1 // Variable size
}
}