Skip to content

Commit bc98a7e

Browse files
authored
added @Environment system (#10)
* added `@Environment` system * added CI * mutex build issue? * what is going on? * da fuque? * test do be parallel
1 parent 59d1fdf commit bc98a7e

File tree

27 files changed

+657
-141
lines changed

27 files changed

+657
-141
lines changed

.github/workflows/ci-embedded.yaml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Build Embedded Wasm
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- "**.swift"
8+
- "**.yml"
9+
pull_request:
10+
branches: [main]
11+
workflow_dispatch:
12+
13+
jobs:
14+
linux:
15+
runs-on: ubuntu-latest
16+
timeout-minutes: 15
17+
strategy:
18+
matrix:
19+
image: ["swiftlang/swift:nightly-main"]
20+
21+
container:
22+
image: ${{ matrix.image }}
23+
steps:
24+
- name: Checkout
25+
uses: actions/checkout@v4
26+
27+
- name: Build
28+
run: |
29+
JAVASCRIPTKIT_EXPERIMENTAL_EMBEDDED_WASM=true \
30+
swift build --triple wasm32-unknown-none-wasm

.github/workflows/ci.yaml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- "**.swift"
8+
- "**.yml"
9+
pull_request:
10+
branches: [main]
11+
workflow_dispatch:
12+
13+
jobs:
14+
linux:
15+
runs-on: ubuntu-latest
16+
timeout-minutes: 15
17+
strategy:
18+
matrix:
19+
image: ["swift:6.0.1"]
20+
21+
container:
22+
image: ${{ matrix.image }}
23+
steps:
24+
- name: Checkout
25+
uses: actions/checkout@v4
26+
27+
- name: Build
28+
run: swift build --build-tests
29+
30+
- name: Run Tests
31+
run: swift test

Examples/Basic/Sources/EmbeddedApp/Views.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import ElementaryDOM
22

3+
extension EnvironmentValues {
4+
@Entry var myText = ""
5+
}
6+
37
@View
48
struct App {
59
@State var counters: [Int] = [1]
610
@State var nextCounterName = 1
11+
@State var data = SomeData()
712

813
var content: some View {
914
for (index, counter) in counters.enumerated() {
@@ -21,6 +26,12 @@ struct App {
2126
nextCounterName += 1
2227
counters.append(nextCounterName)
2328
}
29+
hr()
30+
TextField(value: Binding(get: { data.name }, set: { data.name = $0 }))
31+
div {
32+
p { "Via Binding: \(data.name)" }
33+
p { TestView() }
34+
}.environment(#Key(\.myText), data.name)
2435
}
2536
}
2637

@@ -44,3 +55,30 @@ struct Counter {
4455
}
4556
}
4657
}
58+
59+
@View
60+
struct TextField {
61+
@Binding var value: String
62+
63+
var content: some View {
64+
input(.type(.text), .value("Hello"))
65+
.onInput { event in
66+
value = event.targetValue ?? ""
67+
}
68+
}
69+
}
70+
71+
@Reactive
72+
final class SomeData {
73+
var name: String = ""
74+
var age: Int = 0
75+
}
76+
77+
@View
78+
struct TestView {
79+
@Environment(#Key(\.myText)) var key
80+
81+
var content: some View {
82+
span { "Via Environment: \(key)" }
83+
}
84+
}

Examples/Swiftle/Sources/EmbeddedApp/Views.swift

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ struct GameView {
88
game.handleKey(key)
99
}
1010

11-
func onRestart() {
12-
game = Game()
13-
}
14-
1511
var content: some View {
1612
main(.class("flex flex-col gap-5 items-center h-screen bg-black text-white")) {
1713
div(.class("flex gap-4 items-center pt-5")) {
@@ -28,7 +24,7 @@ struct GameView {
2824

2925
div(.class("relative")) {
3026
KeyboardView(keyboard: game.keyboard, onKeyPressed: onKeyPressed)
31-
GameEndOverlay(game: game, onRestart: onRestart)
27+
GameEndOverlay(game: $game)
3228
}
3329

3430
footer {
@@ -50,13 +46,15 @@ struct GameView {
5046
}
5147
}
5248

53-
struct SwiftLogo: View {
49+
@View
50+
struct SwiftLogo {
5451
var content: some View {
5552
img(.src("swift-bird.svg"), .class("h-10"))
5653
}
5754
}
5855

59-
struct GuessView: View {
56+
@View
57+
struct GuessView {
6058
var guess: Guess
6159

6260
var content: some View {
@@ -68,7 +66,8 @@ struct GuessView: View {
6866
}
6967
}
7068

71-
struct LetterView: View {
69+
@View
70+
struct LetterView {
7271
var guess: LetterGuess?
7372

7473
var content: some View {
@@ -85,7 +84,8 @@ struct LetterView: View {
8584
}
8685
}
8786

88-
struct KeyboardView: View {
87+
@View
88+
struct KeyboardView {
8989
var keyboard: Keyboard
9090
var onKeyPressed: (EnteredKey) -> Void
9191

@@ -112,7 +112,8 @@ struct KeyboardView: View {
112112
}
113113
}
114114

115-
struct KeyboardLetterView: View {
115+
@View
116+
struct KeyboardLetterView {
116117
var guess: LetterGuess
117118
var onKeyPressed: (EnteredKey) -> Void
118119

@@ -133,7 +134,8 @@ struct KeyboardLetterView: View {
133134
}
134135
}
135136

136-
struct EnterKeyView: View {
137+
@View
138+
struct EnterKeyView {
137139
var onKeyPressed: (EnteredKey) -> Void
138140

139141
var content: some View {
@@ -148,7 +150,8 @@ struct EnterKeyView: View {
148150
}
149151
}
150152

151-
struct BackspaceKeyView: View {
153+
@View
154+
struct BackspaceKeyView {
152155
var onKeyPressed: (EnteredKey) -> Void
153156

154157
var content: some View {
@@ -163,9 +166,9 @@ struct BackspaceKeyView: View {
163166
}
164167
}
165168

166-
struct GameEndOverlay: View {
167-
var game: Game
168-
var onRestart: () -> Void
169+
@View
170+
struct GameEndOverlay {
171+
@Binding var game: Game
169172

170173
var content: some View {
171174
if game.state != .playing {
@@ -177,7 +180,7 @@ struct GameEndOverlay: View {
177180
button(.class("bg-orange-500 py-2 px-6 rounded-md shadow-lg")) {
178181
"Restart"
179182
}.onClick { _ in
180-
onRestart()
183+
game = Game()
181184
}
182185
}
183186
}

Examples/Swiftle/Sources/EmbeddedApp/main.swift

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,3 @@ struct App {
99
}
1010

1111
App().mount(in: JSObject.global.document.body.object!)
12-
13-
// // this should probably go in an "onMounted" closure or similar
14-
// Document.onKeyDown { event in
15-
// guard let key = EnteredKey(event) else { return }
16-
// store.onKeyPressed(key)
17-
// }
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
@propertyWrapper
2+
public struct Environment<V> {
3+
enum Storage {
4+
case value(V)
5+
case accessor(_StorageKey<EnvironmentValues, V>)
6+
}
7+
8+
var storage: Storage
9+
10+
public init(_ accessor: _StorageKey<EnvironmentValues, V>) {
11+
storage = .accessor(accessor)
12+
}
13+
14+
public var wrappedValue: V {
15+
switch storage {
16+
case let .value(value):
17+
return value
18+
case let .accessor(accessor):
19+
return accessor.defaultValue()
20+
}
21+
}
22+
23+
public mutating func __load(from context: _ViewRenderingContext) {
24+
__load(from: context.environment)
25+
}
26+
27+
mutating func __load(from values: borrowing EnvironmentValues) {
28+
switch storage {
29+
case let .accessor(accessor):
30+
storage = .value(values[accessor])
31+
default:
32+
fatalError("Cannot load environment value twice")
33+
}
34+
}
35+
}
36+
37+
extension EnvironmentValues: _ValueStorage {
38+
public subscript<Value>(key: _StorageKey<EnvironmentValues, Value>) -> Value {
39+
get {
40+
if let value = values[key.propertyID] {
41+
return value[]
42+
} else {
43+
return key.defaultValue()
44+
}
45+
}
46+
set {
47+
values[key.propertyID] = StoredValue(newValue)
48+
}
49+
}
50+
}
51+
52+
public protocol _ValueStorage {
53+
typealias _Key<Value> = _StorageKey<Self, Value>
54+
subscript<Value>(key: _Key<Value>) -> Value { get set }
55+
}
56+
57+
public struct _StorageKey<Storage: _ValueStorage, Value>: Sendable {
58+
let propertyID: PropertyID
59+
let defaultValue: @Sendable () -> sending Value
60+
61+
public init(_ propertyID: PropertyID, defaultValue: @autoclosure @Sendable @escaping () -> sending Value) {
62+
self.propertyID = propertyID
63+
self.defaultValue = defaultValue
64+
}
65+
}
66+
67+
public struct EnvironmentValues {
68+
var values: [PropertyID: StoredValue] = [:]
69+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
public extension View {
2+
consuming func environment<V>(_ key: EnvironmentValues._Key<V>, _ value: V) -> _EnvironmentView<V, Self> {
3+
_EnvironmentView(wrapped: self, key: key, value: value)
4+
}
5+
}
6+
7+
public struct _EnvironmentView<V, Wrapped: View>: View {
8+
public typealias Tag = Wrapped.Tag
9+
let wrapped: Wrapped
10+
let key: EnvironmentValues._Key<V>
11+
let value: V
12+
13+
public static func _renderView(_ view: consuming sending Self, context: consuming _ViewRenderingContext) -> _RenderedView {
14+
context.environment[view.key] = view.value
15+
return Wrapped._renderView(view.wrapped, context: context)
16+
}
17+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
@propertyWrapper
2+
@dynamicMemberLookup
3+
public struct Binding<V> {
4+
enum Storage {
5+
case stateAccessor(StateAccessor<V>)
6+
case getSet(() -> V, (V) -> Void)
7+
}
8+
9+
let storage: Storage
10+
11+
public init(get: @escaping () -> V, set: @escaping (V) -> Void) {
12+
storage = .getSet(get, set)
13+
}
14+
15+
init(accessor: StateAccessor<V>) {
16+
storage = .stateAccessor(accessor)
17+
}
18+
19+
public var wrappedValue: V {
20+
get {
21+
switch storage {
22+
case let .stateAccessor(accessor):
23+
return accessor.value
24+
case let .getSet(get, _):
25+
return get()
26+
}
27+
}
28+
nonmutating set {
29+
switch storage {
30+
case let .stateAccessor(accessor):
31+
accessor.value = newValue
32+
case let .getSet(_, set):
33+
set(newValue)
34+
}
35+
}
36+
}
37+
38+
@_unavailableInEmbedded
39+
public subscript<P>(dynamicMember keypath: WritableKeyPath<V, P>) -> Binding<P> {
40+
Binding<P>(
41+
get: { self.wrappedValue[keyPath: keypath] },
42+
set: { self.wrappedValue[keyPath: keypath] = $0 }
43+
)
44+
}
45+
}

0 commit comments

Comments
 (0)