v0.11.0-alpha.2 fa49e74

objc_block_darwin_test.go RawHistoryFolder

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}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close