v0.12.0-alpha 7e3b362

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	"structs"
9	"testing"
10
11	"github.com/ebitengine/purego"
12	"github.com/ebitengine/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}

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