all: more efficient type asserting (#341)
Closes #340 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
- commit
- 684058f04464150061697541bfd7424774748ed3
- author
- TotallyGamerJet · · 2025-09-23 18:00 UTC
- committer
- GitHub · · 2025-09-23 18:00 UTC
- parent
- 82c63c0
- signature
- GPG Unverified
B5690EEEBB952194The key is unknown - refs
- tag: v0.10.0-alpha.1
5 files · +45 -5
M func.go +2 -1
| @@ -15,6 +15,7 @@ import ( | |||
| 15 | 15 | "unsafe" | |
| 16 | 16 | ||
| 17 | 17 | "github.com/ebitengine/purego/internal/strings" | |
| 18 | + | "github.com/ebitengine/purego/internal/xreflect" | |
| 18 | 19 | ) | |
| 19 | 20 | ||
| 20 | 21 | var thePool = sync.Pool{New: func() any { | |
| @@ -271,7 +272,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||
| 271 | 272 | } | |
| 272 | 273 | } | |
| 273 | 274 | for i, v := range args { | |
| 274 | - | if variadic, ok := args[i].Interface().([]any); ok { | |
| 275 | + | if variadic, ok := xreflect.TypeAssert[[]any](args[i]); ok { | |
| 275 | 276 | if i != len(args)-1 { | |
| 276 | 277 | panic("purego: can only expand last parameter") | |
| 277 | 278 | } | |
| @@ -15,6 +15,7 @@ import ( | |||||
| 15 | "unsafe" | 15 | "unsafe" | ||
| 16 | 16 | ||||
| 17 | "github.com/ebitengine/purego/internal/strings" | 17 | "github.com/ebitengine/purego/internal/strings" | ||
| 18 | + | "github.com/ebitengine/purego/internal/xreflect" | |||
| 18 | ) | 19 | ) | ||
| 19 | 20 | ||||
| 20 | var thePool = sync.Pool{New: func() any { | 21 | var thePool = sync.Pool{New: func() any { | ||
| @@ -271,7 +272,7 @@ func RegisterFunc(fptr any, cfn uintptr) { | |||||
| 271 | } | 272 | } | ||
| 272 | } | 273 | } | ||
| 273 | for i, v := range args { | 274 | for i, v := range args { | ||
| 274 | - | if variadic, ok := args[i].Interface().([]any); ok { | 275 | + | if variadic, ok := xreflect.TypeAssert[[]any](args[i]); ok { |
| 275 | if i != len(args)-1 { | 276 | if i != len(args)-1 { | ||
| 276 | panic("purego: can only expand last parameter") | 277 | panic("purego: can only expand last parameter") | ||
| 277 | } | 278 | } | ||
A internal/xreflect/reflect_go124.go +15 -0
| @@ -0,0 +1,15 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors | |
| 3 | + | ||
| 4 | + | //go:build !go1.25 | |
| 5 | + | ||
| 6 | + | package xreflect | |
| 7 | + | ||
| 8 | + | import "reflect" | |
| 9 | + | ||
| 10 | + | // TODO: remove this and use Go 1.25's reflect.TypeAssert when minimum go.mod version is 1.25 | |
| 11 | + | ||
| 12 | + | func TypeAssert[T any](v reflect.Value) (T, bool) { | |
| 13 | + | v2, ok := v.Interface().(T) | |
| 14 | + | return v2, ok | |
| 15 | + | } | |
| @@ -0,0 +1,15 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | //go:build !go1.25 | |||
| 5 | + | ||||
| 6 | + | package xreflect | |||
| 7 | + | ||||
| 8 | + | import "reflect" | |||
| 9 | + | ||||
| 10 | + | // TODO: remove this and use Go 1.25's reflect.TypeAssert when minimum go.mod version is 1.25 | |||
| 11 | + | ||||
| 12 | + | func TypeAssert[T any](v reflect.Value) (T, bool) { | |||
| 13 | + | v2, ok := v.Interface().(T) | |||
| 14 | + | return v2, ok | |||
| 15 | + | } | |||
A internal/xreflect/reflect_go125.go +12 -0
| @@ -0,0 +1,12 @@ | |||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |
| 2 | + | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors | |
| 3 | + | ||
| 4 | + | //go:build go1.25 | |
| 5 | + | ||
| 6 | + | package xreflect | |
| 7 | + | ||
| 8 | + | import "reflect" | |
| 9 | + | ||
| 10 | + | func TypeAssert[T any](v reflect.Value) (T, bool) { | |
| 11 | + | return reflect.TypeAssert[T](v) | |
| 12 | + | } | |
| @@ -0,0 +1,12 @@ | |||||
| 1 | + | // SPDX-License-Identifier: Apache-2.0 | |||
| 2 | + | // SPDX-FileCopyrightText: 2025 The Ebitengine Authors | |||
| 3 | + | ||||
| 4 | + | //go:build go1.25 | |||
| 5 | + | ||||
| 6 | + | package xreflect | |||
| 7 | + | ||||
| 8 | + | import "reflect" | |||
| 9 | + | ||||
| 10 | + | func TypeAssert[T any](v reflect.Value) (T, bool) { | |||
| 11 | + | return reflect.TypeAssert[T](v) | |||
| 12 | + | } | |||
M objc/objc_block_darwin.go +7 -2
| @@ -10,6 +10,7 @@ import ( | |||
| 10 | 10 | "unsafe" | |
| 11 | 11 | ||
| 12 | 12 | "github.com/ebitengine/purego" | |
| 13 | + | "github.com/ebitengine/purego/internal/xreflect" | |
| 13 | 14 | ) | |
| 14 | 15 | ||
| 15 | 16 | const ( | |
| @@ -167,7 +168,11 @@ func (b *blockCache) getLayout(typ reflect.Type) blockLayout { | |||
| 167 | 168 | reflect.MakeFunc( | |
| 168 | 169 | typ, | |
| 169 | 170 | func(args []reflect.Value) (results []reflect.Value) { | |
| 170 | - | return b.Functions.Load(args[0].Interface().(Block)).Call(args) | |
| 171 | + | block, ok := xreflect.TypeAssert[Block](args[0]) | |
| 172 | + | if !ok { | |
| 173 | + | panic(fmt.Sprintf("objc: block argument is not a block but %s", args[0].Type().String())) | |
| 174 | + | } | |
| 175 | + | return b.Functions.Load(block).Call(args) | |
| 171 | 176 | }, | |
| 172 | 177 | ).Interface(), | |
| 173 | 178 | ) | |
| @@ -258,7 +263,7 @@ func InvokeBlock[T any](block Block, args ...any) (result T, err error) { | |||
| 258 | 263 | callResult := fn.Call(reflectedArgs) | |
| 259 | 264 | ||
| 260 | 265 | var ok bool | |
| 261 | - | result, ok = callResult[0].Interface().(T) | |
| 266 | + | result, ok = xreflect.TypeAssert[T](callResult[0]) | |
| 262 | 267 | if !ok { | |
| 263 | 268 | return result, fmt.Errorf("objc: the returned value type %s was not %T", callResult[0].Type().String(), result) | |
| 264 | 269 | } | |
| @@ -10,6 +10,7 @@ import ( | |||||
| 10 | "unsafe" | 10 | "unsafe" | ||
| 11 | 11 | ||||
| 12 | "github.com/ebitengine/purego" | 12 | "github.com/ebitengine/purego" | ||
| 13 | + | "github.com/ebitengine/purego/internal/xreflect" | |||
| 13 | ) | 14 | ) | ||
| 14 | 15 | ||||
| 15 | const ( | 16 | const ( | ||
| @@ -167,7 +168,11 @@ func (b *blockCache) getLayout(typ reflect.Type) blockLayout { | |||||
| 167 | reflect.MakeFunc( | 168 | reflect.MakeFunc( | ||
| 168 | typ, | 169 | typ, | ||
| 169 | func(args []reflect.Value) (results []reflect.Value) { | 170 | func(args []reflect.Value) (results []reflect.Value) { | ||
| 170 | - | return b.Functions.Load(args[0].Interface().(Block)).Call(args) | 171 | + | block, ok := xreflect.TypeAssert[Block](args[0]) |
| 172 | + | if !ok { | |||
| 173 | + | panic(fmt.Sprintf("objc: block argument is not a block but %s", args[0].Type().String())) | |||
| 174 | + | } | |||
| 175 | + | return b.Functions.Load(block).Call(args) | |||
| 171 | }, | 176 | }, | ||
| 172 | ).Interface(), | 177 | ).Interface(), | ||
| 173 | ) | 178 | ) | ||
| @@ -258,7 +263,7 @@ func InvokeBlock[T any](block Block, args ...any) (result T, err error) { | |||||
| 258 | callResult := fn.Call(reflectedArgs) | 263 | callResult := fn.Call(reflectedArgs) | ||
| 259 | 264 | ||||
| 260 | var ok bool | 265 | var ok bool | ||
| 261 | - | result, ok = callResult[0].Interface().(T) | 266 | + | result, ok = xreflect.TypeAssert[T](callResult[0]) |
| 262 | if !ok { | 267 | if !ok { | ||
| 263 | return result, fmt.Errorf("objc: the returned value type %s was not %T", callResult[0].Type().String(), result) | 268 | return result, fmt.Errorf("objc: the returned value type %s was not %T", callResult[0].Type().String(), result) | ||
| 264 | } | 269 | } | ||
M objc/objc_runtime_darwin.go +9 -2
| @@ -17,6 +17,7 @@ import ( | |||
| 17 | 17 | ||
| 18 | 18 | "github.com/ebitengine/purego" | |
| 19 | 19 | "github.com/ebitengine/purego/internal/strings" | |
| 20 | + | "github.com/ebitengine/purego/internal/xreflect" | |
| 20 | 21 | ) | |
| 21 | 22 | ||
| 22 | 23 | // TODO: support try/catch? | |
| @@ -341,7 +342,10 @@ func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars [ | |||
| 341 | 342 | // })(unsafe.Pointer(args[0].Interface().(ID)))).v = 123 | |
| 342 | 343 | // | |
| 343 | 344 | // However, since the type of the variable is unknown reflection is used to actually assign the value | |
| 344 | - | id := args[0].Interface().(ID) | |
| 345 | + | id, ok := xreflect.TypeAssert[ID](args[0]) | |
| 346 | + | if !ok { | |
| 347 | + | panic(fmt.Sprintf("objc: id argument is not a ID but %s", args[0].Type().String())) | |
| 348 | + | } | |
| 345 | 349 | ptr := *(*unsafe.Pointer)(unsafe.Pointer(&id)) // circumvent go vet | |
| 346 | 350 | reflect.NewAt(ivar.Type, unsafe.Add(ptr, offset)).Elem().Set(args[2]) | |
| 347 | 351 | return nil | |
| @@ -366,7 +370,10 @@ func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars [ | |||
| 366 | 370 | if len(args) != 2 { | |
| 367 | 371 | panic(fmt.Sprintf("objc: incorrect number of args. expected 2 got %d", len(args))) | |
| 368 | 372 | } | |
| 369 | - | id := args[0].Interface().(ID) | |
| 373 | + | id, ok := xreflect.TypeAssert[ID](args[0]) | |
| 374 | + | if !ok { | |
| 375 | + | panic(fmt.Sprintf("objc: id argument is not a ID but %s", args[0].Type().String())) | |
| 376 | + | } | |
| 370 | 377 | ptr := *(*unsafe.Pointer)(unsafe.Pointer(&id)) // circumvent go vet | |
| 371 | 378 | // the variable is located at an offset from the id | |
| 372 | 379 | return []reflect.Value{reflect.NewAt(ivar.Type, unsafe.Add(ptr, offset)).Elem()} | |
| @@ -17,6 +17,7 @@ import ( | |||||
| 17 | 17 | ||||
| 18 | "github.com/ebitengine/purego" | 18 | "github.com/ebitengine/purego" | ||
| 19 | "github.com/ebitengine/purego/internal/strings" | 19 | "github.com/ebitengine/purego/internal/strings" | ||
| 20 | + | "github.com/ebitengine/purego/internal/xreflect" | |||
| 20 | ) | 21 | ) | ||
| 21 | 22 | ||||
| 22 | // TODO: support try/catch? | 23 | // TODO: support try/catch? | ||
| @@ -341,7 +342,10 @@ func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars [ | |||||
| 341 | // })(unsafe.Pointer(args[0].Interface().(ID)))).v = 123 | 342 | // })(unsafe.Pointer(args[0].Interface().(ID)))).v = 123 | ||
| 342 | // | 343 | // | ||
| 343 | // However, since the type of the variable is unknown reflection is used to actually assign the value | 344 | // However, since the type of the variable is unknown reflection is used to actually assign the value | ||
| 344 | - | id := args[0].Interface().(ID) | 345 | + | id, ok := xreflect.TypeAssert[ID](args[0]) |
| 346 | + | if !ok { | |||
| 347 | + | panic(fmt.Sprintf("objc: id argument is not a ID but %s", args[0].Type().String())) | |||
| 348 | + | } | |||
| 345 | ptr := *(*unsafe.Pointer)(unsafe.Pointer(&id)) // circumvent go vet | 349 | ptr := *(*unsafe.Pointer)(unsafe.Pointer(&id)) // circumvent go vet | ||
| 346 | reflect.NewAt(ivar.Type, unsafe.Add(ptr, offset)).Elem().Set(args[2]) | 350 | reflect.NewAt(ivar.Type, unsafe.Add(ptr, offset)).Elem().Set(args[2]) | ||
| 347 | return nil | 351 | return nil | ||
| @@ -366,7 +370,10 @@ func RegisterClass(name string, superClass Class, protocols []*Protocol, ivars [ | |||||
| 366 | if len(args) != 2 { | 370 | if len(args) != 2 { | ||
| 367 | panic(fmt.Sprintf("objc: incorrect number of args. expected 2 got %d", len(args))) | 371 | panic(fmt.Sprintf("objc: incorrect number of args. expected 2 got %d", len(args))) | ||
| 368 | } | 372 | } | ||
| 369 | - | id := args[0].Interface().(ID) | 373 | + | id, ok := xreflect.TypeAssert[ID](args[0]) |
| 374 | + | if !ok { | |||
| 375 | + | panic(fmt.Sprintf("objc: id argument is not a ID but %s", args[0].Type().String())) | |||
| 376 | + | } | |||
| 370 | ptr := *(*unsafe.Pointer)(unsafe.Pointer(&id)) // circumvent go vet | 377 | ptr := *(*unsafe.Pointer)(unsafe.Pointer(&id)) // circumvent go vet | ||
| 371 | // the variable is located at an offset from the id | 378 | // the variable is located at an offset from the id | ||
| 372 | return []reflect.Value{reflect.NewAt(ivar.Type, unsafe.Add(ptr, offset)).Elem()} | 379 | return []reflect.Value{reflect.NewAt(ivar.Type, unsafe.Add(ptr, offset)).Elem()} | ||