152 lines
6.8 KiB
Markdown
152 lines
6.8 KiB
Markdown
# 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 = ...`).
|