Files
strom/mc/proto/smart_convert.go
T
2026-08-06 15:43:20 +02:00

147 lines
5.1 KiB
Go

package proto
import (
"fmt"
"reflect"
"slices"
"git.anygate.cloud/anygatecloud/strom/mc/data"
"git.anygate.cloud/anygatecloud/strom/mc/event"
"git.anygate.cloud/anygatecloud/strom/mc/proto_base"
"git.anygate.cloud/anygatecloud/strom/mc/proto_generated"
)
// SmartConvertibleTo reports whether a value of type from can be recursively converted to type to.
func SmartConvertibleTo(from, to reflect.Type) bool {
switch from.Kind() {
case reflect.Struct:
if to.Kind() != reflect.Struct {
return false
}
if from.NumField() != to.NumField() {
return false
}
for i := range from.NumField() {
if !SmartConvertibleTo(from.Field(i).Type, to.Field(i).Type) {
return false
}
}
return true
case reflect.Pointer, reflect.Slice:
if from.Kind() != to.Kind() {
return false
}
return SmartConvertibleTo(from.Elem(), to.Elem())
default:
return from.ConvertibleTo(to)
}
}
// SmartConvert recursively converts a reflect.Value from one type to another compatible type.
func SmartConvert(from reflect.Value, to reflect.Type) (ret reflect.Value) {
switch from.Kind() {
case reflect.Struct:
ret = reflect.New(to).Elem()
for i := range from.NumField() {
ret.Field(i).Set(SmartConvert(from.Field(i), to.Field(i).Type))
}
case reflect.Pointer:
if from.IsNil() {
return reflect.Zero(to)
}
ret = reflect.New(to.Elem())
ret.Elem().Set(SmartConvert(from.Elem(), to.Elem()))
case reflect.Slice:
l := from.Len()
ret = reflect.MakeSlice(to, l, l)
for i := range l {
ret.Index(i).Set(SmartConvert(from.Index(i), to.Elem()))
}
default:
return from.Convert(to)
}
return ret
}
// RegisterUntilLatest registers a handler for all versions between the passed in packet version and the latest supported version.
// Warning: If a bad handler is passed in, the program will panic.
func (c *Conn) RegisterUntilLatest(hs ...any) {
for _, h := range hs {
eventType, hv := event.ValidateHandler(h)
c.RegisterUntilLow(eventType, hv, LatestVersion, false)
}
}
// RegisterUntil registers a handler for all versions between the passed in packet version and the specified version.
// Warning: If a bad handler is passed in, the program will panic.
func (c *Conn) RegisterUntil(until string, hs ...any) {
for _, h := range hs {
eventType, hv := event.ValidateHandler(h)
c.RegisterUntilLow(eventType, hv, until, false)
}
}
// RegisterCriticalUntilLatest registers a handler for all versions between the passed in packet version and the latest supported version.
// If this handler returns an error, the connection will be closed.
// Warning: If a bad handler is passed in, the program will panic.
func (c *Conn) RegisterCriticalUntilLatest(hs ...any) {
for _, h := range hs {
eventType, hv := event.ValidateHandler(h)
c.RegisterUntilLow(eventType, hv, LatestVersion, true)
}
}
// RegisterCriticalUntil registers a handler for all versions between the passed in packet version and the specified version.
// If this handler returns an error, the connection will be closed.
// Warning: If a bad handler is passed in, the program will panic.
func (c *Conn) RegisterCriticalUntil(until string, hs ...any) {
for _, h := range hs {
eventType, hv := event.ValidateHandler(h)
c.RegisterUntilLow(eventType, hv, until, true)
}
}
func (c *Conn) RegisterUntilLow(eventType reflect.Type, hv reflect.Value, until string, critical bool) {
c.RegisterLow(eventType, hv, critical)
if !eventType.Implements(reflect.TypeFor[proto_base.EncodeDecodeAble]()) {
panic("expected method with argument that implements proto_base.EncodeDecodeAble")
}
packetInfo, found := LookupPacketInfoByType(reflect.Zero(eventType).Interface().(proto_base.EncodeDecodeAble))
if !found {
panic("packet not found")
}
versionInfo, err := data.LookUpVersionByProtocolVersion(packetInfo.ProtocolVersion)
if err != nil {
panic("protocol version not found")
}
startIndex := slices.Index(proto_generated.SupportedVersions, versionInfo.MinecraftVersion)
endIndex := slices.Index(proto_generated.SupportedVersions, until)
if startIndex > endIndex {
panic(fmt.Sprintf("handler version %s is above until version %s", versionInfo.MinecraftVersion, until))
}
for i := startIndex + 1; i <= endIndex; i++ {
versionName := proto_generated.SupportedVersions[i]
versionInfo, err = data.LookUpVersionByName(versionName)
if err != nil {
panic("protocol version not found this should not happen even if a wrong type is passed in")
}
var newPacketInfo proto_base.PacketInfo
newPacketInfo, found = LookupPacketInfoByNameProtocolVersionStateAndDirection(packetInfo.Name, versionInfo.Version, packetInfo.State, packetInfo.Direction)
if !found {
panic("packet not found in version " + versionName)
}
newT := reflect.TypeOf(newPacketInfo.Type)
if !SmartConvertibleTo(newT, reflect.TypeOf(packetInfo.Type)) {
panic(fmt.Sprintf("packet %v is not convertible to %v", newT, reflect.TypeOf(packetInfo.Type)))
}
handleFunc := func(packet any) error {
v := hv.Call([]reflect.Value{SmartConvert(reflect.ValueOf(packet), reflect.TypeOf(packetInfo.Type))})[0]
if v.IsNil() {
return nil
}
return v.Interface().(error)
}
c.RegisterLow(newT, reflect.ValueOf(handleFunc), critical)
}
}