1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2025 The Ebitengine Authors
3
4package objc_test
5
6import (
7 "fmt"
8 "testing"
9
10 "github.com/ebitengine/purego"
11 "github.com/ebitengine/purego/objc"
12)
13
14func ExampleNewBlock() {
15 _, err := purego.Dlopen("/System/Library/Frameworks/Foundation.framework/Foundation", purego.RTLD_GLOBAL|purego.RTLD_NOW)
16 if err != nil {
17 panic(err)
18 }
19
20 var count = 0
21 block := objc.NewBlock(
22 func(block objc.Block, line objc.ID, stop *bool) {
23 count++
24 fmt.Printf("LINE %d: %s\n", count, objc.Send[string](line, objc.RegisterName("UTF8String")))
25 *stop = count == 3
26 },
27 )
28 defer block.Release()
29
30 lines := objc.ID(objc.GetClass("NSString")).Send(objc.RegisterName("stringWithUTF8String:"), "Alpha\nBeta\nGamma\nDelta\nEpsilon")
31 defer lines.Send(objc.RegisterName("release"))
32
33 lines.Send(objc.RegisterName("enumerateLinesUsingBlock:"), block)
34 // Output:
35 // LINE 1: Alpha
36 // LINE 2: Beta
37 // LINE 3: Gamma
38}
39
40func ExampleInvokeBlock() {
41 type vector struct {
42 X, Y, Z float64
43 }
44
45 block := objc.NewBlock(
46 func(block objc.Block, v1, v2 *vector) *vector {
47 return &vector{
48 X: v1.Y*v2.Z - v1.Z*v2.Y,
49 Y: v1.Z*v2.X - v1.X*v2.Z,
50 Z: v1.X*v2.Y - v1.Y*v2.X,
51 }
52 },
53 )
54 defer block.Release()
55
56 result, err := objc.InvokeBlock[*vector](
57 block,
58 &vector{X: 0.1, Y: 2.3, Z: 4.5},
59 &vector{X: 6.7, Y: 8.9, Z: 0.1},
60 )
61
62 fmt.Printf("{%.2f %.2f %.2f} %v\n", result.X, result.Y, result.Z, err)
63 // Output: {-39.82 30.14 -14.52} <nil>
64}
65
66func TestInvoke(t *testing.T) {
67 t.Run("return an error when passing an invalid number of arguments", func(t *testing.T) {
68 block := objc.NewBlock(func(_ objc.Block, a int32, b int32) int32 {
69 return a + b
70 })
71 defer block.Release()
72
73 if _, err := objc.InvokeBlock[int32](block, int32(8)); err == nil {
74 t.Fatal(err)
75 }
76 })
77
78 t.Run("return an error when passing an invalid return type", func(t *testing.T) {
79 block := objc.NewBlock(func(_ objc.Block, a int32, b int32) int32 {
80 return a + b
81 })
82 defer block.Release()
83
84 if _, err := objc.InvokeBlock[string](block, int32(8), int32(2)); err == nil {
85 t.Fatal(err)
86 }
87 })
88
89 t.Run("add two int32's and returns the result", func(t *testing.T) {
90 block := objc.NewBlock(func(_ objc.Block, a int32, b int32) int32 {
91 return a + b
92 })
93 defer block.Release()
94
95 result, err := objc.InvokeBlock[int32](block, int32(8), int32(2))
96 if err != nil {
97 t.Fatal(err)
98 }
99 if result != 10 {
100 t.Fatalf("expected 10, got %d", result)
101 }
102 })
103
104 t.Run("add two int32's and store the result in a variable", func(t *testing.T) {
105 var result int32
106 block := objc.NewBlock(func(_ objc.Block, a int32, b int32) {
107 result = a + b
108 })
109 defer block.Release()
110
111 block.Invoke(int32(8), int32(2))
112 if result != 10 {
113 t.Fatalf("expected 10, got %d", result)
114 }
115 })
116}
117
118func TestBlockCopyAndBlockRelease(t *testing.T) {
119 t.Parallel()
120
121 var refCount int
122 block := objc.NewBlock(
123 func(objc.Block) {
124 refCount++
125 },
126 )
127 defer block.Release()
128 refCount++
129
130 copies := make([]objc.Block, 17)
131 copies[0] = block
132 for index := 1; index < len(copies); index++ {
133 if refCount != index {
134 t.Fatalf("refCount: %d != %d", refCount, index)
135 }
136
137 copies[index] = copies[index-1].Copy()
138 if copies[index] != block {
139 t.Fatalf("Block.Copy(): %v != %v", copies[index], block)
140 }
141 copies[index].Invoke()
142 }
143
144 for _, copy := range copies[1:] {
145 copy.Release()
146 refCount--
147 }
148 refCount--
149
150 block.Invoke()
151 if refCount != 1 {
152 t.Fatalf("refCount: %d != 1", refCount)
153 }
154}