Create Hello world for macOS (#1)

This commit is contained in:
kornerr committed 2026-10-17 05:55:51 +02:00
1 parent 7597b3abfa
commit 184454cf59
32 files changed
+767

No files matched your search

+151
View File
@@ -0,0 +1,151 @@
# OGS Mahjong (`mj`)
A macOS SwiftUI app for **OGS Mahjong**, built on the **Dialect v4** declarative
reactive engine (see the sibling `../dialect` repo — this project is a
downstream consumer of its code generator).
Business logic lives in `dialect.yml` (YAML). A generator turns it into
`game/ver-mac/src/dialect.swift` (the runtime engine + `struct F` field
constants + component/should/effect registration). The Swift app shell
(`GameApp`/`AppDelegate`) and the SwiftUI views live outside the generated file
and talk to the engine through generated functions like `rootSet(...)`.
Remote: `https://git.opengamestudio.org/mahjong/mahjong` · Version: `2.0.0`
> Current `dialect.yml` is still the "Root / counter" template inherited from the
> dialect example — the Mahjong game logic has not been written yet. The app
> shell, packaging and symlink wiring are in place.
## Structure
```
dialect.yml Single source of truth for the reactive logic.
Generates game/ver-mac/src/dialect.swift
components/ SHARED source-of-truth for all components.
other/other.swift Shared helpers (otherPrintStderr, otherWriteFile,
otherSetupConsoleLogging, ...)
root/root.swift RootComponent (owns DialectController, registers
effects + shoulds, sets didSetup on setup())
root/rootVM.swift RootVM (ObservableObject / @Published countText)
root/mac/view/RootView.swift Per-platform SwiftUI view
game/ver-mac/ macOS app (SPM executable target "ogs-mahjong")
Package.swift SPM manifest (macOS 13+, swift-tools 5.9)
Info.plist Bundle id org.opengamestudio.Mahjong, exec ogs-mahjong
Resources/AppIcon.icns App icon
src/
GameApp.swift @main SwiftUI App entry
AppDelegate.swift Creates RootComponent + RootVM, calls root.setup()
dialect.swift GENERATED — do not hand-edit (see chicken-egg below)
other/other.swift -> symlink components/other/other.swift
root/root.swift -> symlink components/root/root.swift
root/rootVM.swift -> symlink components/root/rootVM.swift
root/view/RootView.swift-> symlink components/root/mac/view/RootView.swift
ref/swiftraylib Symlink to ../../swiftraylib — the raylib/SwiftUI
renderer sample the game will use for 3D rendering.
util/ Build/run scripts (see Build & Run)
paths APP_MAC = .build/OGS Mahjong.app
build-mac generateDialect -> buildMac -> packageMac
run-mac runMac
rm-mac-geom Resets saved window geometry
step/ Sourced steps: generateDialect, buildMac,
packageMac, runMac
history/gitjs/ GitJS "Glavred" history + the original JS source of the
web Mahjong, kept for reference — not part of the build.
```
## Symlink parity rule
`components/` is the source of truth; `game/ver-mac/src/` contains real
directories whose files are **relative symlinks** back into `components/`:
```
src/other/other.swift -> ../../../../components/other/other.swift (4 up)
src/root/root.swift -> ../../../../components/root/root.swift (4 up)
src/root/rootVM.swift -> ../../../../components/root/rootVM.swift (4 up)
src/root/view/RootView.swift-> ../../../../../components/root/mac/view/RootView.swift (5 up)
```
SPM silently **skips** a dangling symlink (no error), so after moving
`components/` or a subdir, audit for broken links:
```bash
find . -type l ! -exec test -e {} \; -not -path './.build/*'
```
## Prerequisite: the generator
`util/step/generateDialect` calls the **sibling** dialect repo:
```bash
$SDIR/../../dialect/util/run-generator --file=$SDIR/../dialect.yml
```
So `/Volumes/MOE/p/dialect` must exist and its generator must be built before
`util/build-mac` can regenerate:
```bash
(cd ../dialect && util/build-generator) # once, or after dialect core changes
```
The generator resolves the `output:` path **relative to `dialect.yml`'s
directory**, which is why `dialect.yml` says `game/ver-mac/src/dialect.swift`.
## Build & Run
```bash
util/build-mac # generateDialect + swift build + package OGS Mahjong.app
util/run-mac # runs .build/OGS Mahjong.app/Contents/MacOS/ogs-mahjong
util/rm-mac-geom # reset the app's saved window geometry
```
`build-mac` sources `util/paths` then the `util/step/*` scripts in order:
1. `generateDialect` — runs the sibling generator, writes `src/dialect.swift`
2. `buildMac` — `cd game/ver-mac && swift build`
3. `packageMac` — assembles `.build/OGS Mahjong.app` (binary `ogs-mahjong`,
`Info.plist`, `Resources/AppIcon.icns`)
## Editing the dialect
- Add/modify contexts and components in `dialect.yml`, then run
`util/build-mac` to regenerate `dialect.swift`.
- **Chicken-egg**: `struct F` (field-name constants) lives only in the
generated `dialect.swift`. Adding a new field to a context in `dialect.yml`
requires hand-patching its `static let <field> = "<field>"` into `struct F`
in `dialect.swift` (ASCII-alphabetical order) if you cannot regenerate first,
otherwise the build fails on `F.<field>` being unresolved.
- `dialect.swift` is generated — edits there are overwritten on the next
`util/build-mac`. Change the source (`dialect.yml` / `components/`) instead.
- **Validate `dialect.yml` after edits** (e.g. `ruby -ryaml -e 'YAML.load_file("dialect.yml")'`).
Every should-branch label (`1. Upon each ...`) MUST end with a colon — the
parser (`components/yml/ymlFun.swift`) blindly `dropLast()`s it, so a missing
colon silently eats the label's last character in the generated `about`
comment. Note the file can never pass a *strict* YAML parser: condition and
reaction lines (under `if:` / `then:`) contain unquoted `: ` (e.g.
`c.countText = "Count: '\(c.count)'"`), which standard YAML rejects. So treat
a parser complaint pointing *inside* an `if:`/`then:` block as expected; any
complaint elsewhere (like a missing branch-label colon) is a real bug.
## Swift 6
The project currently builds in Swift 5 language mode
(`util/step/buildMac` has the Swift 6 flag commented out). Enabling
`-Xswiftc -swift-version -Xswiftc 6` fails today on the singleton/`@MainActor`
concurrency errors (`RootComponent.singleton`, `RootVM.shared` in
`components/root/`, and the `RootVM.shared!.countText = ...` oneliner in the
generated `dialect.swift`). Do not flip it on without addressing those.
## Conventions
- `game/ver-mac/.build/`, `build/`, `.DS_Store`, Xcode/Gradle artifacts are
git-ignored.
- Prefer editing files in `components/` (the real files); the `src/` entries are
symlinks.
- The generated UI callback plumbing is nonisolated; UI singletons are weak
(`RootComponent.singleton`, `RootVM.shared`) and are accessed from the
generated oneliners with force-unwrap (`RootVM.shared!.countText = ...`).