Skip to content

Commit e00cd78

Browse files
committed
Concurrency and host architecture refactoring
Key highlights: - Singleton Hosts (`#default` instead of `#new`): Standalone hosts (`BlHeadlessHost`, `BlOSWindowSDL2Host`, `BlOSSDL3Host`, `BlMorphicWindowHost`) are now singletons accessed via `#default`. Sending `#new` now raises an explicit error (`'Use #default instead'`). This directly affects existing scripts that previously instantiated hosts with `#new` (e.g. `aSpace host: BlHeadlessHost default`). - Instance-Side BlHost API & `BlHostRegistry default`: Replaced static class-side host APIs with instance-side host objects managing their own universe, pulse loop, and spaces. - Converts Morphic hosts to run in a separate pulse loop process (like SDL2 and SDL3) and adds double-buffering. - Introduce `BlEventSimulator` based on class-side methods of `BlSpace`. - Removal of `BlMockedHost` (replaced by `BlHeadlessHost default`): Removed `BlMockedHost` and package `BlocHost-Mock`. Rather than expecting manual, explicit `#pulse` calls from tests, headless testing now runs on a pulse process using `BlHeadlessHost default`, matching how normal hosts operate. Any external script, test, or baseline referencing `BlMockedHost` should be updated to `BlHeadlessHost default`. - Event-driven pulse synchronization in `BlParallelUniverse`. - Adds synchronization methods in `BlSpace` (`#deferAndSettle:`, `#defer:`, `#deferOrValue:`, `#settle`, `#isSettled`). - Moves `eventQueue` from `BlHostSpace` to `BlSpace`, and clears pending events when a window closes. - Uses `BlHost` as a facade to encapsulate `universe` (`#spaces`, `#closeSpaces`). - Adds `#denyPulseProcess` to guard against blocking calls from inside the pulse process.
1 parent 23fe916 commit e00cd78

129 files changed

