updated ebiten version from 2.7.9 to 2.9.9
This commit is contained in:
+267
@@ -0,0 +1,267 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-FileCopyrightText: 2025 The Ebitengine Authors
|
||||
|
||||
package objc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/ebitengine/purego"
|
||||
)
|
||||
|
||||
const (
|
||||
// The end-goal of these defaults is to get an Objective-C memory-managed block object
|
||||
// that won't try to free() a Go pointer, but will call our custom blockFunctionCache.Delete()
|
||||
// when the reference count drops to zero, so the associated function is also unreferenced.
|
||||
|
||||
// blockBaseClass is the name of the class that block objects will be initialized with.
|
||||
blockBaseClass = "__NSMallocBlock__"
|
||||
// blockFlags is the set of flags that block objects will be initialized with.
|
||||
blockFlags = blockHasCopyDispose | blockHasSignature
|
||||
|
||||
// blockHasCopyDispose is a flag that tells the Objective-C runtime the block exports Copy and/or Dispose helpers.
|
||||
blockHasCopyDispose = 1 << 25
|
||||
|
||||
// blockHasSignature is a flag that tells the Objective-C runtime the block exports a function signature.
|
||||
blockHasSignature = 1 << 30
|
||||
)
|
||||
|
||||
// blockDescriptor is the Go representation of an Objective-C block descriptor.
|
||||
// It is a component to be referenced by blockDescriptor.
|
||||
//
|
||||
// The layout of this struct matches Block_literal_1 described in https://clang.llvm.org/docs/Block-ABI-Apple.html#high-level
|
||||
type blockDescriptor struct {
|
||||
_ uintptr
|
||||
size uintptr
|
||||
_ uintptr
|
||||
dispose uintptr
|
||||
signature *uint8
|
||||
}
|
||||
|
||||
// blockLayout is the Go representation of the structure abstracted by a block pointer.
|
||||
// From the Objective-C point of view, a pointer to this struct is equivalent to an ID that
|
||||
// references a block.
|
||||
//
|
||||
// The layout of this struct matches __block_literal_1 described in https://clang.llvm.org/docs/Block-ABI-Apple.html#high-level
|
||||
type blockLayout struct {
|
||||
isa Class
|
||||
flags uint32
|
||||
_ uint32
|
||||
invoke uintptr
|
||||
descriptor *blockDescriptor
|
||||
}
|
||||
|
||||
// blockFunctionCache is a thread safe cache of block layouts.
|
||||
//
|
||||
// The function closures themselves are kept alive by caching them internally until the Objective-C runtime indicates that
|
||||
// they can be released (presumably when the reference count reaches zero). This approach is used instead of appending the function
|
||||
// object to the block allocation, where it is out of the visible domain of Go's GC.
|
||||
type blockFunctionCache struct {
|
||||
mutex sync.RWMutex
|
||||
functions map[Block]reflect.Value
|
||||
}
|
||||
|
||||
// Load retrieves a function (in the form of a reflect.Value, so Call can be invoked) associated with the key Block.
|
||||
func (b *blockFunctionCache) Load(key Block) reflect.Value {
|
||||
b.mutex.RLock()
|
||||
defer b.mutex.RUnlock()
|
||||
return b.functions[key]
|
||||
}
|
||||
|
||||
// Store associates a function (in the form of a reflect.Value) with the key Block.
|
||||
func (b *blockFunctionCache) Store(key Block, value reflect.Value) Block {
|
||||
b.mutex.Lock()
|
||||
defer b.mutex.Unlock()
|
||||
b.functions[key] = value
|
||||
return key
|
||||
}
|
||||
|
||||
// Delete removed the function associated with the key Block.
|
||||
func (b *blockFunctionCache) Delete(key Block) {
|
||||
b.mutex.Lock()
|
||||
defer b.mutex.Unlock()
|
||||
delete(b.functions, key)
|
||||
}
|
||||
|
||||
// newBlockFunctionCache initializes a new blockFunctionCache
|
||||
func newBlockFunctionCache() *blockFunctionCache {
|
||||
return &blockFunctionCache{functions: map[Block]reflect.Value{}}
|
||||
}
|
||||
|
||||
// blockCache is a thread safe cache of block layouts.
|
||||
//
|
||||
// It takes advantage of the block being the first argument of a block call being the block closure,
|
||||
// only invoking [github.com/ebitengine/purego.NewCallback] when it encounters a new function type (rather than on for every block creation).
|
||||
// This should mitigate block creations putting pressure on the callback limit.
|
||||
type blockCache struct {
|
||||
sync.Mutex
|
||||
descriptorTemplate blockDescriptor
|
||||
layoutTemplate blockLayout
|
||||
layouts map[reflect.Type]blockLayout
|
||||
Functions *blockFunctionCache
|
||||
}
|
||||
|
||||
// encode returns a blocks type as if it was given to @encode(typ)
|
||||
func (*blockCache) encode(typ reflect.Type) *uint8 {
|
||||
// this algorithm was copied from encodeFunc,
|
||||
// but altered to panic on error, and to only accept a block-type signature.
|
||||
if typ == nil || typ.Kind() != reflect.Func {
|
||||
panic("objc: not a function")
|
||||
}
|
||||
|
||||
var encoding string
|
||||
switch typ.NumOut() {
|
||||
case 0:
|
||||
encoding = encVoid
|
||||
default:
|
||||
returnType, err := encodeType(typ.Out(0), false)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("objc: %v", err))
|
||||
}
|
||||
encoding = returnType
|
||||
}
|
||||
|
||||
if typ.NumIn() == 0 || typ.In(0) != reflect.TypeOf(Block(0)) {
|
||||
panic(fmt.Sprintf("objc: A Block implementation must take a Block as its first argument; got %v", typ.String()))
|
||||
}
|
||||
|
||||
encoding += encId
|
||||
for i := 1; i < typ.NumIn(); i++ {
|
||||
argType, err := encodeType(typ.In(i), false)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("objc: %v", err))
|
||||
}
|
||||
encoding = fmt.Sprint(encoding, argType)
|
||||
}
|
||||
|
||||
// return the encoding as a C-style string.
|
||||
return &append([]uint8(encoding), 0)[0]
|
||||
}
|
||||
|
||||
// getLayout retrieves a blockLayout VALUE constructed with the supplied function type.
|
||||
// It will panic if the type is not a valid block function.
|
||||
func (b *blockCache) getLayout(typ reflect.Type) blockLayout {
|
||||
b.Lock()
|
||||
defer b.Unlock()
|
||||
|
||||
// return the cached layout, if it exists.
|
||||
if layout, ok := b.layouts[typ]; ok {
|
||||
return layout
|
||||
}
|
||||
|
||||
// otherwise: create a layout, and populate it with the default templates
|
||||
layout := b.layoutTemplate
|
||||
layout.descriptor = &blockDescriptor{}
|
||||
*layout.descriptor = b.descriptorTemplate
|
||||
|
||||
// getting the signature now will panic on invalid types before we invest in creating a callback.
|
||||
layout.descriptor.signature = b.encode(typ)
|
||||
|
||||
// create a global callback.
|
||||
// this single callback can dispatch to any function with the same signature,
|
||||
// since the user-provided functions are associated with the actual block allocations.
|
||||
layout.invoke = purego.NewCallback(
|
||||
reflect.MakeFunc(
|
||||
typ,
|
||||
func(args []reflect.Value) (results []reflect.Value) {
|
||||
return b.Functions.Load(args[0].Interface().(Block)).Call(args)
|
||||
},
|
||||
).Interface(),
|
||||
)
|
||||
|
||||
// store it and return it
|
||||
b.layouts[typ] = layout
|
||||
return layout
|
||||
}
|
||||
|
||||
// newBlockCache initializes a block cache.
|
||||
// It should not be called until AFTER libobjc is fully initialized.
|
||||
func newBlockCache() *blockCache {
|
||||
cache := &blockCache{
|
||||
descriptorTemplate: blockDescriptor{
|
||||
size: unsafe.Sizeof(blockLayout{}),
|
||||
},
|
||||
layoutTemplate: blockLayout{
|
||||
isa: GetClass(blockBaseClass),
|
||||
flags: blockFlags,
|
||||
},
|
||||
layouts: map[reflect.Type]blockLayout{},
|
||||
Functions: newBlockFunctionCache(),
|
||||
}
|
||||
cache.descriptorTemplate.dispose = purego.NewCallback(cache.Functions.Delete)
|
||||
return cache
|
||||
}
|
||||
|
||||
// theBlocksCache is the global block cache
|
||||
var theBlocksCache *blockCache
|
||||
|
||||
// Block is an opaque pointer to an Objective-C object containing a function with its associated closure.
|
||||
type Block ID
|
||||
|
||||
// Copy creates a copy of a block on the Objective-C heap (or increments the reference count if already on the heap).
|
||||
// Use [Block.Release] to free the copy when it is no longer in use.
|
||||
func (b Block) Copy() Block {
|
||||
return _Block_copy(b)
|
||||
}
|
||||
|
||||
// Invoke calls the implementation of a block.
|
||||
func (b Block) Invoke(args ...any) {
|
||||
fn := theBlocksCache.Functions.Load(b)
|
||||
|
||||
reflectedArgs := make([]reflect.Value, len(args)+1)
|
||||
reflectedArgs[0] = reflect.ValueOf(b)
|
||||
for i := range args {
|
||||
reflectedArgs[i+1] = reflect.ValueOf(args[i])
|
||||
}
|
||||
|
||||
fn.Call(reflectedArgs)
|
||||
}
|
||||
|
||||
// Release decrements the Block's reference count, and if it is the last reference, frees it.
|
||||
func (b Block) Release() {
|
||||
_Block_release(b)
|
||||
}
|
||||
|
||||
// NewBlock takes a Go function that takes a Block as its first argument.
|
||||
// It returns an Block that can be called by Objective-C code.
|
||||
// The function panics if an error occurs.
|
||||
// Use [Block.Release] to free this block when it is no longer in use.
|
||||
func NewBlock(fn any) Block {
|
||||
// get or create a block layout for the callback.
|
||||
layout := theBlocksCache.getLayout(reflect.TypeOf(fn))
|
||||
// we created the layout in Go memory, so we'll copy it to a newly-created Objective-C object.
|
||||
block := Block(unsafe.Pointer(&layout)).Copy()
|
||||
// associate the fn with the block we created before returning it.
|
||||
return theBlocksCache.Functions.Store(block, reflect.ValueOf(fn))
|
||||
}
|
||||
|
||||
// InvokeBlock is a convenience method for calling the implementation of a block.
|
||||
// The block implementation must return 1 value.
|
||||
func InvokeBlock[T any](block Block, args ...any) (result T, err error) {
|
||||
block = block.Copy()
|
||||
defer block.Release()
|
||||
|
||||
fn := theBlocksCache.Functions.Load(block)
|
||||
if fn.Type().NumIn() != len(args)+1 {
|
||||
return result, fmt.Errorf("objc: block callback expects %d arguments, got %d", fn.Type().NumIn()-1, len(args))
|
||||
}
|
||||
|
||||
reflectedArgs := make([]reflect.Value, len(args)+1)
|
||||
reflectedArgs[0] = reflect.ValueOf(block)
|
||||
for i := range args {
|
||||
reflectedArgs[i+1] = reflect.ValueOf(args[i])
|
||||
}
|
||||
|
||||
callResult := fn.Call(reflectedArgs)
|
||||
|
||||
var ok bool
|
||||
result, ok = callResult[0].Interface().(T)
|
||||
if !ok {
|
||||
return result, fmt.Errorf("objc: the returned value type %s was not %T", callResult[0].Type().String(), result)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
Reference in New Issue
Block a user