6.1 KiB
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.ymlis 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:
find . -type l ! -exec test -e {} \; -not -path './.build/*'
Prerequisite: the generator
util/step/generateDialect calls the sibling dialect repo:
$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:
(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
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:
generateDialect— runs the sibling generator, writessrc/dialect.swiftbuildMac—cd game/ver-mac && swift buildpackageMac— assembles.build/OGS Mahjong.app(binaryogs-mahjong,Info.plist,Resources/AppIcon.icns)
Editing the dialect
- Add/modify contexts and components in
dialect.yml, then runutil/build-macto regeneratedialect.swift. - Chicken-egg:
struct F(field-name constants) lives only in the generateddialect.swift. Adding a new field to a context indialect.ymlrequires hand-patching itsstatic let <field> = "<field>"intostruct Findialect.swift(ASCII-alphabetical order) if you cannot regenerate first, otherwise the build fails onF.<field>being unresolved. dialect.swiftis generated — edits there are overwritten on the nextutil/build-mac. Change the source (dialect.yml/components/) instead.
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); thesrc/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 = ...).