Lines changed: 4384 additions & 2753 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/BaselineOfBloc/BaselineOfBloc.class.st

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ BaselineOfBloc >> baseline: spec [
2929
package: 'Bloc'
3030
with: [ spec requires: #('Bloc-Display') ].
3131

32+
spec
33+
package: 'Bloc-Simulation'
34+
with: [ spec requires: #('Bloc') ].
35+
3236
spec
3337
package: 'Bloc-Animation'
3438
with: [ spec requires: #('Bloc') ].
@@ -120,7 +124,7 @@ BaselineOfBloc >> baseline: spec [
120124

121125
spec
122126
package: 'Bloc-Layout-Examples'
123-
with: [ spec requires: #('Bloc' 'Bloc-Layout' 'Bloc-Text-Elements') ].
127+
with: [ spec requires: #('Bloc' 'Bloc-Layout' 'Bloc-Tests' 'Bloc-Text-Elements') ].
124128

125129
spec
126130
package: 'Bloc-SVG'
@@ -147,13 +151,9 @@ BaselineOfBloc >> baseline: spec [
147151
package: 'Bloc-Text-Tests'
148152
with: [ spec requires: #('Bloc' 'Bloc-Text') ].
149153

150-
spec
151-
package: 'BlocHost-Mock'
152-
with: [ spec requires: #('Bloc') ].
153-
154154
spec
155155
package: 'Bloc-Scripter'
156-
with: [ spec requires: #('Bloc' 'Bloc-Layout' 'BlocHost-Mock') ].
156+
with: [ spec requires: #('Bloc' 'Bloc-Simulation' 'Bloc-Layout') ].
157157

158158
spec
159159
package: 'BlocHost-Morphic'
@@ -206,8 +206,8 @@ BaselineOfBloc >> baseline: spec [
206206
package: 'Bloc-Tests'
207207
with: [ spec requires: #(
208208
'Bloc'
209+
'Bloc-Simulation'
209210
'Bloc-Text-Elements'
210-
'BlocHost-Mock'
211211
'BlocHost-Morphic'
212212
'BlocHost-OSWindow-SDL2') ].
213213

src/Bloc-Alexandrie-Exporter/BAExporter.class.st

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,13 @@ BAExporter >> element: aBlElement [
6767
BAExporter >> export [
6868

6969
| aCanvas aBounds |
70-
element isLayoutRequested ifTrue: [ element forceLayout ].
70+
element isLayoutRequested ifTrue: [
71+
element isAttachedToSceneGraph
72+
ifTrue: [ element space settle ]
73+
ifFalse: [ element forceLayout ] ].
7174

7275
aBounds := element invalidationBoundsInParent.
73-
7476
aCanvas := self newCanvas: aBounds extent * scale.
75-
7677
aCanvas
7778
clear: self background;
7879
pathScale: scale asPoint;
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
Class {
2+
#name : #BAAbstractExporterTest,
3+
#superclass : #TestCase,
4+
#instVars : [
5+
'testDirectory',
6+
'element'
7+
],
8+
#category : #'Bloc-Alexandrie-Tests-Exporter'
9+
}
10+
11+
{ #category : #testing }
12+
BAAbstractExporterTest class >> isAbstract [
13+
14+
^ self == BAAbstractExporterTest
15+
]
16+
17+
{ #category : #running }
18+
BAAbstractExporterTest >> newContainerWithChild [
19+
20+
| child container |
21+
child := BlElement new
22+
extent: 40 @ 20;
23+
background: Color blue;
24+
yourself.
25+
container := BlElement new
26+
extent: 100 @ 50;
27+
background: Color transparent;
28+
addChild: child;
29+
yourself.
30+
^ container
31+
]
32+
33+
{ #category : #running }
34+
BAAbstractExporterTest >> newTestElement [
35+
36+
^ BlElement new
37+
extent: 100 @ 50;
38+
background: Color red;
39+
yourself
40+
]
41+
42+
{ #category : #running }
43+
BAAbstractExporterTest >> prepareElementToExport: anElement [
44+
45+
^ anElement
46+
]
47+
48+
{ #category : #running }
49+
BAAbstractExporterTest >> setUp [
50+
51+
super setUp.
52+
testDirectory := (FileLocator temp / ('bloc-export-tests-' , UUID new asString36)) ensureCreateDirectory.
53+
element := self newTestElement
54+
]
55+
56+
{ #category : #running }
57+
BAAbstractExporterTest >> tearDown [
58+
59+
testDirectory ifNotNil: [
60+
testDirectory ensureDeleteAll.
61+
testDirectory := nil ].
62+
super tearDown
63+
]
64+
65+
{ #category : #tests }
66+
BAAbstractExporterTest >> testBARendererWriteAsPngTo [
67+
68+
| targetFile |
69+
targetFile := testDirectory / 'renderer-write.png'.
70+
BARenderer write: element asPngTo: targetFile.
71+
self assert: targetFile exists.
72+
self assert: targetFile size > 0
73+
]
74+
75+
{ #category : #tests }
76+
BAAbstractExporterTest >> testExportAsForm [
77+
78+
| form |
79+
form := element exportAsForm.
80+
self assert: (form isKindOf: Form).
81+
self assert: form extent equals: 100 @ 50.
82+
self assert: form depth equals: 32.
83+
self assert: (form colorAt: 50 @ 25) equals: Color red
84+
]
85+
86+
{ #category : #tests }
87+
BAAbstractExporterTest >> testExportAsFormWithCustomBackground [
88+
89+
| container form |
90+
container := self prepareElementToExport: self newContainerWithChild.
91+
form := BAExporter form
92+
element: container;
93+
background: Color yellow;
94+
export.
95+
self assert: (form colorAt: 20 @ 10) equals: Color blue.
96+
self assert: (form colorAt: 80 @ 40) equals: Color yellow
97+
]
98+
99+
{ #category : #tests }
100+
BAAbstractExporterTest >> testExportAsFormWithScale [
101+
102+
| form |
103+
form := BAExporter form
104+
element: element;
105+
scale: 2.0;
106+
export.
107+
self assert: form extent equals: 200 @ 100
108+
]
109+
110+
{ #category : #tests }
111+
BAAbstractExporterTest >> testExportAsJPEG [
112+
113+
| targetFile result readForm |
114+
targetFile := testDirectory / 'test.jpg'.
115+
result := BAExporter jpg
116+
element: element;
117+
target: targetFile;
118+
export.
119+
self assert: result equals: targetFile.
120+
self assert: targetFile exists.
121+
self assert: targetFile size > 0.
122+
self assert: (targetFile binaryReadStreamDo: [ :stream | stream next: 3 ]) equals: #[16rFF 16rD8 16rFF].
123+
readForm := ImageReadWriter formFromFileNamed: targetFile fullName.
124+
self assert: readForm extent equals: 100 @ 50
125+
]
126+
127+
{ #category : #tests }
128+
BAAbstractExporterTest >> testExportAsJPEGQualityAndProgressive [
129+
130+
| targetFile |
131+
targetFile := testDirectory / 'quality.jpg'.
132+
BAExporter jpg
133+
element: element;
134+
target: targetFile;
135+
quality: 50;
136+
isProgressive: false;
137+
export.
138+
self assert: targetFile exists.
139+
self assert: targetFile size > 0
140+
]
141+
142+
{ #category : #tests }
143+
BAAbstractExporterTest >> testExportAsPDF [
144+
145+
| targetFile result header |
146+
targetFile := testDirectory / 'test.pdf'.
147+
result := BAExporter pdf
148+
element: element;
149+
target: targetFile;
150+
export.
151+
self assert: result equals: targetFile.
152+
self assert: targetFile exists.
153+
self assert: targetFile size > 0.
154+
header := targetFile binaryReadStreamDo: [ :stream | (stream next: 4) asString ].
155+
self assert: header equals: '%PDF'
156+
]
157+
158+
{ #category : #tests }
159+
BAAbstractExporterTest >> testExportAsPNG [
160+
161+
| targetFile result readForm |
162+
targetFile := testDirectory / 'test.png'.
163+
result := BAExporter png
164+
element: element;
165+
target: targetFile;
166+
export.
167+
self assert: result equals: targetFile.
168+
self assert: targetFile exists.
169+
self assert: targetFile size > 0.
170+
self assert: (targetFile binaryReadStreamDo: [ :stream | stream next: 4 ]) equals: #[16r89 16r50 16r4E 16r47].
171+
readForm := ImageReadWriter formFromFileNamed: targetFile fullName.
172+
self assert: readForm extent equals: 100 @ 50
173+
]
174+
175+
{ #category : #tests }
176+
BAAbstractExporterTest >> testExportAsSVG [
177+
178+
| targetFile result contents |
179+
targetFile := testDirectory / 'test.svg'.
180+
result := BAExporter svg
181+
element: element;
182+
target: targetFile;
183+
export.
184+
self assert: result equals: targetFile.
185+
self assert: targetFile exists.
186+
self assert: targetFile size > 0.
187+
contents := targetFile readStreamDo: [ :stream | stream contents ].
188+
self assert: (contents includesSubstring: '<svg')
189+
]
190+
191+
{ #category : #tests }
192+
BAAbstractExporterTest >> testExportFromForkedProcess [
193+
194+
| sem form |
195+
sem := Semaphore new.
196+
[ form := element exportAsForm. sem signal ] fork.
197+
sem waitTimeoutMilliseconds: 2000.
198+
self assert: form isNotNil.
199+
self assert: form extent equals: 100 @ 50
200+
]
201+
202+
{ #category : #tests }
203+
BAAbstractExporterTest >> testExportMultipleConcurrentForkedProcesses [
204+
205+
| forms mutex sem count |
206+
forms := OrderedCollection new.
207+
mutex := Mutex new.
208+
sem := Semaphore new.
209+
count := 5.
210+
1 to: count do: [ :i |
211+
[ | f |
212+
f := (BlElement new
213+
extent: (30 * i) @ (20 * i);
214+
background: Color blue) exportAsForm.
215+
mutex critical: [ forms add: f ].
216+
sem signal ] fork ].
217+
count timesRepeat: [ sem waitTimeoutMilliseconds: 3000 ].
218+
self assert: forms size equals: count
219+
]
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
Class {
2+
#name : #BAAttachedExporterTest,
3+
#superclass : #BAAbstractExporterTest,
4+
#instVars : [
5+
'space'
6+
],
7+
#category : #'Bloc-Alexandrie-Tests-Exporter'
8+
}
9+
10+
{ #category : #running }
11+
BAAttachedExporterTest >> prepareElementToExport: anElement [
12+
13+
space root addChild: anElement.
14+
space settle.
15+
^ anElement
16+
]
17+
18+
{ #category : #running }
19+
BAAttachedExporterTest >> setUp [
20+
21+
super setUp.
22+
space := BlSpace new.
23+
space host: BlHeadlessHost default.
24+
space root addChild: element.
25+
space show; settle
26+
]
27+
28+
{ #category : #running }
29+
BAAttachedExporterTest >> tearDown [
30+
31+
space ifNotNil: [
32+
space close; settle.
33+
space := nil ].
34+
super tearDown
35+
]
36+
37+
{ #category : #tests }
38+
BAAttachedExporterTest >> testAeAsFormFromHostPulseProcess [
39+
40+
| form |
41+
space deferAndSettle: [ form := element aeAsForm ].
42+
self assert: form isNotNil.
43+
self assert: form extent equals: 100 @ 50
44+
]
45+
46+
{ #category : #tests }
47+
BAAttachedExporterTest >> testAeAsFormWithPendingLayoutFromHostPulseProcessErrors [
48+
49+
| raisedError |
50+
raisedError := false.
51+
space deferAndSettle: [
52+
element requestLayout.
53+
self should: [ element aeAsForm ] raise: Error.
54+
raisedError := true ].
55+
self assert: raisedError
56+
]
57+
58+
{ #category : #tests }
59+
BAAttachedExporterTest >> testExportFromHostPulseProcess [
60+
61+
| form |
62+
space deferAndSettle: [ form := element exportAsForm ].
63+
self assert: form isNotNil.
64+
self assert: form extent equals: 100 @ 50
65+
]
66+
67+
{ #category : #tests }
68+
BAAttachedExporterTest >> testExportWithPendingLayoutFromHostPulseProcessErrors [
69+
70+
| raisedError |
71+
raisedError := false.
72+
space deferAndSettle: [
73+
element requestLayout.
74+
self should: [ element exportAsForm ] raise: Error.
75+
raisedError := true ].
76+
self assert: raisedError
77+
]

0 commit comments

Comments
 (0)