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