diff --git a/.github/actions/migrate-compiler-cache/action.yml b/.github/actions/migrate-compiler-cache/action.yml new file mode 100644 index 00000000..042fa5ae --- /dev/null +++ b/.github/actions/migrate-compiler-cache/action.yml @@ -0,0 +1,26 @@ +name: Import a previous compiler cache +description: Stage one compatible per-job compiler snapshot for a cold family cache. +inputs: + legacy-prefix: + description: Previous per-job cache prefix without its branch-layer suffix. + required: true +runs: + using: composite + steps: + - name: Describe previous compiler cache + id: migration + shell: bash + env: + LEGACY_CACHE_PREFIX: ${{ inputs.legacy-prefix }} + run: python .github/ci/compiler_cache.py migration-plan --prefix "$LEGACY_CACHE_PREFIX" + - name: Restore previous compiler cache + id: legacy + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.migration.outputs.path }} + key: ${{ steps.migration.outputs.key }} + restore-keys: ${{ steps.migration.outputs.prefix }} + - name: Stage previous compiler cache + if: steps.legacy.outputs.cache-matched-key != '' + shell: bash + run: python .github/ci/compiler_cache.py import diff --git a/.github/actions/prepare-dependencies/action.yml b/.github/actions/prepare-dependencies/action.yml index ae3c0310..f7b4e8e8 100644 --- a/.github/actions/prepare-dependencies/action.yml +++ b/.github/actions/prepare-dependencies/action.yml @@ -4,9 +4,33 @@ inputs: profile: description: CI composition, headless or modules. required: true + cache-job: + description: Unique matrix job name for Windows dependency contributions. + default: worker + windows-vcpkg: + description: Build shared Windows dependencies once, or consume a prepared installation. + default: build + prepared-artifact: + description: Installed vcpkg artifact from this run's platform preparation job. + default: '' +outputs: + dependency-cache-writer: + description: Whether this run may publish its main or develop build dependency cache. + value: ${{ steps.metadata.outputs.dependency-cache-writer }} runs: using: composite steps: + - name: Validate dependency preparation mode + shell: bash + env: + VCPKG_MODE: ${{ inputs.windows-vcpkg }} + PREPARED_ARTIFACT: ${{ inputs.prepared-artifact }} + run: | + case "$VCPKG_MODE" in + build) ;; + consume) test -n "$PREPARED_ARTIFACT" ;; + *) echo 'Unknown Windows vcpkg preparation mode' >&2; exit 1 ;; + esac - name: Describe dependency caches id: metadata shell: bash @@ -19,13 +43,16 @@ runs: with: path: ${{ steps.metadata.outputs.submodule-path }} key: ${{ steps.metadata.outputs.submodule-key }} + restore-keys: | + ${{ steps.metadata.outputs.submodule-prefix }} + ${{ steps.metadata.outputs.submodule-main-prefix }} - name: Check out pinned submodules shell: bash env: CHERYL_SUBMODULE_TOKEN: ${{ github.token }} run: python .github/ci/dependencies.py submodules - name: Save submodule Git objects - if: steps.submodules.outputs.cache-hit != 'true' + if: steps.metadata.outputs.dependency-cache-writer == 'true' && steps.submodules.outputs.cache-hit != 'true' uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ${{ steps.metadata.outputs.submodule-path }} @@ -39,26 +66,12 @@ runs: ${{ steps.metadata.outputs.vcpkg-root }} ${{ steps.metadata.outputs.ccache-tools }} key: ${{ steps.metadata.outputs.tools-key }} - - name: Restore Windows dependency packages and downloads - if: runner.os == 'Windows' - uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: ${{ steps.metadata.outputs.dependency-path }} - key: ${{ steps.metadata.outputs.dependency-key }} - restore-keys: | - ${{ steps.metadata.outputs.dependency-exact-prefix }} - ${{ steps.metadata.outputs.dependency-profile-prefix }} - ${{ steps.metadata.outputs.dependency-runner-prefix }} - - name: Snapshot Windows dependency cache - if: runner.os == 'Windows' - shell: bash - run: python .github/ci/dependencies.py cache-state --snapshot - name: Prepare pinned Windows tools if: runner.os == 'Windows' shell: bash run: python .github/ci/dependencies.py windows-tools - name: Save Windows tools - if: runner.os == 'Windows' && steps.tools.outputs.cache-hit != 'true' + if: runner.os == 'Windows' && steps.metadata.outputs.environment-cache-writer == 'true' && steps.tools.outputs.cache-hit != 'true' uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: | @@ -72,9 +85,6 @@ runs: with: path: ${{ steps.metadata.outputs.pip-path }} key: ${{ steps.metadata.outputs.pip-key }} - restore-keys: | - ${{ steps.metadata.outputs.pip-exact-prefix }} - ${{ steps.metadata.outputs.pip-prefix }} - name: Snapshot Python cache if: runner.os == 'Windows' && inputs.profile == 'modules' shell: bash @@ -89,11 +99,63 @@ runs: shell: bash run: python .github/ci/dependencies.py cache-state --cache pip - name: Save Python downloads and wheels - if: runner.os == 'Windows' && inputs.profile == 'modules' && steps.pip-state.outputs.changed == 'true' + if: runner.os == 'Windows' && inputs.profile == 'modules' && steps.metadata.outputs.environment-cache-writer == 'true' && steps.pip.outputs.cache-hit != 'true' && steps.pip-state.outputs.changed == 'true' uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ${{ steps.metadata.outputs.pip-path }} key: ${{ steps.pip.outputs.cache-primary-key }} + - name: Describe shared Windows dependency pool + if: runner.os == 'Windows' && inputs.windows-vcpkg == 'build' + id: vcpkg + shell: bash + run: python .github/ci/vcpkg_cache.py describe + - name: Restore shared Windows dependency pool + if: runner.os == 'Windows' && inputs.windows-vcpkg == 'build' + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.vcpkg.outputs.cache-path }} + key: ${{ steps.vcpkg.outputs.cache-key }} + restore-keys: ${{ steps.vcpkg.outputs.cache-prefix }} + - name: Install Windows dependencies + if: runner.os == 'Windows' && inputs.windows-vcpkg == 'build' + id: vcpkg-install + shell: pwsh + env: + DEPENDENCY_PROFILE: ${{ inputs.profile }} + run: | + $vswhere = "${env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer/vswhere.exe" + $vs = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath + if (!$vs) { throw 'MSVC x64 tools are unavailable' } + & "$vs/Common7/Tools/Launch-VsDevShell.ps1" -Arch amd64 -HostArch amd64 -SkipAutomaticLocation + python .github/ci/dependencies.py windows-install --profile $env:DEPENDENCY_PROFILE + if ($LASTEXITCODE) { exit $LASTEXITCODE } + - name: Pack prepared Windows dependencies + if: ${{ !cancelled() && runner.os == 'Windows' && inputs.windows-vcpkg == 'build' && steps.vcpkg-install.outcome == 'success' }} + id: vcpkg-pack + shell: bash + run: python .github/ci/vcpkg_cache.py pack --installed "$CHERYL_CI_VCPKG_INSTALLED_DIR" + - name: Retain prepared Windows dependencies + if: ${{ !cancelled() && runner.os == 'Windows' && inputs.windows-vcpkg == 'build' && steps.vcpkg-pack.outputs.has-content == 'true' }} + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: vcpkg-cache-${{ runner.arch }}-${{ inputs.cache-job }}-${{ github.run_attempt }} + path: ${{ steps.vcpkg.outputs.contribution-path }} + if-no-files-found: error + compression-level: 0 + overwrite: true + retention-days: 1 + - name: Download shared Windows installation + if: runner.os == 'Windows' && inputs.windows-vcpkg == 'consume' + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: ${{ inputs.prepared-artifact }} + path: ${{ steps.metadata.outputs.vcpkg-installed-dir }} + - name: Verify shared Windows installation + if: runner.os == 'Windows' && inputs.windows-vcpkg == 'consume' + shell: bash + env: + DEPENDENCY_PROFILE: ${{ inputs.profile }} + run: python .github/ci/dependencies.py windows-verify --profile "$DEPENDENCY_PROFILE" - name: Update Linux package indexes if: runner.os == 'Linux' shell: bash @@ -119,7 +181,7 @@ runs: DEPENDENCY_PROFILE: ${{ inputs.profile }} run: python .github/ci/dependencies.py apt-install --profile "$DEPENDENCY_PROFILE" - name: Save Linux package binaries - if: runner.os == 'Linux' && steps.apt-plan.outputs.needed == 'true' && steps.apt.outputs.cache-hit != 'true' + if: runner.os == 'Linux' && steps.metadata.outputs.environment-cache-writer == 'true' && steps.apt-plan.outputs.needed == 'true' && steps.apt.outputs.cache-hit != 'true' uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ${{ steps.metadata.outputs.dependency-path }}/apt/*.deb diff --git a/.github/actions/restore-compiler-cache/action.yml b/.github/actions/restore-compiler-cache/action.yml new file mode 100644 index 00000000..cd66ffe9 --- /dev/null +++ b/.github/actions/restore-compiler-cache/action.yml @@ -0,0 +1,56 @@ +name: Restore layered compiler caches +description: Restore immutable compiler layers and optionally attach a fresh writable delta. +inputs: + prefix: + description: Compiler cache format, runner, architecture and compiler family prefix. + required: true + attach: + description: Configure ccache for a worker; publishers only restore the layers. + default: 'true' +outputs: + current-path: + description: Current immutable layer, or the publisher's merged layer to save. + value: ${{ steps.metadata.outputs.current-path }} + current-key: + description: New immutable key for this run's family snapshot. + value: ${{ steps.metadata.outputs.current-key }} + delta-archive: + description: Worker archive path for this run's compiler additions. + value: ${{ steps.metadata.outputs.delta-archive }} + current-hit: + description: Whether the current family layer was restored. + value: ${{ steps.current.outputs.cache-matched-key != '' }} +runs: + using: composite + steps: + - name: Describe compiler cache layers + id: metadata + shell: bash + env: + CACHE_PREFIX: ${{ inputs.prefix }} + run: python .github/ci/compiler_cache.py plan --prefix "$CACHE_PREFIX" + - name: Restore main compiler baseline + if: steps.metadata.outputs.restore-main == 'true' + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.metadata.outputs.main-path }} + key: ${{ steps.metadata.outputs.main-prefix }}${{ github.run_id }}-${{ github.run_attempt }} + restore-keys: ${{ steps.metadata.outputs.main-prefix }} + - name: Restore develop compiler additions + if: steps.metadata.outputs.restore-develop == 'true' + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.metadata.outputs.develop-path }} + key: ${{ steps.metadata.outputs.develop-prefix }}${{ github.run_id }}-${{ github.run_attempt }} + restore-keys: ${{ steps.metadata.outputs.develop-prefix }} + - name: Restore current compiler additions + id: current + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.metadata.outputs.current-path }} + key: ${{ steps.metadata.outputs.current-key }} + restore-keys: ${{ steps.metadata.outputs.current-prefix }} + - name: Configure compiler cache layers + if: inputs.attach == 'true' + shell: bash + run: python .github/ci/compiler_cache.py attach diff --git a/.github/ci/cache_cleanup.py b/.github/ci/cache_cleanup.py new file mode 100644 index 00000000..c7338161 --- /dev/null +++ b/.github/ci/cache_cleanup.py @@ -0,0 +1,350 @@ +"""Plan or apply bounded GitHub Actions cache maintenance; dry run by default.""" + +import argparse +from datetime import datetime, timedelta, timezone +import json +import math +import os +from pathlib import Path +import re +import sys +from urllib.error import HTTPError, URLError +from urllib.parse import urlencode +from urllib.request import HTTPRedirectHandler, Request, build_opener + + +GIB = 1024 ** 3 +SNAPSHOT = re.compile( + r"^((?:ccache-v[123]|dependencies-v1|pip-v1)-.+|build-deps-v2-.+-binaries-[0-9a-f]+|vcpkg-v1-Windows-X64-msvc)-[0-9]+-[0-9]+$" +) +PULL_REF = re.compile(r"^refs/pull/([1-9][0-9]*)/merge$") +KNOWN_PREFIXES = ("ccache-v1-", "ccache-v2-", "ccache-v3-", "dependencies-v1-", "pip-v1-", "docs-tools-v1-", + "windows-tools-v1-", "submodule-git-v1-", "apt-debs-v1-", "build-deps-v2-", "vcpkg-v1-") +ENVIRONMENT_KEY = re.compile(r"actions-env-cache-v[1-9][0-9]*-.+") +SOURCE_KEY = re.compile(r"(build-deps-v2-.+-(?:main|develop)-sources-)[0-9a-f]{64}") +COMPILER_POOL = re.compile( + r"(ccache-v3-(?:Linux|Windows)-[A-Za-z0-9_.]+-(?:linux-gcc14|linux-clang18|windows-msvc))-(?:main|develop|pr|branch)" +) +LEGACY_COMPILER = re.compile( + r"ccache-v([12])-(ubuntu-24\.04|windows-2025)-([A-Za-z0-9_.]+)-(.+)-(Release|Debug|RelWithDebInfo)" + r"(?:-(main|develop|pr|branch))?" +) +# Only the pinned matrix profiles have a known replacement pool. +LEGACY_FAMILIES = { + ("ubuntu-24.04", "linux-gcc-hwloc", "Release"): ("Linux", "linux-gcc14"), + ("ubuntu-24.04", "linux-gcc-neutral", "Release"): ("Linux", "linux-gcc14"), + ("ubuntu-24.04", "linux-modules", "Release"): ("Linux", "linux-gcc14"), + ("ubuntu-24.04", "linux-modules-shared", "Release"): ("Linux", "linux-gcc14"), + ("ubuntu-24.04", "linux-clang-debug", "Debug"): ("Linux", "linux-clang18"), + ("windows-2025", "windows-msvc-hwloc", "RelWithDebInfo"): ("Windows", "windows-msvc"), + ("windows-2025", "windows-modules", "RelWithDebInfo"): ("Windows", "windows-msvc"), + ("windows-2025", "windows-modules-shared", "RelWithDebInfo"): ("Windows", "windows-msvc"), +} +MAINTENANCE_PATH = ".github/workflows/cache-maintenance.yml" + + +def lineage(key): + match = SNAPSHOT.fullmatch(key) + return match.group(1) if match else None + + +def compiler_pool(logical): + if logical is None: + return None + pooled = COMPILER_POOL.fullmatch(logical) + if pooled: + return pooled.group(1) + legacy = LEGACY_COMPILER.fullmatch(logical) + if not legacy: + return None + revision, runner, architecture, profile, configuration, role = legacy.groups() + if (revision == "2") != bool(role): + return None + family = LEGACY_FAMILIES.get((runner, profile, configuration)) + return f"ccache-v3-{family[0]}-{architecture}-{family[1]}" if family else None + + +def timestamp(value): + if not isinstance(value, str): + raise ValueError("Cache timestamps must be ISO 8601 strings") + parsed = datetime.fromisoformat(value.replace("Z", "+00:00")) + if parsed.tzinfo is None: + raise ValueError("Cache timestamps must include a timezone") + return parsed + + +def plan_caches(caches, *, default_branch, closed_prs, active_refs, now, + budget_bytes=8 * GIB, grace_seconds=900, closed_pr=None, unresolved_refs=frozenset(), + open_prs=frozenset()): + """Return a deletion plan without mutating caches or accessing GitHub. + + Active/unresolved refs and recently created or accessed entries are protected. + Exact-PR mode never compacts or trims entries outside that confirmed closed PR. + Newest compiler and build-dependency snapshots prefer main, develop, then + confirmed open PRs when the budget requires eviction. Priorities reserve no storage. + """ + if not isinstance(default_branch, str) or not default_branch or now.tzinfo is None: + raise ValueError("A default branch and timezone-aware current time are required") + if type(budget_bytes) is not int or budget_bytes < 0 or grace_seconds < 0: + raise ValueError("The cache budget and grace period must be nonnegative") + if closed_pr is not None and (type(closed_pr) is not int or closed_pr <= 0): + raise ValueError("A pull request number must be positive") + + entries = [] + seen = set() + protected = set() + cutoff = now - timedelta(seconds=grace_seconds) + for cache in caches: + identifier = cache.get("id") + if type(identifier) is not int or identifier <= 0 or identifier in seen: + raise ValueError("Cache IDs must be unique positive integers") + seen.add(identifier) + if type(cache.get("size_in_bytes")) is not int or cache["size_in_bytes"] < 0: + raise ValueError("Cache sizes must be nonnegative integers") + if any(not isinstance(cache.get(key), str) or not cache[key] for key in ("key", "ref", "version")): + raise ValueError("Each cache needs a key, ref and version") + created, accessed = timestamp(cache["created_at"]), timestamp(cache["last_accessed_at"]) + record = (dict(cache), created, accessed, lineage(cache["key"])) + entries.append(record) + if cache["ref"] in active_refs or cache["ref"] in unresolved_refs or max(created, accessed) >= cutoff: + protected.add(identifier) + + before = sum(cache["size_in_bytes"] for cache, *_ in entries) + result = {"before_bytes": before, "after_bytes": before, + "protected_bytes": sum(cache["size_in_bytes"] for cache, *_ in entries if cache["id"] in protected), + "protected_ids": sorted(protected), "deletions": []} + selected = set() + + def remove(cache, reason): + if cache["id"] not in protected and cache["id"] not in selected: + selected.add(cache["id"]) + result["deletions"].append({**cache, "reason": reason}) + result["after_bytes"] -= cache["size_in_bytes"] + + for cache, *_ in entries: + match = PULL_REF.fullmatch(cache["ref"]) + number = int(match.group(1)) if match else None + if number in closed_prs and (closed_pr is None or number == closed_pr): + remove(cache, "closed pull request") + if closed_pr is not None: + return result + + layered_profiles = { + (cache["ref"], "ccache-v1-" + logical[len("ccache-v2-"):].rsplit("-", 1)[0]) + for cache, _, _, logical in entries + if logical is not None and logical.startswith("ccache-v2-") + and logical.rsplit("-", 1)[1] in {"main", "develop", "pr", "branch"} + } + pooled_profiles = { + (cache["ref"], pool) + for cache, _, _, logical in entries + if cache["key"].startswith("ccache-v3-") and (pool := compiler_pool(logical)) is not None + } + newest = {} + baseline = {} + baseline_policy = {} + for record in entries: + cache, created, _, logical = record + source = SOURCE_KEY.fullmatch(cache["key"]) + if logical is not None: + bucket = (cache["ref"], cache["version"], logical) + if bucket not in newest or (created, cache["id"]) > (newest[bucket][1], newest[bucket][0]["id"]): + newest[bucket] = record + priority, reason = 0, "least recently used" + pooled_replacement = (cache["key"].startswith(("ccache-v1-", "ccache-v2-")) + and (cache["ref"], compiler_pool(logical)) in pooled_profiles) + if pooled_replacement: + remove(cache, "replaced compiler snapshot") + replaced = pooled_replacement or (cache["key"].startswith("ccache-v1-") + and (cache["ref"], logical) in layered_profiles) + if not replaced: + known = cache["key"].startswith(KNOWN_PREFIXES) or ENVIRONMENT_KEY.fullmatch(cache["key"]) + if cache["ref"] == "refs/heads/" + default_branch and known: + priority, reason = 2, "default-branch baseline" + if source or (logical is not None and cache["key"].startswith(("ccache-v1-", "ccache-v2-", "ccache-v3-", "build-deps-v2-"))): + match = PULL_REF.fullmatch(cache["ref"]) + if cache["ref"] == "refs/heads/main": + priority, reason = 3, "main baseline" + elif cache["ref"] == "refs/heads/develop": + priority, reason = 2, "develop baseline" + elif match and int(match.group(1)) in open_prs: + priority, reason = 1, "open pull request baseline" + if priority: + bucket = (cache["ref"], cache["version"], source.group(1) if source else logical or cache["key"]) + if bucket not in baseline or (created, cache["id"]) > (baseline[bucket][1], baseline[bucket][0]["id"]): + baseline[bucket] = record + baseline_policy[bucket] = (priority, reason) + for cache, _, _, logical in entries: + if logical is not None and newest[(cache["ref"], cache["version"], logical)][0]["id"] != cache["id"]: + remove(cache, "superseded snapshot") + + priorities = {record[0]["id"]: baseline_policy[bucket] for bucket, record in baseline.items()} + for cache, _, _, _ in sorted(entries, key=lambda record: ( + priorities.get(record[0]["id"], (0, ""))[0], record[2], record[1], record[0]["id"])): + if result["after_bytes"] <= budget_bytes: + break + remove(cache, priorities.get(cache["id"], (0, "least recently used"))[1]) + return result + + +class NoRedirect(HTTPRedirectHandler): + def redirect_request(self, request, file, code, message, headers, new_url): + return None + + +class GitHubApi: + def __init__(self, repository, token, api_url="https://api.github.com"): + if not isinstance(repository, str) or not re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", repository) or any( + part in {".", ".."} for part in repository.split("/")): + raise ValueError("GITHUB_REPOSITORY must be owner/repository") + if not isinstance(api_url, str) or api_url.rstrip("/") != "https://api.github.com": + raise ValueError("Cache maintenance supports the GitHub.com HTTPS API only") + if not isinstance(token, str) or not token or any(character.isspace() for character in token): + raise ValueError("A nonempty GitHub token without whitespace is required") + self.base = api_url.rstrip("/") + "/repos/" + repository + self.token = token + self.opener = build_opener(NoRedirect()) + + def request(self, method, path, *, missing_ok=False, **query): + if method not in {"GET", "DELETE"} or (path and not path.startswith("/")) or any( + value in path for value in ("//", "?", "#")) or ".." in path.split("/"): + raise ValueError("Expected a repository-relative GitHub cache API request") + url = self.base + path + ("?" + urlencode(query) if query else "") + request = Request(url, method=method, headers={ + "Accept": "application/vnd.github+json", "Authorization": "Bearer " + self.token, + "X-GitHub-Api-Version": "2026-03-10", "User-Agent": "cheryl-cache-maintenance", + }) + try: + with self.opener.open(request, timeout=30) as response: + data = response.read() + except HTTPError as error: + status = error.code + error.close() + if status == 404 and missing_ok: + return None + raise RuntimeError(f"GitHub API {method} {path} returned HTTP {status}") from None + except URLError: + raise RuntimeError(f"GitHub API {method} {path} could not be reached") from None + value = json.loads(data) if data else {} + if not isinstance(value, dict): + raise ValueError("The GitHub API returned an unexpected response") + return value + + def pages(self, path, field, **query): + entries = [] + for page in range(1, 1001): + value = self.request("GET", path, per_page=100, page=page, **query) + batch = value.get(field) + if not isinstance(batch, list): + raise ValueError(f"The GitHub API response is missing {field}") + entries.extend(batch) + if len(batch) < 100: + return entries + raise RuntimeError("GitHub API pagination exceeded the maintenance limit") + + +def active_refs(api, caches): + refs = set() + current_run = os.environ.get("GITHUB_RUN_ID") + for status in ("queued", "in_progress", "waiting", "pending", "requested"): + for run in api.pages("/actions/runs", "workflow_runs", status=status): + if (str(run["id"]) == current_run or run.get("path", "").split("@", 1)[0] == MAINTENANCE_PATH + or run.get("name") == "Maintain Actions caches"): + continue + if run.get("head_branch"): + refs.add("refs/heads/" + run["head_branch"]) + refs.add("refs/tags/" + run["head_branch"]) + pulls = run.get("pull_requests", []) + if not pulls and (run.get("event") == "pull_request" or not run.get("head_branch")): + # Fork runs can omit PR metadata; other runs can omit their ref. + # Keep every possible scope when the API cannot identify one. + refs.update(cache["ref"] for cache in caches) + for pull in pulls: + refs.add(f"refs/pull/{int(pull['number'])}/merge") + if pull.get("base", {}).get("ref"): + refs.add("refs/heads/" + pull["base"]["ref"]) + return refs + + +def pull_states(api, caches, closed_pr=None): + numbers = {closed_pr} if closed_pr is not None else { + int(match.group(1)) for cache in caches if (match := PULL_REF.fullmatch(cache["ref"])) + } + closed, unresolved = set(), set() + for number in sorted(numbers): + pull = api.request("GET", f"/pulls/{number}", missing_ok=True) + if pull is None: + unresolved.add(f"refs/pull/{number}/merge") + elif pull.get("state") == "closed": + closed.add(number) + elif pull.get("state") != "open": + raise ValueError("The GitHub API returned an unexpected pull request state") + return closed, unresolved + + +def apply_plan(api, plan, *, apply=False): + result = {"dry_run": not apply, "deleted_ids": [], "missing_ids": []} + if apply: + for cache in plan["deletions"]: + response = api.request("DELETE", f"/actions/caches/{cache['id']}", missing_ok=True) + result["missing_ids" if response is None else "deleted_ids"].append(cache["id"]) + return result + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--apply", action="store_true", help="Delete the planned caches; otherwise report only") + parser.add_argument("--budget-gib", type=float, default=8, help="Repository cache target in GiB (default: 8)") + parser.add_argument("--closed-pr", type=int, help="Only remove caches belonging to this confirmed closed PR") + arguments = parser.parse_args() + if not math.isfinite(arguments.budget_gib) or arguments.budget_gib < 0: + parser.error("The cache budget must be finite and nonnegative") + if arguments.closed_pr is not None and arguments.closed_pr <= 0: + parser.error("The pull request number must be positive") + budget_bytes = int(arguments.budget_gib * GIB) + + api = GitHubApi(os.environ.get("GITHUB_REPOSITORY"), os.environ.get("GITHUB_TOKEN"), + os.environ.get("GITHUB_API_URL", "https://api.github.com")) + repository = api.request("GET", "") + caches = api.pages("/actions/caches", "actions_caches", sort="created_at", direction="asc") + closed, unresolved = pull_states(api, caches, arguments.closed_pr) + if arguments.closed_pr is not None and arguments.closed_pr not in closed: + print("The requested pull request is not confirmed closed; no caches selected.") + return 0 + open_prs = set() if arguments.closed_pr is not None else { + int(match.group(1)) for cache in caches + if (match := PULL_REF.fullmatch(cache["ref"])) + and int(match.group(1)) not in closed and cache["ref"] not in unresolved + } + # Check live workflow admission after gathering cache/PR metadata. A new run + # can still race deletion; cache maintenance offers no atomic restore lease. + running = active_refs(api, caches) + plan = plan_caches(caches, default_branch=repository["default_branch"], closed_prs=closed, + active_refs=running, unresolved_refs=unresolved, now=datetime.now(timezone.utc), + budget_bytes=budget_bytes, closed_pr=arguments.closed_pr, open_prs=open_prs) + print("Applying cache maintenance." if arguments.apply else "Dry run; pass --apply to delete these caches.") + for cache in plan["deletions"]: + print(json.dumps({key: cache[key] for key in ("id", "key", "ref", "size_in_bytes", "reason")})) + summary = (f"Caches: {len(caches)}; selected: {len(plan['deletions'])}; " + f"storage: {plan['before_bytes'] / GIB:.2f} GiB ({plan['before_bytes']} bytes) -> " + f"{plan['after_bytes'] / GIB:.2f} GiB ({plan['after_bytes']} bytes); " + f"budget: {budget_bytes / GIB:.2f} GiB ({budget_bytes} bytes); " + f"protected: {plan['protected_bytes'] / GIB:.2f} GiB ({plan['protected_bytes']} bytes)") + print(summary) + if arguments.closed_pr is None and plan["after_bytes"] > budget_bytes: + print("The target cannot be reached while active, recent or unresolved caches remain protected.") + result = apply_plan(api, plan, apply=arguments.apply) + if arguments.apply: + print(f"Deleted: {len(result['deleted_ids'])}; already absent: {len(result['missing_ids'])}") + if os.environ.get("GITHUB_STEP_SUMMARY"): + with Path(os.environ["GITHUB_STEP_SUMMARY"]).open("a", encoding="utf-8") as output: + output.write("### Cache maintenance" + (" (dry run)" if result["dry_run"] else "") + "\n\n" + summary + "\n") + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except (KeyError, OSError, ValueError, RuntimeError) as error: + print(f"Cache maintenance failed: {error}", file=sys.stderr) + raise SystemExit(1) diff --git a/.github/ci/compiler_cache.py b/.github/ci/compiler_cache.py new file mode 100644 index 00000000..071d7a7c --- /dev/null +++ b/.github/ci/compiler_cache.py @@ -0,0 +1,402 @@ +"""Share native ccache layers across compatible jobs and publish their union.""" + +import argparse +import hashlib +import json +import math +import os +from pathlib import Path, PurePath +import re +import shutil +import stat +import tarfile +import tempfile + + +ROOT = Path(__file__).resolve().parents[2] +PREFIX = re.compile(r"ccache-v3-(?:Linux-[A-Za-z0-9_]+-linux-(?:gcc14|clang18)|Windows-[A-Za-z0-9_]+-windows-msvc)") +LEGACY_PREFIXES = { + "linux-gcc14": ("ubuntu-24.04", ("linux-gcc-hwloc-Release", "linux-gcc-neutral-Release", + "linux-modules-Release", "linux-modules-shared-Release")), + "linux-clang18": ("ubuntu-24.04", ("linux-clang-debug-Debug",)), + "windows-msvc": ("windows-2025", ("windows-msvc-hwloc-RelWithDebInfo", + "windows-modules-RelWithDebInfo", "windows-modules-shared-RelWithDebInfo")), +} +PULL_REF = re.compile(r"refs/pull/([1-9][0-9]*)/merge") +# ccache 4.9 uses four hex digits followed by 29 base32hex digits; 4.14 +# uses 40 hex digits. Both file backends split after the first two digits. +NATIVE_KEY = re.compile(r"[0-9a-f]{2}/(?:[0-9a-f]{2}[0-9a-v]{29}|[0-9a-f]{38})") +# CacheEntry v1 result headers are shared by both native key formats. +RESULT_HEADER = b"\xcc\xac\x01\x00" +EMPTY_MARKER = ".cheryl-empty-layer" +METADATA_FILES = {EMPTY_MARKER, "CACHEDIR.TAG"} +LAYER_LIMIT = 1024 ** 3 +ARCHIVE_ENTRY_LIMIT = 200_000 + + +def _valid_ref(ref): + return (isinstance(ref, str) and ref.startswith("refs/") and not ref.endswith(("/", ".")) + and not any(char.isspace() or ord(char) < 32 or ord(char) == 127 or char in "~^:?*[\\" for char in ref) + and not any(part in ref for part in ("..", "//", "@{")) + and not any(part.startswith(".") or part.endswith(".lock") for part in ref.split("/"))) + + +def select_role(environment, event): + ref = environment.get("GITHUB_REF", "") + if not _valid_ref(ref): + raise ValueError("GITHUB_REF must be a valid full Git reference") + if environment.get("GITHUB_EVENT_NAME") == "pull_request": + match = PULL_REF.fullmatch(ref) + number = event.get("number") if isinstance(event, dict) else None + pull = event.get("pull_request") if isinstance(event, dict) else None + base_info = pull.get("base") if isinstance(pull, dict) else None + base = base_info.get("ref") if isinstance(base_info, dict) else None + if not match or type(number) is not int or number != int(match.group(1)) or not isinstance(base, str) or not _valid_ref("refs/heads/" + base): + raise ValueError("Pull request cache scope must match the event number and base branch") + return {"role": "pr", "restore-main": True, "restore-develop": base == "develop"} + if not ref.startswith(("refs/heads/", "refs/tags/")) or not ref.rsplit("/", 1)[-1]: + raise ValueError("Compiler caches require a branch, tag or pull request merge reference") + if ref == "refs/heads/main": + return {"role": "main", "restore-main": False, "restore-develop": False} + if ref == "refs/heads/develop": + return {"role": "develop", "restore-main": True, "restore-develop": False} + return {"role": "branch", "restore-main": True, "restore-develop": False} + + +def _context(environment, event): + environment = os.environ if environment is None else environment + if event is None: + event = {} + if environment.get("GITHUB_EVENT_NAME") == "pull_request": + with Path(environment["GITHUB_EVENT_PATH"]).open(encoding="utf-8") as source: + event = json.load(source) + return environment, event + + +def metadata(prefix, *, environment=None, event=None, root=ROOT): + environment, event = _context(environment, event) + if not isinstance(prefix, str) or not PREFIX.fullmatch(prefix): + raise ValueError("Compiler cache prefix must identify a supported ccache-v3 runner, architecture and compiler family") + for name in ("GITHUB_RUN_ID", "GITHUB_RUN_ATTEMPT"): + value = environment.get(name) + if not isinstance(value, str) or not re.fullmatch(r"[1-9][0-9]*", value): + raise ValueError(f"{name} must be a positive integer") + selected = select_role(environment, event) + directory = Path(root).absolute() / "build" / "cache" / "compiler" + role = selected["role"] + output = {"role": role, "main-path": str(directory / "main"), + "develop-path": str(directory / "develop"), "current-path": str(directory / role), + "delta-path": str(directory / "delta"), + "delta-archive": str(directory.parent / "compiler-artifacts" / "delta.tar"), + "main-prefix": f"{prefix}-main-", "develop-prefix": f"{prefix}-develop-", + "current-prefix": f"{prefix}-{role}-"} + output["current-key"] = output["current-prefix"] + environment["GITHUB_RUN_ID"] + "-" + environment["GITHUB_RUN_ATTEMPT"] + output.update({name: str(selected[name]).lower() for name in ("restore-main", "restore-develop")}) + return output + + +def file_url(path): + return (path if isinstance(path, PurePath) else Path(path)).as_uri() + + +def storage_configuration(current, parents): + return " ".join([file_url(current) + "|update-mtime=true", + *(file_url(parent) + "|read-only" for parent in parents)]) + + +def _files(directory): + directory = Path(directory).absolute() + for ancestor in (directory, *directory.parents): + if ancestor.is_symlink() or (hasattr(ancestor, "is_junction") and ancestor.is_junction()): + raise ValueError(f"Compiler cache paths must not contain symbolic links: {ancestor}") + if not directory.exists(): + return {} + if not directory.is_dir(): + raise ValueError(f"Compiler cache path must be a directory: {directory}") + files = {} + for parent, directories, names in os.walk(directory, followlinks=False): + for name in sorted(directories + names): + path = Path(parent) / name + mode = path.lstat().st_mode + if path.is_symlink() or (hasattr(path, "is_junction") and path.is_junction()): + raise ValueError(f"Compiler cache paths must not contain symbolic links: {path}") + if stat.S_ISREG(mode): + files[path.relative_to(directory).as_posix()] = path + elif not stat.S_ISDIR(mode): + raise ValueError(f"Compiler cache contains a non-regular file: {path}") + return files + + +def _digest(path): + digest = hashlib.sha256() + with path.open("rb") as source: + for block in iter(lambda: source.read(1024 * 1024), b""): + digest.update(block) + return digest.digest() + + +def _entries(directory): + return {name: path for name, path in _files(directory).items() if name not in METADATA_FILES} + + +def fingerprint(directory): + digest = hashlib.sha256() + for name, path in sorted(_entries(directory).items()): + encoded = name.encode("utf-8") + digest.update(len(encoded).to_bytes(8, "big")) + digest.update(encoded) + digest.update(_digest(path)) + return digest.hexdigest() + + +def _is_result(path): + with path.open("rb") as source: + return source.read(len(RESULT_HEADER)) == RESULT_HEADER + + +def deduplicate(current, parents): + current_files = _entries(current) + parent_files = [_entries(parent) for parent in parents] + removed = 0 + for name, path in current_files.items(): + parent = next((files[name] for files in parent_files if name in files), None) + if parent is None: + continue + # Result keys identify compiler inputs; serialized headers can differ by creation time. + same_result = NATIVE_KEY.fullmatch(name) and _is_result(path) and _is_result(parent) + if same_result or (path.stat().st_size == parent.stat().st_size and _digest(path) == _digest(parent)): + path.unlink() + removed += 1 + return removed + + +def _layers(environment, event, root): + info = metadata(environment.get("CHERYL_CCACHE_PREFIX"), environment=environment, event=event, root=root) + parents = [] + if info["restore-develop"] == "true": + parents.append(Path(info["develop-path"])) + if info["restore-main"] == "true": + parents.append(Path(info["main-path"])) + return info, Path(info["current-path"]), parents + + +def configure(*, environment=None, event=None, root=ROOT): + environment, event = _context(environment, event) + info, current, parents = _layers(environment, event, root) + delta = Path(info["delta-path"]) + for directory in [delta, current, *parents]: + _files(directory) + directory.mkdir(parents=True, exist_ok=True) + if _entries(delta): + raise ValueError("A compiler worker must start with a fresh writable delta") + stats = Path(root).absolute() / "build" / "ccache" + _files(stats) + stats.mkdir(parents=True, exist_ok=True) + return {"CCACHE_DIR": str(stats), "CCACHE_REMOTE_ONLY": "1", + "CCACHE_REMOTE_STORAGE": storage_configuration(delta, [current, *parents]), + "CCACHE_NOFILECLONE": "1", "CCACHE_NOHARDLINK": "1"} + + +def _native_entries(directory): + entries = _entries(directory) + for name in sorted(entries): + if not NATIVE_KEY.fullmatch(name): + raise ValueError(f"Compiler archives contain an invalid native file-backend entry: {name!r}") + return entries + + +def trim(directory, *, limit=LAYER_LIMIT): + entries = _native_entries(directory) + sizes = {name: path.stat().st_size for name, path in entries.items()} + total = sum(sizes.values()) + removed = 0 + for name, path in sorted(entries.items(), key=lambda item: (item[1].stat().st_mtime_ns, item[0])): + if total <= limit: + break + path.unlink() + total -= sizes[name] + removed += 1 + return removed + + +def merge(destination, source): + """Union results by native key; keep one complete, newest manifest per key.""" + destination_files = _native_entries(destination) + source_files = _native_entries(source) + for name, path in sorted(source_files.items()): + target = destination_files.get(name) + if target is not None: + existing, incoming = target.stat(), path.stat() + if _is_result(target) and _is_result(path): + # The same result key can have different serialization timestamps. + if incoming.st_mtime_ns > existing.st_mtime_ns: + os.utime(target, ns=(existing.st_atime_ns, incoming.st_mtime_ns)) + continue + if incoming.st_mtime_ns < existing.st_mtime_ns: + continue + target = Path(destination) / name + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(path, target) + + +def pack(output, *, environment=None, event=None, root=ROOT): + environment, event = _context(environment, event) + info, current, parents = _layers(environment, event, root) + delta = Path(info["delta-path"]) + _native_entries(delta) + removed = deduplicate(delta, [current, *parents]) + trimmed = trim(delta) + entries = _native_entries(delta) + output = Path(output).absolute() + if output != Path(info["delta-archive"]): + raise ValueError("Compiler delta archive must use the planned output path") + _files(output.parent) + output.parent.mkdir(parents=True, exist_ok=True) + output.unlink(missing_ok=True) + if entries: + # Cache entries are already compressed; tar preserves their LRU timestamps. + with tarfile.open(output, "w", format=tarfile.PAX_FORMAT) as archive: + for name, path in sorted(entries.items()): + member = tarfile.TarInfo(name) + status = path.stat() + member.size = status.st_size + member.mtime = status.st_mtime + member.mode = 0o644 + with path.open("rb") as source: + archive.addfile(member, source) + return {"has-content": str(bool(entries)).lower(), "removed": str(removed), "trimmed": str(trimmed)} + + +def _archive_members(archive): + members = [] + seen = set() + total = 0 + for member in archive: + if (not member.isfile() or not NATIVE_KEY.fullmatch(member.name) or member.name in seen + or member.size < 0 or not math.isfinite(member.mtime) or member.mtime < 0): + raise ValueError(f"Compiler delta archive contains an invalid native entry: {member.name!r}") + total += member.size + if total > LAYER_LIMIT or len(members) >= ARCHIVE_ENTRY_LIMIT: + raise ValueError("Compiler delta archive exceeds the layer limits") + seen.add(member.name) + members.append(member) + return members + + +def _merge_archives(current, directory): + archives = sorted(_files(directory).values()) + # Validate every contribution before changing the restored snapshot. + for path in archives: + if path.name != "delta.tar": + raise ValueError("Expected a compiler delta.tar artifact") + with tarfile.open(path, "r:") as archive: + _archive_members(archive) + for path in archives: + with tarfile.open(path, "r:") as archive: + members = _archive_members(archive) + with tempfile.TemporaryDirectory(prefix="compiler-delta-", dir=current.parent) as temporary: + incoming = Path(temporary) + for member in members: + target = incoming / member.name + target.parent.mkdir(parents=True, exist_ok=True) + with archive.extractfile(member) as source, target.open("wb") as output: + shutil.copyfileobj(source, output) + os.utime(target, (member.mtime, member.mtime)) + merge(current, incoming) + return len(archives) + + +def migration_plan(prefix, *, environment=None, event=None, root=ROOT): + environment, event = _context(environment, event) + info, current, _ = _layers(environment, event, root) + selected = environment["CHERYL_CCACHE_PREFIX"] + family = next(family for family in LEGACY_PREFIXES if selected.endswith("-" + family)) + runner, profiles = LEGACY_PREFIXES[family] + architecture = selected.split("-")[3] + allowed = {f"ccache-v2-{runner}-{architecture}-{profile}" for profile in profiles} + if prefix not in allowed: + raise ValueError("Legacy compiler cache must belong to the selected runner and compiler family") + legacy_prefix = f"{prefix}-{info['role']}-" + return {"path": str(current), "prefix": legacy_prefix, + "key": legacy_prefix + environment["GITHUB_RUN_ID"] + "-" + environment["GITHUB_RUN_ATTEMPT"]} + + +def import_legacy(*, environment=None, event=None, root=ROOT): + environment, event = _context(environment, event) + _, current, _ = _layers(environment, event, root) + staging = current.parent / "migration" + merge(staging, current) + entries = _native_entries(current) + for path in entries.values(): + path.unlink() + for name in METADATA_FILES: + (current / name).unlink(missing_ok=True) + return {"imported": str(len(entries))} + + +def publish(deltas, *, environment=None, event=None, root=ROOT): + environment, event = _context(environment, event) + _, current, parents = _layers(environment, event, root) + _native_entries(current) + current.mkdir(parents=True, exist_ok=True) + before = fingerprint(current) + initial_snapshot = bool(_native_entries(current)) or (current / EMPTY_MARKER).exists() + merge(current, current.parent / "migration") + archives = _merge_archives(current, deltas) + removed = deduplicate(current, parents) + trimmed = trim(current) + entries = _native_entries(current) + marker = current / EMPTY_MARKER + if entries: + marker.unlink(missing_ok=True) + else: + # Remember an initialized empty layer so legacy imports are not repeated. + marker.write_text("Current compiler cache layer is empty.\n", encoding="utf-8") + return {"changed": str(fingerprint(current) != before or not initial_snapshot).lower(), + "has-content": str(bool(entries) or marker.exists()).lower(), + "removed": str(removed), "trimmed": str(trimmed), "archives": str(archives)} + + +def _emit(values, destination): + for name, value in values.items(): + if any(char in str(value) for char in "\r\n"): + raise ValueError("Compiler cache output values must be single-line") + with Path(os.environ[destination]).open("a", encoding="utf-8") as output: + for name, value in values.items(): + output.write(f"{name}={value}\n") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + commands = parser.add_subparsers(dest="command", required=True) + describe = commands.add_parser("plan") + describe.add_argument("--prefix", required=True) + commands.add_parser("attach") + package = commands.add_parser("pack") + package.add_argument("--output", required=True) + publication = commands.add_parser("publish") + publication.add_argument("--deltas", required=True) + migration = commands.add_parser("migration-plan") + migration.add_argument("--prefix", required=True) + commands.add_parser("import") + args = parser.parse_args() + if args.command == "plan": + _emit(metadata(args.prefix), "GITHUB_OUTPUT") + _emit({"CHERYL_CCACHE_PREFIX": args.prefix}, "GITHUB_ENV") + elif args.command == "attach": + _emit(configure(), "GITHUB_ENV") + else: + if args.command == "pack": + result = pack(args.output) + elif args.command == "publish": + result = publish(args.deltas) + elif args.command == "migration-plan": + result = migration_plan(args.prefix) + else: + result = import_legacy() + _emit(result, "GITHUB_OUTPUT") + print("Compiler cache " + args.command + ": " + json.dumps(result, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/.github/ci/configure_components.py b/.github/ci/configure_components.py new file mode 100644 index 00000000..89ccaaa2 --- /dev/null +++ b/.github/ci/configure_components.py @@ -0,0 +1,109 @@ +"""Configure isolated component compositions without building source targets.""" + +import argparse +import os +from pathlib import Path +import subprocess + + +MODULES = { + "startup-neutral": ("runtime/startup", ["-DCHERYL_STARTUP_OPENGL=OFF"]), + "startup-opengl": ("runtime/startup", ["-DCHERYL_STARTUP_OPENGL=ON"]), + "native": ("platform/native-glfw", []), + "opengl": ("graphics/opengl", []), + "tgui": ("ui/tgui", []), + "rmlui": ("ui/rmlui", []), + "audio": ("audio/miniaudio", []), + "workers-json": ("runtime/workers", ["-DCHERYL_BUILD_WORKER_TOPOLOGY_HWLOC=OFF", "-DCHERYL_BUILD_WORKER_TOPOLOGY_JSON=ON"]), + "workers-native": ("runtime/workers", ["-DCHERYL_BUILD_WORKER_TOPOLOGY_HWLOC=ON", "-DCHERYL_BUILD_WORKER_TOPOLOGY_JSON=OFF"]), +} + + +def fixture(source, destination, *, supplied): + destination.mkdir(parents=True) + selections = ("STARTUP", "WORKERS", "NATIVE_GLFW", "OPENGL", "UI_TGUI", "UI_RMLUI", "AUDIO_MINIAUDIO", + "DEBUG_TERMINAL_LINUX", "DEMO", "FIRST_GAME", "TUTORIAL_GAME", "SDK", "DOCS", "ALL_TESTS", "ACCEPTANCE_TESTS") + lines = ['cmake_minimum_required(VERSION 3.28)', 'project(component_host LANGUAGES C CXX)', + 'set(BUILD_SHARED_LIBS ON)', 'set(CMAKE_POSITION_INDEPENDENT_CODE OFF)'] + lines += [f'set(CHERYL_BUILD_{name} OFF)' for name in selections] + lines += [f'set(CHERYL_BUILD_TESTS {"OFF" if supplied else "ON"})', + f'set(CHERYL_BUILD_CONSUMER_TESTS {"OFF" if supplied else "ON"})', + f'add_subdirectory("{source.as_posix()}" engine EXCLUDE_FROM_ALL)'] + lines += ['if(TARGET Cheryl::Workers OR TARGET Cheryl::Startup OR TARGET Cheryl::NativeGLFW)', + ' message(FATAL_ERROR "An omitted component was created")', 'endif()'] + if supplied: + lines += ['set(CHERYL_STARTUP_OPENGL OFF)', + f'add_subdirectory("{source.as_posix()}/projects/modules/runtime/startup" startup EXCLUDE_FROM_ALL)', + 'get_target_property(before cheryl_assembly INTERFACE_LINK_LIBRARIES)', + 'if("module_startup" IN_LIST before)', + ' message(FATAL_ERROR "Available supplied module was selected implicitly")', 'endif()', + 'cheryl_select_public_component(Cheryl::Startup)', + 'cheryl_select_public_component(Cheryl::Startup)', + 'get_target_property(after cheryl_assembly INTERFACE_LINK_LIBRARIES)', + 'list(FILTER after EXCLUDE REGEX "^::@")', + 'if(NOT after STREQUAL "Cheryl::Engine;module_startup")', + ' message(FATAL_ERROR "Supplied assembly selection is incorrect: ${after}")', 'endif()', + 'if(NOT BUILD_SHARED_LIBS OR CMAKE_POSITION_INDEPENDENT_CODE)', + ' message(FATAL_ERROR "Component configuration changed host settings")', 'endif()'] + else: + lines += ['if(NOT CHERYL_BUILD_WORKER_TOPOLOGY_HWLOC OR NOT CHERYL_BUILD_WORKER_TOPOLOGY_JSON)', + ' message(FATAL_ERROR "Legacy provider defaults were unexpectedly rewritten")', 'endif()'] + (destination / "CMakeLists.txt").write_text("\n".join(lines) + "\n", encoding="utf-8") + + +def main(arguments=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--source", type=Path, default=Path(__file__).resolve().parents[2]) + parser.add_argument("--build-root", type=Path, default=Path("build")) + parser.add_argument("--vcpkg-root", type=Path, required=True) + parser.add_argument("--installed-dir", type=Path, required=True) + parser.add_argument("--triplet", default="x64-windows" if os.name == "nt" else "x64-linux-dynamic") + parser.add_argument("--case", action="append", choices=["engine-only", "supplied", *MODULES]) + args = parser.parse_args(arguments) + source = args.source.resolve() + build_root = args.build_root.resolve() + for name in args.case or ["engine-only", *MODULES, "supplied"]: + build = build_root / ("sdk-config-" + name) + if build.exists(): + raise ValueError(f"Use a fresh matrix output directory; preserving existing files: {build}") + if name in {"engine-only", "supplied"}: + entrypoint = build_root / ("sdk-config-" + name + "-source") + fixture(source, entrypoint, supplied=name == "supplied") + options = [] + else: + directory, options = MODULES[name] + entrypoint = source / "projects/modules" / directory + command = ["cmake", "-S", str(entrypoint), "-B", str(build), "-G", "Ninja", + "-DCMAKE_BUILD_TYPE=Release", "-DCHERYL_BUILD_SHARED_LIBS=ON", + f"-DCMAKE_TOOLCHAIN_FILE={args.vcpkg_root.resolve() / 'scripts/buildsystems/vcpkg.cmake'}", + f"-DVCPKG_INSTALLED_DIR={args.installed_dir.resolve()}", f"-DVCPKG_TARGET_TRIPLET={args.triplet}", + f"-DVCPKG_MANIFEST_DIR={source / '.github/ci'}", "-DVCPKG_MANIFEST_INSTALL=OFF", + f"-DCMAKE_PROJECT_INCLUDE={source / 'tools/dependencies/prepared.cmake'}", + "-DCHERYL_BUILD_TESTS=OFF", "-DCHERYL_BUILD_CONSUMER_TESTS=OFF", "-DCHERYL_BUILD_SDK=OFF", *options] + if name in {"native", "opengl", "startup-opengl"}: + command += ["-DGAINPUT_ENABLE_HID=OFF", "-DGLFW_BUILD_WAYLAND=OFF"] + if name in {"opengl", "startup-opengl"}: + command.append(f"-DCHERYL_NATIVE_GLFW_SOURCE={source / 'projects/modules/platform/native-glfw'}") + if name == "startup-opengl": + command.append(f"-DCHERYL_OPENGL_SOURCE={source / 'projects/modules/graphics/opengl'}") + print("+ " + subprocess.list2cmdline(command), flush=True) + build.mkdir(parents=True) + result = subprocess.run(command, text=True, capture_output=True) + (build / "configure.log").write_text(result.stdout + result.stderr, encoding="utf-8") + if result.returncode: + print(result.stdout + result.stderr, flush=True) + result.check_returncode() + if name in {"tgui", "rmlui"}: + # Upstream cache initializers must preserve the owned font-service + # configuration after normal variable scopes disappear. + print("+ " + subprocess.list2cmdline(command) + " (reconfigure)", flush=True) + result = subprocess.run(command, text=True, capture_output=True) + (build / "reconfigure.log").write_text(result.stdout + result.stderr, encoding="utf-8") + if result.returncode: + print(result.stdout + result.stderr, flush=True) + result.check_returncode() + print(f"Configured {name}; no source targets built.", flush=True) + + +if __name__ == "__main__": + main() diff --git a/.github/ci/dependencies.py b/.github/ci/dependencies.py index a8c65764..368849b7 100644 --- a/.github/ci/dependencies.py +++ b/.github/ci/dependencies.py @@ -9,18 +9,88 @@ import shutil import subprocess import sys +import time ROOT = Path(__file__).resolve().parents[2] -CCACHE_VERSION = "4.14" -CCACHE_PACKAGE_VERSION = "4.14.0" -APT_COMMON = ["ccache", "ninja-build", "pkg-config", "g++-14", "libfreetype-dev", "libharfbuzz-dev", - "libicu-dev", "libhwloc-dev", "libdw-dev", "libelf-dev", "fonts-urw-base35"] -APT_EXTRA = { - "headless": ["clang-18", "clang-tools-18"], - "modules": ["libgl-dev", "libx11-dev", "libxrandr-dev", "libxinerama-dev", "libxcursor-dev", - "libxi-dev", "libxext-dev", "python3-jinja2"], -} +WINDOWS_TRIPLET = "x64-windows" +LINUX_TRIPLET = "x64-linux-dynamic" +PREPARED_STATE = ".cheryl-prepared-vcpkg.json" + + +def windows_features(profile): + if profile not in {"headless", "modules"}: + raise ValueError("Windows dependencies require the headless or modules profile") + return ["hwloc", "native"] if profile == "modules" else ["hwloc"] + + +def dependency_scope(*, environment=None): + environment = os.environ if environment is None else environment + ref = environment.get("GITHUB_REF") + event = environment.get("GITHUB_EVENT_NAME") + branch = environment.get("GITHUB_BASE_REF") if event == "pull_request" else { + "refs/heads/main": "main", "refs/heads/develop": "develop" + }.get(ref) + writer = (environment.get("GITHUB_REPOSITORY") == "cppcooper/cheryl-engine" + and ref in {"refs/heads/main", "refs/heads/develop"} + and event in {"push", "workflow_dispatch"}) + return {"dependency-role": "develop" if branch == "develop" else "main", + "dependency-cache-writer": str(writer).lower()} + + +def environment_configuration(): + with (ROOT / ".github/ci/environment.json").open(encoding="utf-8") as source: + return json.load(source) + + +def environment_metadata(profile, *, environment=None, configuration=None): + """Describe immutable environment caches without inspecting Git or source revisions.""" + environment = os.environ if environment is None else environment + configuration = environment_configuration() if configuration is None else configuration + if profile not in {"headless", "modules", "docs"}: + raise ValueError("Unknown environment cache profile") + revision = configuration.get("cache_version") if isinstance(configuration, dict) else None + if type(revision) is not int or revision <= 0: + raise ValueError("The environment cache version must be a positive integer") + runner = [environment.get(name) for name in ("RUNNER_OS", "RUNNER_ARCH")] + if any(not isinstance(value, str) or not value or any( + not (character.isascii() and (character.isalnum() or character in "_.-")) + for character in value) for value in runner): + raise ValueError("Environment cache keys require a runner OS and architecture") + image = {name: environment.get(name, "") for name in ("ImageOS", "ImageVersion")} + if any(not isinstance(value, str) for value in image.values()): + raise ValueError("Runner image identities must be strings") + identity = digest(json.dumps({"configuration": configuration, "image": image}, + sort_keys=True, separators=(",", ":")).encode()) + writer = (environment.get("GITHUB_REPOSITORY") == "cppcooper/cheryl-engine" + and environment.get("GITHUB_REF") == "refs/heads/main" + and environment.get("GITHUB_EVENT_NAME") in {"push", "workflow_dispatch"}) + return {"environment-prefix": f"actions-env-cache-v{revision}-{'-'.join(runner)}-{profile}-{identity}", + "environment-cache-writer": str(writer).lower()} + + +def environment_keys(profile, baseline, requirements, *, python_tag, environment=None, configuration=None): + if not isinstance(baseline, str) or not baseline: + raise ValueError("The vcpkg tool baseline must be nonempty") + if not isinstance(requirements, bytes) or not isinstance(python_tag, str) or not python_tag or any( + not (character.isascii() and (character.isalnum() or character in "_.-")) for character in python_tag): + raise ValueError("Python cache keys require requirement bytes and an interpreter ABI tag") + values = environment_metadata(profile, environment=environment, configuration=configuration) + prefix = values["environment-prefix"] + values.update({"tools-key": f"{prefix}-tools-{digest(baseline.encode())}", + "pip-key": f"{prefix}-pip-{python_tag}-{digest(requirements)}"}) + return values + + +def apt_cache_key(profile, uris, *, environment=None, configuration=None): + if isinstance(uris, str): + raise ValueError("APT cache keys require a sequence of resolved package URI lines") + uris = tuple(uris) + if any(not isinstance(value, str) for value in uris): + raise ValueError("APT cache keys require a sequence of resolved package URI lines") + prefix = environment_metadata(profile, environment=environment, configuration=configuration)["environment-prefix"] + downloads = digest("\n".join(sorted(uris)).encode()) + return f"{prefix}-apt-{downloads}" def run(command, *, capture=False, cwd=ROOT, environment=None): @@ -45,6 +115,8 @@ def digest(value): def metadata(profile): + if profile not in {"headless", "modules"}: + raise ValueError("Dependency preparation requires the headless or modules profile") runner = f"{os.environ['RUNNER_OS']}-{os.environ['RUNNER_ARCH']}" manifest = (ROOT / ".github/ci/vcpkg.json").read_bytes() baseline = json.loads(manifest)["builtin-baseline"] @@ -58,46 +130,54 @@ def metadata(profile): cache = ROOT / "build/cache/dependencies" vcpkg = Path(os.environ["RUNNER_TEMP"]).resolve() / "vcpkg" ccache = ROOT / "build/cache/tools/ccache" - dependency_prefix = f"dependencies-v1-{runner}-{profile}-" - dependency_tag = digest(manifest + b"\0" + requirements) - run_id = f"{os.environ['GITHUB_RUN_ID']}-{os.environ['GITHUB_RUN_ATTEMPT']}" - dependency_key = f"{dependency_prefix}{dependency_tag}-{run_id}" - pip_prefix = f"pip-v1-{runner}-{sys.implementation.cache_tag}-" + scope = dependency_scope() + role = scope["dependency-role"] + source_prefix = f"build-deps-v2-{runner}-{role}-sources-" + installed = ROOT / "build/ci/vcpkg_installed" pip = ROOT / "build/cache/pip" emit("GITHUB_OUTPUT", { - "submodule-key": f"submodule-git-v1-{runner}-{submodules}", + **environment_keys(profile, baseline, requirements, python_tag=sys.implementation.cache_tag), + **scope, + "submodule-key": source_prefix + submodules, + "submodule-prefix": source_prefix, + "submodule-main-prefix": f"build-deps-v2-{runner}-main-sources-", "submodule-path": modules_path.resolve().as_posix(), - "tools-key": f"windows-tools-v1-{runner}-{baseline}-ccache-{CCACHE_PACKAGE_VERSION}", "vcpkg-root": vcpkg.as_posix(), + "vcpkg-installed-dir": installed.as_posix(), "ccache-tools": ccache.as_posix(), "dependency-path": cache.as_posix(), - "dependency-key": dependency_key, - "dependency-exact-prefix": f"{dependency_prefix}{dependency_tag}-", - "dependency-profile-prefix": dependency_prefix, - "dependency-runner-prefix": f"dependencies-v1-{runner}-", "pip-path": pip.as_posix(), - "pip-key": f"{pip_prefix}{digest(requirements)}-{run_id}", - "pip-exact-prefix": f"{pip_prefix}{digest(requirements)}-", - "pip-prefix": pip_prefix, + "native-cache-path": (ROOT / "build/cache/vcpkg").as_posix(), + "native-cache-key": f"shared-native-v2-{runner}-{role}-{digest(manifest)}", + "native-cache-prefix": f"shared-native-v2-{runner}-main-", }) environment = { "CHERYL_CI_DEPENDENCY_CACHE_DIR": cache.as_posix(), - "CHERYL_CI_DEPENDENCY_CACHE_KEY": dependency_key, "CHERYL_CI_VCPKG_ROOT": vcpkg.as_posix(), + "CHERYL_CI_VCPKG_INSTALLED_DIR": installed.as_posix(), "CHERYL_CI_CCACHE_TOOLS": ccache.as_posix(), } if os.environ["RUNNER_OS"] == "Windows": - archives = cache / "vcpkg/archives" - downloads = cache / "vcpkg/downloads" + pool = ROOT / "build/cache/vcpkg" + archives = pool / "archives" + downloads = pool / "downloads" for directory in (archives, downloads, pip): directory.mkdir(parents=True, exist_ok=True) environment.update({ + "CHERYL_CI_VCPKG_CACHE_DIR": pool.as_posix(), "VCPKG_BINARY_SOURCES": f"clear;files,{archives.as_posix()},readwrite", "VCPKG_DOWNLOADS": downloads.as_posix(), "PIP_CACHE_DIR": pip.as_posix(), }) else: (cache / "apt").mkdir(parents=True, exist_ok=True) + pool = ROOT / "build/cache/vcpkg" + archives = pool / "archives" + downloads = pool / "downloads" + for directory in (archives, downloads): + directory.mkdir(parents=True, exist_ok=True) + environment.update({"VCPKG_BINARY_SOURCES": f"clear;files,{archives.as_posix()},readwrite", + "VCPKG_DOWNLOADS": downloads.as_posix()}) emit("GITHUB_ENV", environment) @@ -120,6 +200,7 @@ def submodules(): def windows_tools(): + versions = environment_configuration()["windows"] baseline = json.loads((ROOT / ".github/ci/vcpkg.json").read_text(encoding="utf-8"))["builtin-baseline"] vcpkg = Path(os.environ["CHERYL_CI_VCPKG_ROOT"]) if not (vcpkg / ".git").exists(): @@ -133,25 +214,161 @@ def windows_tools(): tools = Path(os.environ["CHERYL_CI_CCACHE_TOOLS"]) executable = tools / "ccache.exe" if not executable.is_file(): - run(["choco", "install", "ccache", f"--version={CCACHE_PACKAGE_VERSION}", "--yes", "--no-progress"]) + for attempt in range(3): + try: + run(["choco", "install", "ccache", f"--version={versions['ccache_package_version']}", "--yes", "--no-progress"]) + break + except subprocess.CalledProcessError: + if attempt == 2: + raise + delay = 10 * (attempt + 1) + print(f"ccache installation failed; retrying in {delay} seconds (attempt {attempt + 2}/3).", flush=True) + time.sleep(delay) installed = Path(os.environ["ChocolateyInstall"]) / "lib/ccache/tools" candidates = sorted(installed.rglob("ccache.exe")) if not candidates: raise RuntimeError("ccache executable is unavailable") shutil.copytree(candidates[0].parent, tools, dirs_exist_ok=True) version = run([str(executable), "--version"], capture=True).splitlines()[0] - if version != f"ccache version {CCACHE_VERSION}": + if version != f"ccache version {versions['ccache_version']}": raise RuntimeError(f"Unexpected ccache version: {version}") print(version, flush=True) with Path(os.environ["GITHUB_PATH"]).open("a", encoding="utf-8") as output: output.write(f"{tools}\n") +def windows_install(profile): + features = windows_features(profile) + vcpkg = Path(os.environ["CHERYL_CI_VCPKG_ROOT"]).resolve() / "vcpkg.exe" + command = [str(vcpkg), "install", f"--triplet={WINDOWS_TRIPLET}", f"--host-triplet={WINDOWS_TRIPLET}", + f"--x-manifest-root={(ROOT / '.github/ci').as_posix()}", + f"--x-install-root={(ROOT / 'build/ci/vcpkg_installed').as_posix()}", + "--x-no-default-features", *[f"--x-feature={feature}" for feature in features]] + run(command, cwd=ROOT) + + +def prepared_identity(features, *, triplet=WINDOWS_TRIPLET): + manifest_bytes = (ROOT / ".github/ci/vcpkg.json").read_bytes() + manifest = json.loads(manifest_bytes) + baseline = manifest.get("builtin-baseline") + if not isinstance(baseline, str) or len(baseline) != 40 or any( + character not in "0123456789abcdef" for character in baseline): + raise ValueError("Prepared native dependencies require a pinned vcpkg baseline") + revision = os.environ.get("GITHUB_SHA", "") + run_id = os.environ.get("GITHUB_RUN_ID", "") + if len(revision) not in {40, 64} or any(character not in "0123456789abcdef" for character in revision): + raise ValueError("Prepared native dependencies require a source revision") + if not run_id.isascii() or not run_id.isdecimal() or int(run_id) <= 0 or str(int(run_id)) != run_id: + raise ValueError("Prepared native dependencies require a positive source run ID") + return {"schema": 1, "manifest_sha256": digest(manifest_bytes), "baseline": baseline, + "target_triplet": triplet, "host_triplet": triplet, + "features": features, "source_revision": revision, "source_run_id": run_id} + + +def installed_status(status, *, triplet=WINDOWS_TRIPLET): + installed = set() + fields = {} + for line in [*status.decode("utf-8").splitlines(), ""]: + if not line.strip(): + if fields: + package = fields.get("Package") + parts = fields.get("Status", "").split() + if not package or not fields.get("Architecture") or len(parts) != 3 or parts[0] not in { + "install", "hold", "deinstall", "purge"} or parts[1] != "ok" or parts[2] not in { + "installed", "not-installed", "half-installed"}: + raise ValueError(f"Malformed vcpkg installed package status: {package or ''}") + if fields.get("Architecture") == triplet: + if parts[2] == "half-installed": + raise ValueError(f"Prepared native dependencies contain an incomplete installed package: {package or ''}") + if parts[2] == "installed" and (not fields.get("Feature") or fields.get("Feature") == "core"): + installed.add(package) + fields = {} + elif not line[0].isspace(): + name, separator, value = line.partition(":") + if not separator or name in fields: + raise ValueError("Malformed vcpkg installed package status") + fields[name] = value.strip() + return installed + + +def check_prepared_tree(installed, *, triplet=WINDOWS_TRIPLET): + # vcpkg resolves platform and host dependencies; this checks the prepared installation for transfer. + status = (installed / "vcpkg/status").read_bytes() + packages = installed_status(status, triplet=triplet) + if not packages: + raise ValueError(f"Prepared native dependencies contain no installed core packages for {triplet}") + directories = ["include", "lib", "share", "debug/lib"] + if "windows" in triplet: + directories += ["bin", "debug/bin"] + for directory in directories: + path = installed / triplet / directory + if not path.is_dir() or not any(file.is_file() for file in path.rglob("*")): + raise ValueError(f"Prepared native dependencies are missing files in {triplet}/{directory}") + return digest(status) + + +def native_stamp(profile, *, triplet=WINDOWS_TRIPLET): + features = windows_features(profile) if triplet == WINDOWS_TRIPLET else ["hwloc"] + identity = prepared_identity(features, triplet=triplet) + installed = ROOT / "build/ci/vcpkg_installed" + identity["status_sha256"] = check_prepared_tree(installed, triplet=triplet) + (installed / PREPARED_STATE).write_text(json.dumps(identity, sort_keys=True) + "\n", encoding="utf-8") + print(f"Prepared native dependencies: {triplet}; features: {', '.join(features)}", flush=True) + + +def native_verify(profile, *, triplet=WINDOWS_TRIPLET): + required_features = windows_features(profile) if triplet == WINDOWS_TRIPLET else ["hwloc"] + installed = ROOT / "build/ci/vcpkg_installed" + state = json.loads((installed / PREPARED_STATE).read_text(encoding="utf-8")) + fields = {"schema", "manifest_sha256", "baseline", "target_triplet", "host_triplet", "features", "status_sha256", + "source_revision", "source_run_id"} + if not isinstance(state, dict) or set(state) != fields or type(state["schema"]) is not int or state["schema"] != 1: + raise ValueError("Unsupported prepared native dependency metadata") + features = state["features"] + if features not in (windows_features("headless"), windows_features("modules")) or not set(required_features).issubset(features): + raise ValueError("Prepared native dependencies do not provide the requested features") + identity = prepared_identity(features, triplet=triplet) + if any(state[name] != value for name, value in identity.items()): + raise ValueError("Prepared native dependencies do not match this manifest or triplet, source revision, or workflow run") + if state["status_sha256"] != check_prepared_tree(installed, triplet=triplet): + raise ValueError("Prepared native dependency status changed during transfer") + print(f"Verified prepared native dependencies for {profile}; no installation required.", flush=True) + + +def windows_stamp(profile): + native_stamp(profile) + + +def windows_verify(profile): + native_verify(profile) + + +def linux_tools(): + baseline = json.loads((ROOT / ".github/ci/vcpkg.json").read_text(encoding="utf-8"))["builtin-baseline"] + vcpkg = Path(os.environ["CHERYL_CI_VCPKG_ROOT"]) + if not (vcpkg / ".git").exists(): + run(["git", "clone", "--filter=blob:none", "--no-checkout", + "https://github.com/microsoft/vcpkg.git", str(vcpkg)]) + run(["git", "-C", str(vcpkg), "checkout", "--detach", baseline]) + if not (vcpkg / "vcpkg").is_file(): + run(["bash", "bootstrap-vcpkg.sh", "-disableMetrics"], cwd=vcpkg) + run([str(vcpkg / "vcpkg"), "version"]) + + +def linux_install(profile): + if profile not in {"headless", "modules"}: + raise ValueError("Linux dependencies require the headless or modules profile") + run([sys.executable, str(ROOT / "tools/dependencies/prepare.py"), + "--vcpkg-root", os.environ["CHERYL_CI_VCPKG_ROOT"], + "--installed-dir", os.environ["CHERYL_CI_VCPKG_INSTALLED_DIR"]]) + + def apt_command(profile): + packages = environment_configuration()["linux"] cache = Path(os.environ["CHERYL_CI_DEPENDENCY_CACHE_DIR"]) / "apt" return ["sudo", "apt-get", "-o", f"Dir::Cache::archives={cache}", "-o", "APT::Keep-Downloaded-Packages=true", "--yes", "install", - *APT_COMMON, *APT_EXTRA[profile]] + *packages["apt_common"], *packages["apt_extra"][profile]] def apt_plan(profile): @@ -161,10 +378,9 @@ def apt_plan(profile): command.insert(2, "--print-uris") plan = run(command, capture=True, environment=environment) uris = sorted(line for line in plan.splitlines() if line.startswith("'")) - downloads = digest("\n".join(uris).encode()) emit("GITHUB_OUTPUT", { "needed": str(bool(uris)).lower(), - "key": f"apt-debs-v1-{os.environ['RUNNER_OS']}-{os.environ['RUNNER_ARCH']}-{downloads}", + "key": apt_cache_key(profile, uris), }) @@ -187,17 +403,35 @@ def cache_state(name, snapshot): def main(): parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("operation", choices=("metadata", "submodules", "windows-tools", "apt-plan", "apt-install", "cache-state")) - parser.add_argument("--profile", choices=("headless", "modules"), default="headless") + parser.add_argument("operation", choices=("metadata", "environment-prefix", "submodules", "windows-tools", "windows-install", "windows-stamp", "windows-verify", "linux-tools", "linux-install", "linux-stamp", "linux-verify", "apt-plan", "apt-install", "cache-state")) + parser.add_argument("--profile", choices=("headless", "modules", "docs"), default="headless") parser.add_argument("--cache", choices=("vcpkg", "pip"), default="vcpkg") parser.add_argument("--snapshot", action="store_true") args = parser.parse_args() - if args.operation == "metadata": + if args.profile == "docs" and args.operation != "environment-prefix": + parser.error("The docs profile only describes environment cache prefixes") + if args.operation == "environment-prefix": + emit("GITHUB_OUTPUT", environment_metadata(args.profile)) + elif args.operation == "metadata": metadata(args.profile) elif args.operation == "submodules": submodules() elif args.operation == "windows-tools": windows_tools() + elif args.operation == "windows-install": + windows_install(args.profile) + elif args.operation == "windows-stamp": + windows_stamp(args.profile) + elif args.operation == "windows-verify": + windows_verify(args.profile) + elif args.operation == "linux-tools": + linux_tools() + elif args.operation == "linux-install": + linux_install(args.profile) + elif args.operation == "linux-stamp": + native_stamp(args.profile, triplet=LINUX_TRIPLET) + elif args.operation == "linux-verify": + native_verify(args.profile, triplet=LINUX_TRIPLET) elif args.operation == "apt-plan": apt_plan(args.profile) elif args.operation == "apt-install": diff --git a/.github/ci/environment.json b/.github/ci/environment.json new file mode 100644 index 00000000..8669cd39 --- /dev/null +++ b/.github/ci/environment.json @@ -0,0 +1,40 @@ +{ + "cache_version": 5, + "windows": { + "ccache_version": "4.14", + "ccache_package_version": "4.14.0" + }, + "linux": { + "apt_common": [ + "ccache", + "ninja-build", + "pkg-config", + "patchelf", + "g++-14", + "libdw-dev", + "libelf-dev", + "fonts-urw-base35", + "libfreetype-dev", + "libharfbuzz-dev", + "libicu-dev", + "libhwloc-dev" + ], + "apt_extra": { + "headless": [ + "clang-18", + "clang-tools-18" + ], + "modules": [ + "libgl-dev", + "libx11-dev", + "libxrandr-dev", + "libxinerama-dev", + "libxcursor-dev", + "libxi-dev", + "libxext-dev", + "python3-jinja2", + "libudev-dev" + ] + } + } +} diff --git a/.github/ci/github_api.py b/.github/ci/github_api.py new file mode 100644 index 00000000..9a67521b --- /dev/null +++ b/.github/ci/github_api.py @@ -0,0 +1,93 @@ +"""GitHub.com API access for repository-owned workflow automation.""" + +import json +import os +from pathlib import Path +import re +from urllib.error import HTTPError, URLError +from urllib.parse import urlencode, urlsplit +from urllib.request import HTTPRedirectHandler, Request, build_opener + + +class GitHubError(RuntimeError): + def __init__(self, method, path, status): + self.status = status + super().__init__(f"GitHub API {method} {path} returned HTTP {status}") + + +class NoRedirect(HTTPRedirectHandler): + def redirect_request(self, request, file, code, message, headers, new_url): + return None + + +class GitHubApi: + def __init__(self, repository, token): + if (not isinstance(repository, str) or not re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", repository) + or any(part in {".", ".."} for part in repository.split("/"))): + raise ValueError("GITHUB_REPOSITORY must be owner/repository") + if not isinstance(token, str) or not token or any(character.isspace() for character in token): + raise ValueError("A nonempty GitHub token without whitespace is required") + self.repository = repository + self.token = token + self.base = "https://api.github.com/repos/" + repository + self.opener = build_opener(NoRedirect()) + + def _send(self, method, path, url, data=None, *, content_type="application/json", length=None, missing_ok=False): + headers = {"Accept": "application/vnd.github+json", "Authorization": "Bearer " + self.token, + "X-GitHub-Api-Version": "2026-03-10", "User-Agent": "cheryl-release-automation"} + if data is not None: + headers["Content-Type"] = content_type + if length is not None: + headers["Content-Length"] = str(length) + request = Request(url, method=method, data=data, headers=headers) + try: + with self.opener.open(request, timeout=120) as response: + contents = response.read() + except HTTPError as error: + status = error.code + error.close() + if status == 404 and missing_ok: + return None + raise GitHubError(method, path, status) from None + except (URLError, OSError): + raise RuntimeError(f"GitHub API {method} {path} could not be reached") from None + result = json.loads(contents) if contents else None + if result is not None and not isinstance(result, (dict, list)): + raise ValueError("The GitHub API returned an unexpected response") + return result + + def request(self, method, path, body=None, *, missing_ok=False, **query): + if (method not in {"GET", "POST", "PATCH", "PUT", "DELETE"} or not isinstance(path, str) + or not path.startswith("/") or any(character.isspace() for character in path) + or any(value in path for value in ("//", "?", "#")) or ".." in path.split("/")): + raise ValueError("Expected a repository-relative GitHub API request") + data = json.dumps(body).encode("utf-8") if body is not None else None + url = self.base + path + ("?" + urlencode(query) if query else "") + return self._send(method, path, url, data, missing_ok=missing_ok) + + def pages(self, path, field=None, **query): + entries = [] + for page in range(1, 1001): + value = self.request("GET", path, per_page=100, page=page, **query) + batch = value.get(field) if field and isinstance(value, dict) else value + if not isinstance(batch, list): + raise ValueError("The GitHub API returned an unexpected page") + entries.extend(batch) + if len(batch) < 100: + return entries + raise RuntimeError("GitHub API pagination exceeded the automation limit") + + def upload(self, upload_url, path): + url = upload_url.split("{", 1)[0] + parsed = urlsplit(url) + if (parsed.scheme != "https" or parsed.netloc != "uploads.github.com" or parsed.query or parsed.fragment + or not re.fullmatch(r"/repos/" + re.escape(self.repository) + r"/releases/[1-9][0-9]*/assets", parsed.path)): + raise ValueError("Expected this repository's GitHub release upload URL") + path = Path(path) + with path.open("rb") as source: + size = os.fstat(source.fileno()).st_size + result = self._send("POST", parsed.path, url + "?" + urlencode({"name": path.name}), source, + content_type="application/octet-stream", length=size) + if not isinstance(result, dict): + raise ValueError("The GitHub API did not confirm the uploaded asset") + return result diff --git a/.github/ci/native_notices.py b/.github/ci/native_notices.py new file mode 100644 index 00000000..6ca9e629 --- /dev/null +++ b/.github/ci/native_notices.py @@ -0,0 +1,216 @@ +"""Collect installed Linux native-library notices for a relocatable SDK.""" + +import argparse +import json +import os +from pathlib import Path +import re +import subprocess +import sys + + +def package_owner(library, *, run=subprocess.run): + """Ask the installed package database about both a link and its real file.""" + for path in dict.fromkeys((library, library.resolve())): + for command in (["dpkg-query", "-S", str(path)], ["pacman", "-Qqo", str(path)]): + try: + result = run(command, capture_output=True, text=True, check=False) + except FileNotFoundError: + continue + if result.returncode: + continue + if command[0] == "dpkg-query": + owners = {line.partition(": ")[0] for line in result.stdout.splitlines() if ": " in line} + else: + owners = set(result.stdout.splitlines()) + owners = {name for name in owners if re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9.+:_-]*", name)} + if len(owners) > 1: + raise ValueError(f"Ambiguous package ownership for {library.name}: {', '.join(sorted(owners))}") + if owners: + return owners.pop() + return None + + +def _override_files(directory): + if not directory.is_dir(): + return [] + files = [] + for path in sorted(directory.iterdir()): + if re.match(r"(?i)^(copyright$|licen[cs]e|copying)", path.name): + files.extend([path] if path.is_file() else sorted(child for child in path.rglob("*") if child.is_file())) + return files + + +def _provider(library, package, native_prefix): + if native_prefix is None: + return None + share = native_prefix / "share" + if not share.is_dir(): + return None + stem = re.sub(r"(?:\.so(?:\..*)?|\.dll|\.dylib)$", "", library.name, flags=re.IGNORECASE).removeprefix("lib") + candidates = [package.split(":")[0]] if package else [] + candidates.extend((stem, "lib" + stem)) + for name in dict.fromkeys(candidates): + directory = share / name + if _override_files(directory): + return directory + # Common provider names omit ABI suffixes, e.g. icuuc -> icu, + # brotlidec -> brotli, or glib-2.0 -> glib2. + normalized = re.sub(r"[^a-z]", "", stem.lower()) + matches = [] + for directory in sorted(share.iterdir()): + name = re.sub(r"[^a-z]", "", directory.name.lower().removeprefix("lib")) + if name and (normalized == name or normalized.startswith(name)) and _override_files(directory): + matches.append(directory) + if len(matches) > 1: + raise ValueError(f"Ambiguous native-prefix notices for {library.name}: {', '.join(p.name for p in matches)}") + return matches[0] if matches else None + + +def _references(source, system_root): + contents = source.read_text(encoding="utf-8", errors="replace") + references = [] + for name in re.findall(r"/usr/share/common-licenses/([A-Za-z0-9.+_-]+)", contents): + references.append(system_root / "usr/share/common-licenses" / name.rstrip(".")) + for name in re.findall( + r"(?i)\b(?:see|file|in|available\s+(?:in|at))\s+(?:the\s+)?(?:file\s+)?[\"'`]?" + r"((?:\.\.?/)*[A-Za-z0-9_./+-]*(?:COPYING|LICENSE|copyright|LGPL|GPL)[A-Za-z0-9_./+-]*)", contents): + if not name.startswith("/"): + references.append(source.parent / name) + # Git source archives sometimes materialize a license symlink as its + # relative target text, e.g. COPYING contains LICENSES/LGPL-2.1-or-later.txt. + for name in re.findall(r"(?mi)^\s*((?:\.\.?/)*[A-Za-z0-9_./+-]*(?:LICENSE|COPYING|LGPL|GPL)" + r"[A-Za-z0-9_./+-]*)\s*$", contents): + if not name.startswith("/"): + references.append(source.parent / name) + return [path for path in references if path.is_file()] + + +def _arch_license_files(package, system_root, *, run): + try: + result = run(["pacman", "-Qi", "--", package], capture_output=True, text=True, check=False, + env={**os.environ, "LC_ALL": "C"}) + except FileNotFoundError: + return [] + if result.returncode: + return [] + match = re.search(r"^Licenses\s*:[ \t]*([^\n]*(?:\n[ \t]+[^\n]*)*)", result.stdout, re.MULTILINE) + if not match or not re.fullmatch(r"[A-Za-z0-9.+_()\s-]+", match[1]): + return [] + files = [] + exception = False + for name in re.findall(r"[A-Za-z0-9][A-Za-z0-9.+_-]*", match[1]): + if name in {"AND", "OR"}: + continue + if name == "WITH": + exception = True + continue + directory = system_root / "usr/share/licenses/spdx" + if exception: + directory /= "exceptions" + path = directory / (name + ".txt") + if not path.is_file(): + return [] + files.append(path) + exception = False + return files if not exception else [] + + +def _notices(library, package, native_prefix, system_root, *, run=subprocess.run): + provider = _provider(library, package, native_prefix) + if provider: + return package or provider.name, provider, _override_files(provider), "native-prefix" + if package: + name = package.split(":")[0] + doc = system_root / "usr/share/doc" / name / "copyright" + licenses = system_root / "usr/share/licenses" / name + files = ([doc] if doc.is_file() else []) + files.extend(sorted(path for path in licenses.rglob("*") if path.is_file()) if licenses.is_dir() else []) + # Arch keeps standard license texts centrally instead of duplicating + # them for each package. Include every declared alternative and exception. + if not files: + files = _arch_license_files(package, system_root, run=run) + if files: + return package, None, files, "installed-package" + owner = package or "unknown package" + raise ValueError( + f"No native notices for {library.name} ({owner}); supply --native-prefix with " + f"share/{package.split(':')[0] if package else library.name.split('.so')[0].removeprefix('lib')}/" + "copyright, LICENSE*, or COPYING*") + + +def collect_notices(libraries, destination, *, native_prefix=None, system_root=Path("/"), + run=subprocess.run, query_packages=None): + """Collect notices, with filesystem/package-query injection for isolated checks.""" + destination, system_root = Path(destination), Path(system_root) + native_prefix = Path(native_prefix) if native_prefix is not None else None + query_packages = sys.platform != "win32" if query_packages is None else query_packages + output = {} + records, skipped = [], [] + for library in sorted(set(map(Path, libraries)), key=str): + if not library.is_file(): + raise ValueError(f"Native library is missing: {library.name}") + if re.match(r"(?i)^(?:lib)?(?:cheryl[._-]|cengine[._-]|" + r"cm(?:startup|workers|nativeglfw|opengl|tgui|rmlui|miniaudio|audiominiaudio)[._-]|" + r"glfw[\d._-]|tgui[._-]|rmlui[._-])", library.name): + skipped.append(library.name) + continue + provider = _provider(library, None, native_prefix) + package = provider.name if provider else package_owner(library, run=run) if query_packages else None + package, provider, seeds, origin = _notices(library, package, native_prefix, system_root, run=run) + directory = package.split(":")[0] + pending = [(path, None) for path in seeds] + seen, notice_names = set(), [] + for path, referenced in pending: + resolved = path.resolve() + if resolved in seen: + continue + seen.add(resolved) + if referenced: + relative = Path(directory) / "references" / path.name + elif provider: + relative = Path(directory) / path.relative_to(provider) + elif path.name == "copyright" and path.parent.parent.name == "doc": + relative = Path(directory) / "copyright" + elif path.is_relative_to(system_root / "usr/share/licenses/spdx"): + relative = Path(directory) / "licenses" / path.relative_to(system_root / "usr/share/licenses/spdx") + else: + relative = Path(directory) / "licenses" / path.relative_to(system_root / "usr/share/licenses" / directory) + data = path.read_bytes() + if relative in output and output[relative] != data: + raise ValueError(f"Conflicting native notice contents: {relative.as_posix()}") + output[relative] = data + notice_names.append(relative.as_posix()) + pending.extend((child, True) for child in _references(path, system_root)) + records.append({"library": library.name, "package": package, "origin": origin, "notices": sorted(notice_names)}) + # Resolve every library before writing, so a missing notice does not leave + # behind a partial provenance manifest that looks complete. + for relative, contents in sorted(output.items()): + target = destination / relative + if target.exists() and target.read_bytes() != contents: + raise ValueError(f"Native notice destination already differs: {relative.as_posix()}") + for relative, contents in sorted(output.items()): + target = destination / relative + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(contents) + provenance = {"schema": 1, "libraries": records, "already_in_sdk": sorted(skipped)} + destination.mkdir(parents=True, exist_ok=True) + (destination / "NATIVE-NOTICES.json").write_text(json.dumps(provenance, indent=2) + "\n", encoding="utf-8") + return provenance + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--destination", type=Path, required=True) + parser.add_argument("--native-prefix", type=Path) + parser.add_argument("libraries", nargs="+", type=Path) + args = parser.parse_args() + try: + result = collect_notices(args.libraries, args.destination, native_prefix=args.native_prefix) + except (OSError, ValueError) as error: + parser.error(str(error)) + print(json.dumps(result, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/.github/ci/package.py b/.github/ci/package.py index 57f08237..c6881792 100644 --- a/.github/ci/package.py +++ b/.github/ci/package.py @@ -4,8 +4,10 @@ import gzip import hashlib import json -from pathlib import Path +import os +from pathlib import Path, PurePosixPath import platform +import re import shutil import subprocess import tarfile @@ -13,6 +15,52 @@ import zipfile +def sdk_name(metadata): + platform_name = {"Linux": "linux", "Windows": "windows"}[metadata["system"]] + suffix = "-static" if metadata["linkage"] == "static" else "" + return f"cheryl-SDK-v{metadata['version']}-{platform_name}{suffix}" + + +def system_runtime(name, system): + if system == "Windows": + name = name.lower() + if (name.startswith(("api-ms-", "ext-ms-", "vcruntime", "msvcp", "msvcr", "concrt", "ucrtbase")) + or name in {"icu.dll", "opengl32.dll", "kernel32.dll", "kernelbase.dll", "ntdll.dll", "user32.dll", + "gdi32.dll", "advapi32.dll", "shell32.dll", "ole32.dll", "oleaut32.dll", "comdlg32.dll", + "comctl32.dll", "shlwapi.dll", "ws2_32.dll", "winmm.dll", "imm32.dll", "dwmapi.dll", + "setupapi.dll", "hid.dll", "cfgmgr32.dll", "bcrypt.dll", "version.dll", "normaliz.dll", + "dbghelp.dll", "psapi.dll"}): + return True + root = os.environ.get("SystemRoot") + return bool(root and any((Path(root) / directory / name).is_file() for directory in ("System32", "SysWOW64"))) + return bool(re.match( + r"^(?:ld-linux|ld64|ld-musl)|^lib(?:c|m|mvec|dl|pthread|rt|resolv|util|anl|BrokenLocale|nss_[A-Za-z0-9_]+|stdc\+\+|gcc_s|" + r"GL|GLX|EGL|OpenGL|GLdispatch|GLESv[0-9]+|X[A-Za-z0-9_]*|xcb[^/]*|wayland[^/]*|xkbcommon[^/]*|decor[^/]*|" + r"drm[^/]*|gbm|asound|pulse[^/]*|jack[^/]*|udev)\.so(?:\.|$)", name)) + + +def cheryl_runtime(name): + return bool(re.match(r"^(?:lib)?cheryl(?:[-.]|$)", name, re.IGNORECASE)) + + +def embedded_runtime(name): + return not cheryl_runtime(name) and bool(re.search(r"(?:spdlog|gainput|glad|(?:^|lib)gl46|miniaudio)", name, re.IGNORECASE)) + + +def runtime_key(name, system): + return name.lower() if system == "Windows" else name + + +def runtime_files(stage, system): + directory, pattern = ("bin", "*.dll") if system == "Windows" else ("lib", "*.so*") + return {runtime_key(path.name, system): path for path in sorted((stage / directory).glob(pattern)) if path.is_file()} + + +def check_runtime_name(name): + if PurePosixPath(name).name != name or "\\" in name: + raise ValueError(f"SDK imports a non-relocatable runtime path: {name}") + + def source_identity(source): snapshot = source / "SOURCE-MANIFEST.json" if snapshot.exists(): @@ -37,58 +85,287 @@ def collect(repo, commit, prefix=""): return {"revision": revision, "submodules": pins} -def copy_native_dependencies(prefix, stage): - if not (prefix / "include").is_dir() or not (prefix / "lib").is_dir(): - raise ValueError("Expected an installed native dependency prefix") - destination = stage / "dependencies" - for directory in ("include", "lib", "bin", "share"): - for path in (prefix / directory).rglob("*"): - if not path.is_file(): - continue - relative = path.relative_to(prefix) - if directory == "include": - selected = True - elif directory == "lib": - selected = path.suffix in (".lib", ".cmake", ".pdb") - elif directory == "bin": - selected = path.suffix == ".dll" - else: - selected = path.name == "copyright" or path.suffix == ".cmake" - if selected and not path.name.endswith("-debug.cmake"): - target = destination / relative - target.parent.mkdir(parents=True, exist_ok=True) - shutil.copy2(path, target) - if path.suffix == ".cmake": - # Some vcpkg configs use toolchain variables instead of their own prefix. - original = target.read_text(encoding="utf-8") - contents = original - for variable in ("VCPKG_INSTALLED_DIR", "_VCPKG_INSTALLED_DIR"): - contents = contents.replace("${" + variable + "}/${VCPKG_TARGET_TRIPLET}", - "${cheryl_sdk_native_prefix}") - if contents != original: - parent = "/".join(".." for _ in relative.parent.parts) - contents = ('get_filename_component(cheryl_sdk_native_prefix ' - '"${CMAKE_CURRENT_LIST_DIR}/' + parent + '" ABSOLUTE)\n' + contents) - target.write_text(contents, encoding="utf-8") - - -def check_contents(stage): +def check_symlinks(stage, *, allow_symlinks=True): + root = stage.resolve() for path in stage.rglob("*"): if path.is_symlink(): - raise ValueError(f"SDK files must be self-contained: {path}") + target = path.readlink() + if not allow_symlinks or target.is_absolute(): + raise ValueError(f"SDK symlinks must be relative and supported by the archive: {path}") + try: + resolved = path.resolve(strict=True) + except (OSError, RuntimeError) as error: + raise ValueError(f"SDK symlink is broken or cyclic: {path}") from error + if not resolved.is_relative_to(root): + raise ValueError(f"SDK symlink escapes the SDK prefix: {path}") + + +def check_contents(stage, metadata=None): + check_symlinks(stage) + metadata = json.loads((stage / "SDK-MANIFEST.json").read_text(encoding="utf-8")) if metadata is None else metadata + if (not isinstance(metadata, dict) or metadata.get("linkage") not in {"static", "shared"} + or not isinstance(metadata.get("component_versions"), dict) or not metadata["component_versions"].get("engine")): + raise ValueError("SDK metadata must declare linkage and the Engine component version") + for path in stage.rglob("*"): if path.suffix.lower() in (".c", ".cc", ".cpp", ".cxx", ".m", ".mm", ".exe"): raise ValueError(f"SDK must not include application or implementation files: {path}") if not (stage / "include/cheryl/core.h").is_file(): raise ValueError("The SDK is missing public Engine headers") - if not any((stage / "lib").glob("*cheryl-engine.*")): - raise ValueError("The SDK is missing the compiled Engine library") + artifacts = ["engine_library"] + if metadata["linkage"] == "shared" and metadata.get("system") == "Windows": + artifacts.append("engine_import_library") + for field in artifacts: + name = metadata.get(field) + path = PurePosixPath(name) if isinstance(name, str) else None + if not path or path.is_absolute() or ".." in path.parts or ":" in name or "\\" in name or not path.parts: + raise ValueError("SDK metadata needs a relative compiled Engine library path") + artifact = stage / path + if not artifact.is_file() or not artifact.resolve().is_relative_to(stage.resolve()): + raise ValueError(f"The SDK is missing the compiled Engine library: {name}") if not (stage / "lib/cmake/Cheryl/CherylConfig.cmake").is_file(): raise ValueError("The SDK is missing CMake integration") if not (stage / "share/licenses/cheryl/LICENSE").is_file(): raise ValueError("The SDK is missing the Cheryl license") + if metadata["linkage"] == "shared": + private_headers = ("spdlog", "GLFW", "gainput", "glad", "miniaudio.h", "freetype2", "harfbuzz", "unicode", "hwloc.h") + for name in private_headers: + if (stage / "include" / name).exists(): + raise ValueError(f"Shared SDK contains private dependency headers: {name}") + for config in (stage / "lib/cmake/Cheryl").glob("CherylTargets*.cmake"): + contents = config.read_text(encoding="utf-8") + shared_targets = {entry[field] for entry in metadata.get("shared_runtime_dependencies", []) + for field in ("dependency", "sdk_target") if entry.get(field)} + component_targets = {"engine": "Cheryl::Engine", "startup": "Cheryl::Startup", "workers": "Cheryl::Workers", + "native_glfw": "Cheryl::NativeGLFW", "opengl": "Cheryl::OpenGL", + "ui_tgui": "Cheryl::UI::TGUI", "ui_rmlui": "Cheryl::UI::RmlUi", + "audio_miniaudio": "Cheryl::Audio::Miniaudio"} + shared_targets.update(target for component, target in component_targets.items() + if component in metadata["component_versions"]) + + def runtime_hints(match): + hints = [name for name in match[1].split(";") if name] + if any(name not in shared_targets or name.startswith("Cheryl::Private_") + or (name not in component_targets.values() + and re.search(r"(?:spdlog|gainput|glad|(?:^|lib)gl46|miniaudio)", name, re.IGNORECASE)) + for name in hints): + raise ValueError(f"Shared SDK exports undeclared dependent runtime hints: {config.name}: {hints}") + return "" + + # CMake records private shared dependencies as optional runtime path + # hints. They do not introduce public link or compile requirements. + contents = re.sub(r'(?m)^\s*IMPORTED_LINK_DEPENDENT_LIBRARIES(?:_[A-Z0-9_]+)?\s+"([^"]*)"\s*$', + runtime_hints, contents) + if re.search(r"(?:Cheryl::Private_|spdlog::|gainput::|gl46[;>\"]|Freetype::|harfbuzz::|ICU::|CherylSdk::Hwloc)", contents): + raise ValueError(f"Shared SDK exports a private dependency: {config.name}") + if (stage / "dependencies").exists(): + raise ValueError("SDK must not contain an undeclared private dependency prefix") + for dependency in metadata.get("shared_runtime_dependencies", []): + name = dependency.get("runtime_name") or dependency.get("artifact") + if name and dependency.get("deployment") != "sdk" and not system_runtime(name, metadata["system"]): + raise ValueError(f"SDK leaves a non-system shared runtime external: {name}") + if dependency.get("deployment") == "sdk": + directory = "bin" if metadata["system"] == "Windows" else "lib" + name = dependency.get("artifact") + if not isinstance(name, str) or PurePosixPath(name).name != name or not name: + raise ValueError("SDK runtime dependencies need artifact filenames") + if not (stage / directory / name).is_file(): + raise ValueError(f"SDK is missing a declared shared runtime: {name}") + + +def ninja_archive_input(build, binary, archive): + ninja = build / "build.ninja" + if not ninja.is_file(): + return False + contents = re.sub(r"\$\r?\n[ \t]*", "", ninja.read_text(encoding="utf-8")) + output = re.escape(binary.relative_to(build).as_posix()) + for block in re.split(r"\n(?=build )", contents): + header, _, variables = block.partition("\n") + if not header.startswith("build "): + continue + outputs = header.split(": ", 1)[0].replace("\\", "/") + if not re.search(r"(?:^|\s)" + output + r"(?:\s|$)", outputs): + continue + libraries = re.search(r"(?m)^ LINK_LIBRARIES = (.*)$", variables) + if libraries: + paths = libraries[1].replace("$ ", " ").replace("$:", ":").replace("\\", "/") + return bool(re.search(r'(?:^|[/\s"])' + re.escape(archive.name) + r'(?=$|[\s"])', paths, re.IGNORECASE)) + return False + + +def ownership_evidence(build, metadata): + """Check producer link evidence, keeping producer paths outside the SDK.""" + if "dependency_ownership" not in metadata: + return [] + inventory = json.loads((build / "cheryl-dependency-ownership.json").read_text(encoding="utf-8")) + if inventory.get("schema") != 1: + raise ValueError("Unsupported dependency ownership inventory") + evidence = [] + owners = {} + maps = {path.stem: path.read_text(encoding="utf-8", errors="replace") + for directory in (build, build / "link-maps") for path in directory.glob("*.map")} + for entry in inventory["dependencies"]: + archive = Path(entry["archive"]) + binary = Path(entry["binary"]) + owner = entry["target_owner"] + if not archive.is_file() or not binary.is_file(): + raise ValueError(f"Dependency ownership evidence is missing an artifact: {entry['dependency']}") + previous = owners.setdefault(archive.resolve(), owner) + if previous != owner: + raise ValueError(f"A private archive has multiple binary owners: {archive.name}") + item = {"dependency": entry["dependency"], "owner": owner, "archive": archive.name, + "owner_artifact": binary.name, "evidence": "static archive closure"} + if metadata["linkage"] == "shared": + # GNU link maps show archive(member); MSVC maps show archive:member. + windows = metadata["system"] == "Windows" + pattern = (r"(?i)(?:^|[\s/\\])" + re.escape(archive.stem) + r"(?:\.lib)?:" if windows + else re.escape(archive.name) + r"\(") + owner_map = maps.get(owner, "") + extracted = bool(re.search(pattern, owner_map)) + if not extracted and archive.name not in owner_map: + if not windows or not ninja_archive_input(build, binary, archive): + raise ValueError(f"Owner link map has no dependency archive input: {owner}/{archive.name}") + for other, contents in maps.items(): + if other != owner and re.search(pattern, contents): + raise ValueError(f"Private dependency members are linked into two owners: {archive.name}/{other}") + item["evidence"] = ("owner link map archive members" if extracted else + "owner Ninja link archive input; no members required" if windows else + "owner link map archive input; no members required") + evidence.append(item) + expected = {(entry["dependency"], entry["owner"]) for entry in metadata["dependency_ownership"]} + actual = {(entry["dependency"], entry["owner"]) for entry in evidence} + if expected != actual: + raise ValueError("SDK ownership metadata does not match the producer inventory") + return evidence + + +def shared_runtime_evidence(build, metadata): + """Resolve exact runtime names from the declared producer shared libraries.""" + if "shared_runtime_dependencies" not in metadata: + return [] + inventory = json.loads((build / "cheryl-shared-dependencies.json").read_text(encoding="utf-8")) + if inventory.get("schema") != 1: + raise ValueError("Unsupported shared dependency inventory") + declarations = {(entry["dependency"], entry["consumer"]): entry for entry in inventory["dependencies"]} + expected = {(entry["dependency"], entry["consumer"]) for entry in metadata["shared_runtime_dependencies"]} + if expected != set(declarations): + raise ValueError("SDK shared metadata does not match the producer inventory") + evidence = [] + for entry in metadata["shared_runtime_dependencies"]: + declaration = declarations[entry["dependency"], entry["consumer"]] + item = dict(entry) + library = Path(declaration["library"]) if declaration["library"] else None + if library: + if not library.is_file() or library.name != entry["artifact"]: + raise ValueError(f"Shared dependency evidence is missing an artifact: {entry['dependency']}") + if metadata["system"] == "Windows": + if declaration["system"]: + item["runtime_name"] = library.stem + ".dll" if library.suffix.lower() == ".lib" else library.name + elif library.suffix.lower() != ".dll": + raise ValueError(f"Windows shared dependency needs a DLL location: {entry['dependency']}") + else: + item["runtime_name"] = library.name + else: + output = subprocess.check_output(["readelf", "-d", str(library)], text=True) + soname = re.search(r"\(SONAME\).*?\[([^]]+)\]", output) + item["runtime_name"] = soname[1] if soname else library.name + item["runtime_version"] = library.resolve().name + elif declaration["system"]: + item["runtime_name"] = "icu.dll" if metadata["system"] == "Windows" and "icu" in entry["dependency"].lower() else "" + else: + raise ValueError("A non-system shared dependency has no runtime artifact") + evidence.append(item) + return evidence + + +def runtime_imports(library, system): + if system == "Windows": + tool = shutil.which("dumpbin") + if tool: + command = [tool, "/nologo", "/dependents", str(library)] + else: + tool = shutil.which("llvm-readobj") + if not tool: + raise RuntimeError("SDK runtime import verification requires dumpbin or llvm-readobj") + command = [tool, "--coff-imports", str(library)] + output = subprocess.check_output(command, text=True) + return re.findall(r"(?im)^\s*(?:Name:\s*)?([^\s]+\.dll)\s*$", output) + output = subprocess.check_output(["readelf", "-d", str(library)], text=True) + return re.findall(r"\(NEEDED\).*?\[([^]]+)\]", output) + + +def shared_runtime_closure(stage, metadata): + """Record bundled native runtimes reached through the installed binaries.""" + system = metadata["system"] + records = metadata.setdefault("shared_runtime_dependencies", []) + declared = {runtime_key(entry.get("runtime_name", entry.get("artifact", "")), system) for entry in records} + binaries = runtime_files(stage, system) + pending = [] + for entry in records: + if entry.get("deployment") != "sdk": + continue + name = entry.get("runtime_name", entry["artifact"]) + library = binaries.get(runtime_key(name, system)) + if not library: + raise ValueError(f"SDK is missing a declared shared runtime: {name}") + pending.append((entry["dependency"], library)) + pending.extend(("component::" + library.name, library) for library in binaries.values() if cheryl_runtime(library.name)) + inspected = set() + for parent, library in pending: + if library.resolve() in inspected: + continue + inspected.add(library.resolve()) + for dependency in runtime_imports(library, system): + check_runtime_name(dependency) + if system_runtime(dependency, system): + continue + key = runtime_key(dependency, system) + child = binaries.get(key) + if not child: + raise ValueError(f"SDK is missing a required shared runtime: {library.name}: {dependency}") + pending.append(("runtime::" + dependency, child)) + if key in declared or cheryl_runtime(dependency): + continue + item = {"dependency": "runtime::" + dependency, "consumer": parent, + "consumer_artifact": library.name, "artifact": child.name, "runtime_name": child.name, + "runtime_version": child.resolve().name, "deployment": "sdk", "evidence": "runtime import closure"} + records.append(item) + declared.add(key) + + +def check_runtime_imports(stage, metadata): + system = metadata["system"] + binaries = runtime_files(stage, system) + declared = {runtime_key(entry.get("runtime_name", entry.get("artifact", "")), system) + for entry in metadata.get("shared_runtime_dependencies", []) if entry.get("deployment") == "sdk"} + recorded_files = {binaries[name].resolve() for name in declared if name in binaries} + evidence = [] + for library in binaries.values(): + if library.is_symlink(): + continue + if embedded_runtime(library.name): + raise ValueError(f"SDK contains an embedded private dependency as a shared runtime: {library.name}") + if not cheryl_runtime(library.name) and library.resolve() not in recorded_files: + raise ValueError(f"SDK contains an undeclared shared runtime: {library.name}") + imports = runtime_imports(library, system) + for name in imports: + check_runtime_name(name) + if embedded_runtime(name): + raise ValueError(f"SDK imports an embedded private dependency: {library.name}: {name}") + if system_runtime(name, system): + continue + key = runtime_key(name, system) + if key not in binaries: + raise ValueError(f"SDK is missing a required shared runtime: {library.name}: {name}") + if not cheryl_runtime(name) and key not in declared: + raise ValueError(f"SDK imports a private dependency without a shared declaration: {library.name}: {name}") + evidence.append({"binary": library.name, "imports": imports}) + if not evidence and metadata["linkage"] == "shared": + raise ValueError("Shared SDK contains no binaries to inspect") + return evidence def archive_sdk(stage, destination): + check_symlinks(stage, allow_symlinks=destination.suffix != ".zip") if destination.suffix == ".zip": with zipfile.ZipFile(destination, "w", compression=zipfile.ZIP_DEFLATED) as archive: for path in sorted(stage.rglob("*")): @@ -100,7 +377,7 @@ def archive_sdk(stage, destination): archive.writestr(info, path.read_bytes()) else: with destination.open("wb") as raw, gzip.GzipFile(fileobj=raw, mode="wb", filename="", mtime=0) as compressed: - with tarfile.open(fileobj=compressed, mode="w") as archive: + with tarfile.open(fileobj=compressed, mode="w", dereference=False) as archive: for path in [stage, *sorted(stage.rglob("*"))]: info = archive.gettarinfo(str(path), arcname=path.relative_to(stage.parent).as_posix()) info.uid = info.gid = info.mtime = 0 @@ -112,35 +389,40 @@ def archive_sdk(stage, destination): archive.addfile(info) -def bundle(source, build, output, native_prefix=None): +def bundle(source, build, output): metadata = json.loads((build / "cheryl-sdk.json").read_text(encoding="utf-8")) metadata.update(source_identity(source)) - architecture = {"AMD64": "x64", "x86_64": "x64"}.get(metadata["architecture"], metadata["architecture"]) - platform_name = metadata["system"].lower() + metadata["dependency_ownership"] = ownership_evidence(build, metadata) + if "shared_runtime_dependencies" in metadata: + metadata["shared_runtime_dependencies"] = shared_runtime_evidence(build, metadata) if metadata["system"] == "Linux": try: release = platform.freedesktop_os_release() except OSError: release = {} metadata["distribution"] = {key: release[key] for key in ("ID", "VERSION_ID") if key in release} - if "ID" in release: - platform_name += "-" + release["ID"] + release.get("VERSION_ID", "") - name = (f"cheryl-{metadata['version']}-sdk-{platform_name}-{architecture}-" - f"{metadata['compiler'].lower()}{metadata['compiler_version']}-{metadata['configuration'].lower()}-" - f"{metadata['revision'][:12]}") + name = sdk_name(metadata) output.mkdir(parents=True, exist_ok=True) with tempfile.TemporaryDirectory(dir=output) as temporary: stage = Path(temporary) / name subprocess.run(["cmake", "--install", str(build), "--prefix", str(stage), "--component", "CherylSDK"], check=True) + shared_runtime_closure(stage, metadata) if metadata["system"] == "Windows": - copy_native_dependencies(native_prefix or build / "vcpkg_installed/x64-windows", stage) manifest = json.loads((source / ".github/ci/vcpkg.json").read_text(encoding="utf-8")) metadata["vcpkg_baseline"] = manifest["builtin-baseline"] - metadata["runtime_directory"] = "dependencies/bin" + metadata["system_dependencies"] = ["Windows 10 1903+ system APIs including ICU and OpenGL", "MSVC DLL runtime"] else: - metadata["system_dependencies"] = ["FreeType", "HarfBuzz", "ICU", "hwloc (when selected)", - "libdw and its dependencies", "X11/OpenGL (when selected)"] - check_contents(stage) + metadata["system_dependencies"] = ["Linux loader, glibc and NSS", "C++ compiler runtime", + "OpenGL/GL driver, X11/xcb/Wayland/xkbcommon/libdecor (when selected)", + "ALSA/PulseAudio/JACK service clients (when selected)", "libudev (when selected)"] + check_contents(stage, metadata) + metadata["runtime_import_evidence"] = check_runtime_imports(stage, metadata) + runtime_directories = [] + if metadata["system"] == "Windows": + runtime_directories = ["bin"] + elif any((stage / "lib").glob("*.so*")): + runtime_directories = ["lib"] + metadata["runtime_directories"] = [name for name in runtime_directories if (stage / name).is_dir()] text = json.dumps(metadata, indent=2, sort_keys=True) + "\n" (stage / "SDK-MANIFEST.json").write_text(text, encoding="utf-8") destination = output / (name + (".zip" if metadata["system"] == "Windows" else ".tar.gz")) @@ -158,9 +440,8 @@ def main(): parser.add_argument("--source", type=Path, default=Path(__file__).resolve().parents[2]) parser.add_argument("--build-dir", type=Path, required=True) parser.add_argument("--output", type=Path, required=True) - parser.add_argument("--native-prefix", type=Path) args = parser.parse_args() - bundle(args.source.resolve(), args.build_dir.resolve(), args.output.resolve(), args.native_prefix) + bundle(args.source.resolve(), args.build_dir.resolve(), args.output.resolve()) if __name__ == "__main__": diff --git a/.github/ci/release.py b/.github/ci/release.py new file mode 100644 index 00000000..a65eae63 --- /dev/null +++ b/.github/ci/release.py @@ -0,0 +1,764 @@ +"""Assemble release archives and publish an explicitly approved release merge.""" + +import argparse +from contextlib import contextmanager +import gzip +import hashlib +import io +import json +import os +from pathlib import Path, PurePosixPath +import posixpath +import re +import shutil +import stat +import subprocess +import tarfile +import tempfile +import time +import zipfile + + +REPOSITORY = "cppcooper/cheryl-engine" +ROOT = Path(__file__).resolve().parents[2] +REVISION = re.compile(r"[0-9a-f]{40}") +VERSION = re.compile(r"(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)") +VARIANTS = { + "candidate-sdk-linux-modules": ("Linux", "static", "Release", "GNU"), + "candidate-sdk-linux-modules-shared": ("Linux", "shared", "Release", "GNU"), + "candidate-sdk-windows-modules": ("Windows", "static", "RelWithDebInfo", "MSVC"), + "candidate-sdk-windows-modules-shared": ("Windows", "shared", "RelWithDebInfo", "MSVC"), +} +COMPONENTS = { + "engine": "VERSION_PROJECT_ENGINE", + "startup": "VERSION_PROJECT_MODULE_STARTUP", + "workers": "VERSION_PROJECT_MODULE_WORKERS", + "native_glfw": "VERSION_PROJECT_MODULE_NATIVE_GLFW", + "debug_terminal_linux": "VERSION_PROJECT_MODULE_DEBUG_TERMINAL_LINUX", + "opengl": "VERSION_PROJECT_MODULE_OPENGL", + "ui_tgui": "VERSION_PROJECT_MODULE_UI_TGUI", + "ui_rmlui": "VERSION_PROJECT_MODULE_UI_RMLUI", + "audio_miniaudio": "VERSION_PROJECT_MODULE_AUDIO_MINIAUDIO", +} +REQUIRED_COMPONENTS = set(COMPONENTS) - {"debug_terminal_linux"} +FEATURES = {"startup", "startup_opengl", "workers", "native_glfw", "native_input", "opengl", + "tgui", "rmlui", "miniaudio", "hwloc", "json"} +MANIFEST_LIMIT = 1024 * 1024 +SIDECARS = {"SDK-MANIFESTS.json", "SHA256SUMS"} + + +def release_names(version): + return {"Linux": f"cheryl-SDK-v{version}-linux.tar.gz", + "Windows": f"cheryl-SDK-v{version}-windows.zip", + "static": f"cheryl-SDK-v{version}-static.tar.gz", + "source": f"cheryl-src-v{version}.tar.gz"} + + +def publication_revision(environment, requested=None): + required = { + "GITHUB_ACTIONS": "true", "GITHUB_REPOSITORY": REPOSITORY, + "GITHUB_EVENT_NAME": "push", "GITHUB_REF": "refs/heads/main", + } + if any(environment.get(name) != value for name, value in required.items()): + return None + revision = environment.get("GITHUB_SHA", "") + if not isinstance(revision, str) or not REVISION.fullmatch(revision): + raise RuntimeError("A release requires the full checked main commit ID") + if requested is not None and requested != revision: + raise RuntimeError("The requested release revision differs from the checked main push") + return revision + + +def project_version(versions): + version = versions.get("VERSION_PROJECT_CHERYL") + if not isinstance(version, str) or not VERSION.fullmatch(version) or version.rsplit(".", 1)[1] != "0": + raise RuntimeError("A stable release needs an explicit x.y.0 Cheryl project version in CMake") + return version + + +def release_pull(api, revision, version): + matches = [] + for pull in api.pages(f"/commits/{revision}/pulls"): + if not isinstance(pull, dict): + raise RuntimeError("GitHub returned invalid associated pull request data") + head, base = pull.get("head") or {}, pull.get("base") or {} + if (base.get("ref") != "main" or head.get("ref") != "develop" + or (head.get("repo") or {}).get("full_name") != REPOSITORY + or (base.get("repo") or {}).get("full_name") != REPOSITORY + or pull.get("state") != "closed" or pull.get("merge_commit_sha") != revision + or not pull.get("merged_at")): + continue + title = pull.get("title", "") + if not isinstance(title, str) or not re.fullmatch(r"Release " + VERSION.pattern, title): + continue + if title != f"Release {version}": + raise RuntimeError("The merged release PR title does not match the CMake project version") + matches.append(pull) + if not matches: + return None + if len(matches) != 1: + raise RuntimeError("The main commit has more than one matching release PR") + pull = matches[0] + commit = api.request("GET", f"/commits/{revision}") + parents = commit.get("parents") if isinstance(commit, dict) else None + head = pull["head"].get("sha") + if (not isinstance(commit, dict) or commit.get("sha") != revision + or not isinstance(parents, list) or len(parents) != 2 + or not isinstance(head, str) or not REVISION.fullmatch(head) + or any(not isinstance(parent, dict) or not isinstance(parent.get("sha"), str) + or not REVISION.fullmatch(parent["sha"]) for parent in parents) + or parents[1].get("sha") != head): + raise RuntimeError("A release must be an ordinary merge with the develop PR head as its second parent") + return pull + + +def current_main(api, revision): + response = api.request("GET", "/git/ref/heads/main") + value = response.get("object") if isinstance(response, dict) else None + if (not isinstance(response, dict) or response.get("ref") != "refs/heads/main" + or not isinstance(value, dict) or value.get("type") != "commit" + or not isinstance(value.get("sha"), str) or not REVISION.fullmatch(value["sha"])): + raise RuntimeError("GitHub did not return the canonical main commit") + return value["sha"] == revision + + +def wait_for_documentation(api, revision, *, sleep=time.sleep, monotonic=time.monotonic, timeout=600): + deadline = monotonic() + timeout + while True: + runs = [run for run in api.pages("/actions/workflows/documentation.yml/runs", field="workflow_runs", + branch="main", event="push", head_sha=revision) + if isinstance(run, dict) and run.get("head_sha") == revision + and run.get("head_branch") == "main" and run.get("event") == "push" + and (run.get("head_repository") or {}).get("full_name") == REPOSITORY] + if runs: + latest = max(runs, key=lambda run: run.get("id", 0)) + identifier = latest.get("id") + if type(identifier) is not int or identifier <= 0: + raise RuntimeError("The documentation workflow run has an invalid ID") + # GitHub's default latest-job filter excludes prior attempts. + jobs = [job for job in api.pages(f"/actions/runs/{identifier}/jobs", field="jobs", filter="latest") + if isinstance(job, dict) and job.get("name") == "Documentation checks"] + if len(jobs) > 1: + raise RuntimeError("The latest documentation attempt has ambiguous check jobs") + if jobs and jobs[0].get("status") == "completed": + if jobs[0].get("conclusion") == "success": + return + raise RuntimeError("Documentation checks did not succeed for the release commit") + if not jobs and latest.get("status") == "completed": + raise RuntimeError("The documentation workflow completed without its required check job") + remaining = deadline - monotonic() + if remaining <= 0: + raise RuntimeError("Timed out waiting for documentation checks on the release commit") + sleep(min(15, remaining)) + + +def _regular(path): + if not stat.S_ISREG(path.lstat().st_mode): + raise RuntimeError(f"Expected a regular release input: {path.name}") + + +def _digest(path): + with path.open("rb") as source: + return hashlib.file_digest(source, "sha256").hexdigest() + + +def _json(contents): + if len(contents) > MANIFEST_LIMIT: + raise RuntimeError("SDK manifest exceeds the release metadata limit") + value = json.loads(contents) + if not isinstance(value, dict): + raise RuntimeError("SDK metadata must be a JSON object") + return value + + +def _small_file(path): + if path.stat().st_size > MANIFEST_LIMIT: + raise RuntimeError("Release metadata exceeds the release metadata limit") + return path.read_bytes() + + +def _safe_path(name): + if (not isinstance(name, str) or not name or "\\" in name or ":" in name + or any(ord(character) < 32 for character in name) + or name.startswith("/") or any(part in {"", ".", ".."} for part in name.split("/"))): + raise RuntimeError("A release archive contains an unsafe path") + return name + + +def _archive_root(path): + suffix = ".zip" if path.name.endswith(".zip") else ".tar.gz" + return path.name[:-len(suffix)] + + +def _members(archive): + return archive.infolist() if isinstance(archive, zipfile.ZipFile) else archive.getmembers() + + +def _member_name(member): + return (member.filename if isinstance(member, zipfile.ZipInfo) else member.name).rstrip("/") + + +def _open_archive(path): + archive = zipfile.ZipFile(path) if path.name.endswith(".zip") else tarfile.open(path, "r:gz") + try: + root, seen, links, folded = _archive_root(path), set(), {}, set() + for member in _members(archive): + name = _safe_path(_member_name(member)) + if name != root and not name.startswith(root + "/"): + raise RuntimeError("A release archive has an unexpected root directory") + if name in seen: + raise RuntimeError("A release archive contains duplicate paths") + seen.add(name) + if isinstance(member, zipfile.ZipInfo): + if name.casefold() in folded or any( + part.endswith((".", " ")) or re.fullmatch(r"(?i)(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?", part) + for part in name.split("/")): + raise RuntimeError("A release ZIP contains colliding or unsupported Windows paths") + folded.add(name.casefold()) + mode = stat.S_IFMT(member.external_attr >> 16) + if mode not in {0, stat.S_IFREG, stat.S_IFDIR} or member.flag_bits & 1: + raise RuntimeError("A release ZIP contains an unsupported member") + elif member.issym(): + target = member.linkname + if (not target or "\\" in target or ":" in target or target.startswith("/") + or any(ord(character) < 32 for character in target)): + raise RuntimeError("A release archive contains an unsafe symlink") + resolved = posixpath.normpath(posixpath.join(posixpath.dirname(name), target)) + if resolved != root and not resolved.startswith(root + "/"): + raise RuntimeError("A release archive symlink escapes its root") + links[name] = resolved + elif not member.isfile() and not member.isdir(): + raise RuntimeError("A release archive contains an unsupported member") + for name in seen: + if any(parent.as_posix() in links for parent in PurePosixPath(name).parents): + raise RuntimeError("A release archive path traverses a symlink") + for name in links: + target, followed = name, set() + while target in links: + if target in followed: + raise RuntimeError("A release archive contains a cyclic symlink") + followed.add(target) + target = links[target] + return archive + except BaseException: + archive.close() + raise + + +def _contents(archive, member): + return archive.open(member) if isinstance(archive, zipfile.ZipFile) else archive.extractfile(member) + + +def _archive_metadata(path, prefix="", filename="SDK-MANIFEST.json"): + name = "/".join(filter(None, (_archive_root(path), prefix, filename))) + with _open_archive(path) as archive: + members = [member for member in _members(archive) if _member_name(member) == name] + if (len(members) != 1 or (members[0].is_dir() if isinstance(members[0], zipfile.ZipInfo) + else not members[0].isfile()) + or (members[0].file_size if isinstance(members[0], zipfile.ZipInfo) else members[0].size) > MANIFEST_LIMIT): + raise RuntimeError("The release archive must contain one bounded manifest at its root") + with _contents(archive, members[0]) as source: + return _json(source.read(MANIFEST_LIMIT + 1)) + + +def _sdk_metadata(metadata, expected, revision, versions, *, local_validation=False): + actual = tuple(metadata.get(field) for field in ("system", "linkage", "configuration", "compiler")) + matched = (actual == expected if not local_validation else + actual[:2] == expected[:2] and actual[3] == expected[3] and actual[2] in {"Release", "RelWithDebInfo"}) + if not matched or metadata.get("architecture") not in ("AMD64", "x86_64", "x64"): + raise RuntimeError("SDK metadata does not match its accepted CI variant") + if metadata.get("revision") != revision or metadata.get("version") != project_version(versions): + raise RuntimeError("SDK revision or version differs from the checked release source") + components = metadata.get("component_versions") + if not isinstance(components, dict) or not REQUIRED_COMPONENTS.issubset(components): + raise RuntimeError("SDK metadata is missing required component versions") + for component, value in components.items(): + variable = COMPONENTS.get(component) + if variable is None or variable not in versions or value != versions[variable]: + raise RuntimeError(f"SDK component version differs from CMake: {component}") + features = metadata.get("features") + if (not isinstance(features, dict) or set(features) != FEATURES + or any(value not in ("ON", "OFF") for value in features.values()) + or any(features[name] != "ON" for name in FEATURES - ({"hwloc"} if local_validation else set()))): + raise RuntimeError("SDK metadata is missing required release features") + _source_identity({"revision": revision, "submodules": metadata.get("submodules")}) + + +def _source_identity(identity): + if (not isinstance(identity, dict) or set(identity) != {"revision", "submodules"} + or not isinstance(identity.get("revision"), str) or not REVISION.fullmatch(identity["revision"]) + or not isinstance(identity.get("submodules"), dict)): + raise RuntimeError("The release source identity is invalid") + for path, revision in identity["submodules"].items(): + _safe_path(path) + if not isinstance(revision, str) or not REVISION.fullmatch(revision): + raise RuntimeError("The release source submodule revision is invalid") + return identity + + +def validate_packages(packages, revision, versions, *, local_validation=False): + packages = Path(packages) + project_version(versions) + if packages.is_symlink() or not packages.is_dir() or {path.name for path in packages.iterdir()} != set(VARIANTS): + raise RuntimeError("The accepted artifact must contain exactly the four expected SDK variants") + archives, manifests = {}, {} + for variant, expected in VARIANTS.items(): + directory = packages / variant + if directory.is_symlink() or not directory.is_dir(): + raise RuntimeError("An SDK variant must be a real directory") + checksum, manifest_path = directory / "SHA256SUMS", directory / "SDK-MANIFEST.json" + _regular(checksum) + _regular(manifest_path) + text = _small_file(checksum).decode("utf-8") + match = re.fullmatch(r"([0-9a-f]{64}) ([A-Za-z0-9][A-Za-z0-9._+-]*)(?:\r?\n)?", text) + if not match: + raise RuntimeError("Each SDK checksum file must name one safe archive basename") + digest, name = match.groups() + archive = directory / name + suffix = ".zip" if expected[0] == "Windows" else ".tar.gz" + if not name.endswith(suffix) or {path.name for path in directory.iterdir()} != {name, "SHA256SUMS", "SDK-MANIFEST.json"}: + raise RuntimeError("An SDK bundle has unexpected files or archive format") + _regular(archive) + if _digest(archive) != digest: + raise RuntimeError(f"SDK checksum mismatch: {name}") + metadata = _json(_small_file(manifest_path)) + _sdk_metadata(metadata, expected, revision, versions, local_validation=local_validation) + if _archive_metadata(archive) != metadata: + raise RuntimeError("The SDK archive and its outer manifest disagree") + if name in archives: + raise RuntimeError("SDK release archive names must be unique") + archives[name] = {"path": archive, "digest": digest} + manifests[name] = metadata + return archives, manifests + + +@contextmanager +def _tar_writer(path): + with path.open("wb") as destination: + with gzip.GzipFile(filename="", mode="wb", fileobj=destination, mtime=0, compresslevel=6) as compressed: + with tarfile.open(fileobj=compressed, mode="w", format=tarfile.PAX_FORMAT) as archive: + yield archive + + +def _tar_member(name, *, size=0, mode=0o644, link=None, directory=False): + member = tarfile.TarInfo(name) + member.mode, member.size = mode, size + if link is not None: + member.type, member.linkname, member.size = tarfile.SYMTYPE, link, 0 + elif directory: + member.type = tarfile.DIRTYPE + return member + + +def _json_bytes(value): + return (json.dumps(value, indent=2, sort_keys=True) + "\n").encode("utf-8") + + +def _tar_bytes(archive, name, data): + archive.addfile(_tar_member(name, size=len(data)), io.BytesIO(data)) + + +def _append_sdk(destination, path, root): + original = _archive_root(path) + with _open_archive(path) as archive: + for member in sorted(_members(archive), key=_member_name): + relative = _member_name(member)[len(original):].lstrip("/") + if not relative: + continue + name = root + "/" + relative + if isinstance(member, zipfile.ZipInfo): + copied = _tar_member(name, size=member.file_size, directory=member.is_dir(), + mode=0o755 if member.is_dir() else 0o644) + else: + copied = _tar_member(name, size=member.size, mode=member.mode & 0o777, + link=member.linkname if member.issym() else None, + directory=member.isdir()) + if copied.isfile(): + with _contents(archive, member) as contents: + destination.addfile(copied, contents) + else: + destination.addfile(copied) + + +def _shared_archive(source, destination): + if source.name == destination.name: + shutil.copyfile(source, destination) + return + root = _archive_root(destination) + if destination.name.endswith(".tar.gz"): + with _tar_writer(destination) as archive: + _append_sdk(archive, source, root) + return + original = _archive_root(source) + with _open_archive(source) as archive, zipfile.ZipFile(destination, "w", compression=zipfile.ZIP_DEFLATED) as output: + for member in sorted(_members(archive), key=_member_name): + relative = _member_name(member)[len(original):].lstrip("/") + if not relative: + continue + name = root + "/" + relative + ("/" if member.is_dir() else "") + copied = zipfile.ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0)) + copied.compress_type = zipfile.ZIP_DEFLATED + copied.external_attr = ((stat.S_IFDIR | 0o755) if member.is_dir() else (stat.S_IFREG | 0o644)) << 16 + with output.open(copied, "w") as target: + if not member.is_dir(): + with _contents(archive, member) as contents: + shutil.copyfileobj(contents, target) + + +def _git(repo, *arguments): + try: + return subprocess.check_output(["git", "-C", str(repo), *arguments], stderr=subprocess.PIPE) + except subprocess.CalledProcessError as error: + raise RuntimeError(f"Cannot read the pinned source repository: {repo}") from error + + +def source_snapshot(source, revision, destination): + """Read pinned Git objects; generated and untracked working files never enter the archive.""" + source = Path(source) + if not isinstance(revision, str) or not REVISION.fullmatch(revision): + raise RuntimeError("A source snapshot requires a full checked commit ID") + repositories, pins = [], {} + + def collect(repo, commit, prefix=""): + if repo.is_symlink() or _git(repo, "rev-parse", "HEAD").decode().strip() != commit: + raise RuntimeError(f"Source checkout does not match its pinned revision: {prefix or '.'}") + entries = [] + for entry in filter(None, _git(repo, "ls-tree", "-rz", commit).split(b"\0")): + metadata, raw_name = entry.split(b"\t", 1) + mode, kind, sha = metadata.decode().split() + name = _safe_path(raw_name.decode("utf-8")) + if not prefix and name == "SOURCE-MANIFEST.json": + raise RuntimeError("The source tree reserves SOURCE-MANIFEST.json for snapshot identity") + if mode == "160000" and kind == "commit": + pins[prefix + name] = sha + collect(repo / name, sha, prefix + name + "/") + elif kind == "blob" and mode in {"100644", "100755", "120000"}: + entries.append((prefix + name, mode, sha)) + else: + raise RuntimeError("The pinned source contains an unsupported Git tree entry") + repositories.append((repo, entries)) + + collect(source, revision) + identity = _source_identity({"revision": revision, "submodules": pins}) + root = _archive_root(destination) + with _tar_writer(destination) as archive: + for repo, entries in repositories: + with subprocess.Popen(["git", "-C", str(repo), "cat-file", "--batch"], + stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as objects: + try: + for name, mode, sha in entries: + objects.stdin.write((sha + "\n").encode("ascii")) + objects.stdin.flush() + header = objects.stdout.readline().decode("ascii").strip().split() + if len(header) != 3 or header[:2] != [sha, "blob"] or not header[2].isdigit(): + raise RuntimeError("Git did not return the requested source blob") + size = int(header[2]) + if mode == "120000": + if size > MANIFEST_LIMIT: + raise RuntimeError("A source symlink is too large") + link = objects.stdout.read(size).decode("utf-8") + archive.addfile(_tar_member(root + "/" + name, link=link, mode=0o777)) + else: + archive.addfile(_tar_member(root + "/" + name, size=size, + mode=0o755 if mode == "100755" else 0o644), objects.stdout) + if objects.stdout.read(1) != b"\n": + raise RuntimeError("Git returned a truncated source blob") + except BaseException: + objects.kill() + raise + finally: + objects.stdin.close() + if objects.wait() != 0: + raise RuntimeError("Git could not finish reading the pinned source") + _tar_bytes(archive, root + "/SOURCE-MANIFEST.json", _json_bytes(identity)) + # Apply the same traversal and link rules to source archives as binary SDKs. + with _open_archive(destination): + pass + return identity + + +def _static_dispatcher(filename): + missing = ('set(Cheryl_FOUND FALSE)\n set(Cheryl_NOT_FOUND_MESSAGE "This SDK supports Linux and Windows")' + if filename == "CherylConfig.cmake" else "set(PACKAGE_VERSION_UNSUITABLE TRUE)") + return ("if(CMAKE_SYSTEM_NAME STREQUAL \"Linux\")\n" + " set(_cheryl_platform linux)\n" + "elseif(CMAKE_SYSTEM_NAME STREQUAL \"Windows\")\n" + " set(_cheryl_platform windows)\n" + f"else()\n {missing}\n return()\nendif()\n" + f'include("${{CMAKE_CURRENT_LIST_DIR}}/../../../${{_cheryl_platform}}/lib/cmake/Cheryl/{filename}")\n' + "unset(_cheryl_platform)\n").encode("utf-8") + + +def assemble_release(packages, source, output, revision, versions, *, local_validation=False): + archives, manifests = validate_packages(packages, revision, versions, local_validation=local_validation) + version, output = project_version(versions), Path(output) + names = release_names(version) + if output.is_symlink() or (output.exists() and (not output.is_dir() or any(output.iterdir()))): + raise RuntimeError("Release assembly needs an empty real output directory") + output.mkdir(parents=True, exist_ok=True) + with tempfile.TemporaryDirectory(prefix="cheryl-assembly-", dir=output.parent) as temporary: + directory = Path(temporary) + identity = source_snapshot(source, revision, directory / names["source"]) + sdks = {} + with _tar_writer(directory / names["static"]) as static_archive: + for variant, expected in VARIANTS.items(): + original = next(name for name, manifest in manifests.items() + if (manifest["system"], manifest["linkage"]) == expected[:2]) + metadata = manifests[original] + if metadata["submodules"] != identity["submodules"]: + raise RuntimeError("An SDK's submodule pins differ from the release source snapshot") + prefix = expected[0].lower() if expected[1] == "static" else "" + final_name = names["static"] if prefix else names[expected[0]] + if prefix: + _append_sdk(static_archive, archives[original]["path"], _archive_root(directory / final_name) + "/" + prefix) + else: + _shared_archive(archives[original]["path"], directory / final_name) + sdks[variant] = {"archive": final_name, "prefix": prefix, "manifest": metadata} + for filename in ("CherylConfig.cmake", "CherylConfigVersion.cmake"): + _tar_bytes(static_archive, _archive_root(directory / names["static"]) + "/lib/cmake/Cheryl/" + filename, + _static_dispatcher(filename)) + aggregate = {"schema": 2, "validation": "local" if local_validation else "ci", + "version": version, "revision": revision, "sdks": sdks, + "source": {"archive": names["source"], "manifest": identity}} + (directory / "SDK-MANIFESTS.json").write_bytes(_json_bytes(aggregate)) + assets = {path.name: {"path": path, "digest": _digest(path)} for path in directory.iterdir()} + (directory / "SHA256SUMS").write_text("".join(f"{value['digest']} {name}\n" for name, value in sorted(assets.items())), + encoding="utf-8") + validate_release(directory, revision, versions, local_validation=local_validation) + for path in directory.iterdir(): + path.rename(output / path.name) + return output + + +def validate_release(packages, revision, versions, *, local_validation=False): + packages, version = Path(packages), project_version(versions) + names = release_names(version) + expected = set(names.values()) | SIDECARS + if packages.is_symlink() or not packages.is_dir() or {path.name for path in packages.iterdir()} != expected: + raise RuntimeError("The release must contain exactly four named archives and two audit sidecars") + for name in expected: + _regular(packages / name) + checksums = {} + for line in _small_file(packages / "SHA256SUMS").decode("utf-8").splitlines(): + match = re.fullmatch(r"([0-9a-f]{64}) ([A-Za-z0-9][A-Za-z0-9._+-]*)", line) + if not match or match[2] in checksums: + raise RuntimeError("The release checksum file contains invalid or duplicate entries") + checksums[match[2]] = match[1] + if set(checksums) != expected - {"SHA256SUMS"}: + raise RuntimeError("The release checksums must cover all four archives and SDK metadata") + assets = {} + for name in expected: + digest = _digest(packages / name) + if name != "SHA256SUMS" and digest != checksums[name]: + raise RuntimeError(f"Release checksum mismatch: {name}") + assets[name] = {"path": packages / name, "digest": digest} + aggregate = _json(_small_file(packages / "SDK-MANIFESTS.json")) + if (aggregate.get("schema") != 2 or aggregate.get("revision") != revision or aggregate.get("version") != version + or not isinstance(aggregate.get("sdks"), dict) or set(aggregate["sdks"]) != set(VARIANTS)): + raise RuntimeError("The assembled SDK metadata differs from the checked release source") + if aggregate.get("validation") != ("local" if local_validation else "ci"): + raise RuntimeError("Local validation archives cannot be published as CI release assets") + source = aggregate.get("source") + if not isinstance(source, dict) or set(source) != {"archive", "manifest"} or source["archive"] != names["source"]: + raise RuntimeError("The assembled source metadata is invalid") + identity = _source_identity(source["manifest"]) + if identity["revision"] != revision or _archive_metadata(packages / names["source"], filename="SOURCE-MANIFEST.json") != identity: + raise RuntimeError("The source archive and its release identity disagree") + for variant, expected_variant in VARIANTS.items(): + sdk = aggregate["sdks"][variant] + prefix = expected_variant[0].lower() if expected_variant[1] == "static" else "" + name = names["static"] if prefix else names[expected_variant[0]] + if (not isinstance(sdk, dict) or set(sdk) != {"archive", "prefix", "manifest"} + or sdk["archive"] != name or sdk["prefix"] != prefix or not isinstance(sdk["manifest"], dict)): + raise RuntimeError("An assembled SDK has an unexpected archive or platform prefix") + metadata = sdk["manifest"] + _sdk_metadata(metadata, expected_variant, revision, versions, local_validation=local_validation) + if metadata["submodules"] != identity["submodules"]: + raise RuntimeError("An SDK's submodule pins differ from the release source snapshot") + if _archive_metadata(packages / name, prefix) != metadata: + raise RuntimeError("The SDK archive and its outer manifest disagree") + return assets, aggregate + + +def _tag_revision(api, tag): + response = api.request("GET", f"/git/ref/tags/{tag}", missing_ok=True) + if response is None: + return None + if not isinstance(response, dict) or response.get("ref") != "refs/tags/" + tag: + raise RuntimeError("GitHub returned an unexpected release tag") + value = response.get("object") + seen = set() + for _ in range(8): + if (not isinstance(value, dict) or not isinstance(value.get("sha"), str) + or not REVISION.fullmatch(value["sha"]) or value["sha"] in seen): + raise RuntimeError("The release tag has an invalid target") + if value.get("type") == "commit": + return value["sha"] + if value.get("type") != "tag": + raise RuntimeError("The release tag does not identify a commit") + seen.add(value["sha"]) + annotated = api.request("GET", f"/git/tags/{value['sha']}") + value = annotated.get("object") if isinstance(annotated, dict) else None + raise RuntimeError("The release tag has too many nested annotated tags") + + +def _release(api, tag): + matches = [release for release in api.pages("/releases") + if isinstance(release, dict) and release.get("tag_name") == tag] + if len(matches) > 1: + raise RuntimeError("GitHub returned multiple releases for one tag") + return matches[0] if matches else None + + +def _release_id(release): + identifier = release.get("id") if isinstance(release, dict) else None + if type(identifier) is not int or identifier <= 0: + raise RuntimeError("GitHub returned an invalid release ID") + return identifier + + +def _check_asset(asset, name, expected): + if (not isinstance(asset, dict) or asset.get("name") != name or asset.get("state") != "uploaded" + or asset.get("digest") != "sha256:" + expected["digest"] + or asset.get("size") != expected["path"].stat().st_size): + raise RuntimeError(f"An existing release asset differs from the checked SDK: {name}") + + +def _empty_starter(asset): + return (isinstance(asset, dict) and asset.get("state") == "starter" + and type(asset.get("id")) is int and asset["id"] > 0 + and type(asset.get("size")) is int and asset["size"] == 0) + + +def _assets(api, release, expected, *, allow_starters=False): + assets = {} + for asset in api.pages(f"/releases/{_release_id(release)}/assets"): + name = asset.get("name") if isinstance(asset, dict) else None + if name not in expected or name in assets: + raise RuntimeError("The release contains unexpected or duplicate assets") + if not (allow_starters and release.get("draft") is True and _empty_starter(asset)): + _check_asset(asset, name, expected[name]) + assets[name] = asset + return assets + + +def publish_release(api, packages, revision, versions, *, sleep=time.sleep, monotonic=time.monotonic, docs_timeout=600): + if api.repository != REPOSITORY or not isinstance(revision, str) or not REVISION.fullmatch(revision): + raise RuntimeError("Releases require the canonical repository and a full checked commit ID") + version = project_version(versions) + if release_pull(api, revision, version) is None: + return "not-release" + if not current_main(api, revision): + return "stale" + assets, _ = validate_release(packages, revision, versions) + wait_for_documentation(api, revision, sleep=sleep, monotonic=monotonic, timeout=docs_timeout) + tag = "v" + version + target = _tag_revision(api, tag) + if target is not None and target != revision: + raise RuntimeError("The release tag already identifies a different commit; it will not be moved") + release = _release(api, tag) + if release is not None and type(release.get("draft")) is not bool: + raise RuntimeError("GitHub returned an invalid release publication state") + if release is not None and release.get("prerelease") is not False: + raise RuntimeError("An existing release conflicts with the stable prerelease setting") + existing = _assets(api, release, assets, allow_starters=True) if release is not None else {} + if release is not None and release.get("draft") is not True: + if target != revision or set(existing) != set(assets): + raise RuntimeError("The published release does not match the complete checked SDK set") + return "unchanged" + if not current_main(api, revision): + return "stale" + if target is None: + created = api.request("POST", "/git/refs", {"ref": "refs/tags/" + tag, "sha": revision}) + if (not isinstance(created, dict) or created.get("ref") != "refs/tags/" + tag + or (created.get("object") or {}).get("sha") != revision + or (created.get("object") or {}).get("type") != "commit"): + raise RuntimeError("GitHub did not confirm the release tag at the accepted commit") + if release is None: + if not current_main(api, revision): + return "stale" + release = api.request("POST", "/releases", {"tag_name": tag, "target_commitish": revision, + "name": f"Cheryl {version}", "draft": True, "prerelease": False, + "generate_release_notes": True}) + _release_id(release) + if (release.get("tag_name") != tag or release.get("draft") is not True + or release.get("prerelease") is not False): + raise RuntimeError("GitHub did not create the expected draft release") + for name, value in sorted(assets.items()): + asset = existing.get(name) + if asset is not None and asset.get("state") == "uploaded": + continue + if asset is not None: + # Failed uploads can leave a zero-byte starter. Recheck its + # current state before removing only that unfinished draft asset. + latest_release = api.request("GET", f"/releases/{_release_id(release)}") + if (not isinstance(latest_release, dict) or latest_release.get("id") != release["id"] + or latest_release.get("tag_name") != tag or latest_release.get("draft") is not True + or latest_release.get("prerelease") is not False): + raise RuntimeError("The release is no longer the matching stable draft") + latest_asset = api.request("GET", f"/releases/assets/{asset['id']}") + if (not _empty_starter(latest_asset) or latest_asset.get("id") != asset["id"] + or latest_asset.get("name") != name): + raise RuntimeError("The unfinished release asset changed; it will not be deleted") + if _tag_revision(api, tag) != revision: + raise RuntimeError("The release tag changed before retrying an upload") + if not current_main(api, revision): + return "stale" + if api.request("DELETE", f"/releases/assets/{asset['id']}") is not None: + raise RuntimeError("GitHub did not confirm removal of the unfinished upload") + if not current_main(api, revision): + return "stale" + _check_asset(api.upload(release["upload_url"], value["path"]), name, value) + if set(_assets(api, release, assets)) != set(assets): + raise RuntimeError("The draft release is missing checked assets") + latest_release = api.request("GET", f"/releases/{_release_id(release)}") + if (not isinstance(latest_release, dict) or latest_release.get("id") != release["id"] + or latest_release.get("tag_name") != tag or latest_release.get("draft") is not True + or latest_release.get("prerelease") is not False): + raise RuntimeError("The release is no longer the matching stable draft") + if not current_main(api, revision): + return "stale" + if _tag_revision(api, tag) != revision: + raise RuntimeError("The release tag changed before publication") + result = api.request("PATCH", f"/releases/{_release_id(release)}", {"draft": False, "make_latest": "true"}) + if (not isinstance(result, dict) or result.get("id") != release["id"] + or result.get("tag_name") != tag or result.get("draft") is not False + or result.get("prerelease") is not False): + raise RuntimeError("GitHub did not confirm release publication") + return "published" + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--packages", type=Path, required=True) + parser.add_argument("--revision") + parser.add_argument("--assemble-only", action="store_true") + parser.add_argument("--source", type=Path, default=ROOT) + parser.add_argument("--output", type=Path) + parser.add_argument("--local-validation", action="store_true") + args = parser.parse_args(argv) + if args.assemble_only: + if args.output is None or args.revision is None: + parser.error("--assemble-only requires --output and --revision") + from versions import read_versions + versions = read_versions(_git(args.source, "show", args.revision + ":cmake/CherylVersions.cmake").decode("utf-8")) + assemble_release(args.packages, args.source, args.output, args.revision, versions, + local_validation=args.local_validation) + print(f"Assembled four release archives in {args.output}") + return 0 + if args.output is not None or args.local_validation: + parser.error("--output and --local-validation are only valid with --assemble-only") + revision = publication_revision(os.environ, args.revision) + if revision is None: + print("Release publication is disabled outside canonical main pushes.") + return 0 + from github_api import GitHubApi + from versions import read_versions + versions = read_versions((ROOT / "cmake/CherylVersions.cmake").read_text(encoding="utf-8")) + result = publish_release(GitHubApi(REPOSITORY, os.environ.get("GITHUB_TOKEN", "")), args.packages, revision, versions) + print(f"Release publication: {result}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.github/ci/run.py b/.github/ci/run.py index 60a6bd77..61cf6669 100644 --- a/.github/ci/run.py +++ b/.github/ci/run.py @@ -1,4 +1,4 @@ -"""Configure, build and check one explicitly selected CI composition.""" +"""Build or check one explicitly selected CI composition.""" import argparse import os @@ -18,19 +18,26 @@ def main(arguments=None): parser.add_argument("--source", type=Path, default=Path(__file__).resolve().parents[2]) parser.add_argument("--build-dir", type=Path, required=True) parser.add_argument("--profile", choices=("headless", "modules", "consumer"), required=True) + parser.add_argument("--phase", choices=("all", "build", "test"), default="all") parser.add_argument("--build-type", choices=("Release", "Debug", "RelWithDebInfo"), default="Release") + parser.add_argument("--linkage", choices=("static", "shared"), default="static") parser.add_argument("--hwloc", choices=("ON", "OFF"), default="OFF") parser.add_argument("--jobs", type=int, default=os.cpu_count() or 1) - parser.add_argument("--vcpkg-root", type=Path) + parser.add_argument("--vcpkg-root", type=Path, default=os.environ.get("CHERYL_CI_VCPKG_ROOT") if sys.platform == "win32" else None) + parser.add_argument("--vcpkg-installed-dir", type=Path, default=os.environ.get("CHERYL_CI_VCPKG_INSTALLED_DIR") if sys.platform == "win32" else None) parser.add_argument("--sdk", action="store_true") args = parser.parse_args(arguments) if args.jobs < 1: parser.error("--jobs must be positive") + if args.vcpkg_installed_dir and not args.vcpkg_root: + parser.error("--vcpkg-installed-dir requires --vcpkg-root") source = args.source.resolve() build = args.build_dir.resolve() modules = args.profile == "modules" testing = args.profile != "consumer" environment = os.environ.copy() + if sys.platform == "win32": + environment["PATH"] = str(build) + os.pathsep + environment.get("PATH", "") if testing: environment.setdefault("CHERYL_STB_ALLOCATION_TTF", str(source / "assets/fonts/DejaVuSans.ttf")) flag = lambda value: "ON" if value else "OFF" @@ -41,12 +48,15 @@ def main(arguments=None): "-DCHERYL_BUILD_DEBUG_TERMINAL_LINUX=OFF", f"-DCHERYL_BUILD_DEMO={flag(modules)}", f"-DCHERYL_BUILD_FIRST_GAME={flag(modules)}", f"-DCHERYL_BUILD_NATIVE_GLFW={flag(modules)}", f"-DCHERYL_BUILD_OPENGL={flag(modules)}", + "-DCHERYL_BUILD_STARTUP=ON", f"-DCHERYL_STARTUP_OPENGL={flag(modules)}", f"-DCHERYL_BUILD_UI_TGUI={flag(modules)}", f"-DCHERYL_BUILD_UI_RMLUI={flag(modules)}", f"-DCHERYL_BUILD_AUDIO_MINIAUDIO={flag(modules)}", f"-DCHERYL_BUILD_WORKER_TOPOLOGY_HWLOC={args.hwloc}", f"-DCHERYL_BUILD_WORKER_TOPOLOGY_JSON={flag(testing)}", "-DGAINPUT_ENABLE_HID=OFF", - "-DGLFW_BUILD_WAYLAND=OFF", "-Dgtest_force_shared_crt=ON", "-DBUILD_SHARED_LIBS=OFF", - "-DTGUI_SHARED_LIBS=OFF", f"-DCHERYL_BUILD_SDK={flag(args.sdk)}", "-DCMAKE_INSTALL_LIBDIR=lib"] + "-DGLFW_BUILD_WAYLAND=OFF", "-Dgtest_force_shared_crt=ON", + f"-DCHERYL_BUILD_SHARED_LIBS={flag(args.linkage == 'shared')}", + f"-DCHERYL_BUILD_SDK={flag(args.sdk)}", "-DCHERYL_BUILD_DOCS=OFF", + "-DCMAKE_INSTALL_LIBDIR=lib"] if modules and sys.platform == "win32": configure.append(f"-DPython_EXECUTABLE:FILEPATH={Path(sys.executable).resolve().as_posix()}") if sys.platform == "win32" and environment.get("CMAKE_CXX_COMPILER_LAUNCHER") == "ccache": @@ -54,17 +64,23 @@ def main(arguments=None): configure += ["-DCMAKE_POLICY_DEFAULT_CMP0141=NEW", "-DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=$<$:Embedded>"] if args.vcpkg_root: - features = (["hwloc"] if args.hwloc == "ON" else []) + (["native"] if modules else []) + features = (["hwloc"] if args.hwloc == "ON" else []) + (["native"] if modules and sys.platform == "win32" else []) + triplet = "x64-windows" if sys.platform == "win32" else "x64-linux-dynamic" configure += [f"-DCMAKE_TOOLCHAIN_FILE={args.vcpkg_root.resolve() / 'scripts/buildsystems/vcpkg.cmake'}", - f"-DVCPKG_MANIFEST_DIR={source / '.github/ci'}", "-DVCPKG_TARGET_TRIPLET=x64-windows", + f"-DVCPKG_MANIFEST_DIR={source / '.github/ci'}", f"-DVCPKG_TARGET_TRIPLET={triplet}", "-DVCPKG_MANIFEST_NO_DEFAULT_FEATURES=ON", f"-DVCPKG_MANIFEST_FEATURES={';'.join(features)}", - f"-DCMAKE_PROJECT_INCLUDE={source / '.github/ci/windows-hwloc.cmake'}"] - targets = ["consumer-cengine"] + f"-DCMAKE_PROJECT_INCLUDE={source / 'tools/dependencies/prepared.cmake'}"] + if sys.platform == "win32": + configure.append("-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded$<$:Debug>DLL") + if args.vcpkg_installed_dir: + configure += [f"-DVCPKG_INSTALLED_DIR={args.vcpkg_installed_dir.resolve().as_posix()}", + "-DVCPKG_MANIFEST_INSTALL=OFF"] + targets = ["consumer-cengine", "consumer-headers-cheryl", "consumer-module-startup"] if args.sdk: targets.append("cheryl-sdk") - consumers = ["cheryl-consumer"] - suites = ["tests-engine", "tests-logging", "acceptance-engine", "tests-worker-topology-json"] + consumers = ["cheryl-consumer", "cheryl-startup-consumer"] + suites = ["tests-engine", "tests-logging", "acceptance-engine", "acceptance-logging", "tests-worker-topology-json", "tests-startup"] if testing: targets += suites + ["consumer-worker-topology"] if args.hwloc == "ON": @@ -74,10 +90,17 @@ def main(arguments=None): owners = ("native-glfw", "opengl", "ui-tgui", "ui-rmlui", "audio-miniaudio") module_suites = [f"tests-{owner}" for owner in owners] + ["tests-ui-coexist"] targets += module_suites + [f"consumer-module-{owner}" for owner in owners] + ["demo", "first-game"] + targets.append("consumer-module-ui-tgui-native") suites += module_suites consumers += [f"cheryl-{owner}-consumer" for owner in owners] - run(configure, source, environment) - run(["cmake", "--build", build, "--target", *targets, "--parallel", str(args.jobs)], source, environment) + consumers.append("cheryl-ui-tgui-consumer-native") + if args.phase != "test": + run(configure, source, environment) + if modules: + run([sys.executable, source / "tools/input/generate_controls.py", "--check"], source, environment) + run(["cmake", "--build", build, "--target", *targets, "--parallel", str(args.jobs)], source, environment) + if args.phase == "build": + return if testing: # Owner runners only: no aggregates or native desktop/device suites. pattern = "^(" + "|".join(name + r"\." for name in suites) + "|consumer-worker-topology$)" diff --git a/.github/ci/sdk-consumer/CMakeLists.txt b/.github/ci/sdk-consumer/CMakeLists.txt index c1257e3f..44e1811a 100644 --- a/.github/ci/sdk-consumer/CMakeLists.txt +++ b/.github/ci/sdk-consumer/CMakeLists.txt @@ -1,7 +1,10 @@ cmake_minimum_required(VERSION 3.28) project(cheryl_sdk_consumer LANGUAGES CXX) -find_package(Cheryl CONFIG REQUIRED) +find_package(Cheryl 4 CONFIG REQUIRED) +if(NOT TARGET Cheryl::Cheryl) + message(FATAL_ERROR "The installed SDK must provide the Cheryl::Cheryl aggregate target") +endif() include("${CHERYL_REPOSITORY_ROOT}/cmake/CherylConsumers.cmake") enable_testing() @@ -28,6 +31,9 @@ endif() if(TARGET Cheryl::UI::TGUI) sdk_consumer(sdk-tgui projects/modules/ui/tgui/tests/consumer Cheryl::UI::TGUI ${TARGET_TEST_CONSUMER_MODULE_UI_TGUI_HEADERS}) + add_executable(sdk-tgui-native standalone/native-tgui.cpp) + target_link_libraries(sdk-tgui-native PRIVATE Cheryl::UI::TGUI) + add_test(NAME sdk-tgui-native COMMAND sdk-tgui-native) endif() if(TARGET Cheryl::UI::RmlUi) sdk_consumer(sdk-rmlui projects/modules/ui/rmlui/tests/consumer Cheryl::UI::RmlUi @@ -42,18 +48,48 @@ if(TARGET Cheryl::Audio::Miniaudio) "${CHERYL_REPOSITORY_ROOT}/projects/modules/audio/miniaudio/tests/support") endif() -cheryl_add_header_probes(sdk-startup Cheryl::Startup HEADERS cheryl/core/engine/startup.h) +if(TARGET Cheryl::Startup) + cheryl_add_header_probes(sdk-startup Cheryl::Startup NEUTRAL + HEADERS cheryl/application/startup.h cheryl/modules/startup.h cheryl/core/engine/startup.h cheryl/support/startup.h) + add_executable(sdk-startup-consumer "${CHERYL_REPOSITORY_ROOT}/projects/modules/runtime/startup/tests/consumer/src/main.cpp") + target_link_libraries(sdk-startup-consumer PRIVATE Cheryl::Startup) + add_test(NAME sdk-startup-consumer COMMAND sdk-startup-consumer) +endif() if(TARGET Cheryl::OpenGL::Startup) - cheryl_add_header_probes(sdk-opengl-startup Cheryl::OpenGL::Startup HEADERS cheryl/backends/opengl/startup.h) + cheryl_add_header_probes(sdk-opengl-startup Cheryl::OpenGL::Startup + HEADERS cheryl/backends/opengl/startup.h cheryl/support/opengl-startup.h) endif() -add_executable(sdk-worker "${CHERYL_REPOSITORY_ROOT}/projects/engine/support/worker-topology/tests/consumer.cpp") +add_executable(sdk-worker "${CHERYL_REPOSITORY_ROOT}/projects/modules/runtime/workers/tests/consumer.cpp") target_link_libraries(sdk-worker PRIVATE Cheryl::Engine) if(TARGET Cheryl::WorkerTopology::Hwloc) + cheryl_add_header_probes(sdk-worker-hwloc Cheryl::WorkerTopology::Hwloc NEUTRAL + HEADERS cheryl/support/worker-topology-hwloc.h) target_link_libraries(sdk-worker PRIVATE Cheryl::WorkerTopology::Hwloc) target_compile_definitions(sdk-worker PRIVATE CHERYL_CONSUMER_WORKER_HWLOC=1) endif() if(TARGET Cheryl::WorkerTopology::Json) + cheryl_add_header_probes(sdk-worker-json Cheryl::WorkerTopology::Json NEUTRAL + HEADERS cheryl/support/worker-topology-json.h) target_link_libraries(sdk-worker PRIVATE Cheryl::WorkerTopology::Json) target_compile_definitions(sdk-worker PRIVATE CHERYL_CONSUMER_WORKER_JSON=1) endif() add_test(NAME sdk-worker COMMAND sdk-worker) + +# Exercise Engine-owned state and exceptions across a separately compiled library. +get_target_property(sdk_engine_kind Cheryl::Engine TYPE) +set(sdk_bridge_kind STATIC) +if(sdk_engine_kind STREQUAL "SHARED_LIBRARY") + set(sdk_bridge_kind SHARED) +endif() +add_library(sdk-state-bridge ${sdk_bridge_kind} state-bridge.cpp) +target_link_libraries(sdk-state-bridge PRIVATE Cheryl::Engine) +target_compile_definitions(sdk-state-bridge PRIVATE SDK_STATE_BUILDING) +if(sdk_bridge_kind STREQUAL "STATIC") + target_compile_definitions(sdk-state-bridge PUBLIC SDK_STATE_STATIC) +endif() +set_target_properties(sdk-state-bridge PROPERTIES + CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN YES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}") +add_executable(sdk-state state.cpp) +target_link_libraries(sdk-state PRIVATE sdk-state-bridge Cheryl::Engine) +add_test(NAME sdk-state COMMAND sdk-state) diff --git a/.github/ci/sdk-consumer/standalone/CMakeLists.txt b/.github/ci/sdk-consumer/standalone/CMakeLists.txt new file mode 100644 index 00000000..2e3fad5d --- /dev/null +++ b/.github/ci/sdk-consumer/standalone/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.28) +project(cheryl_standalone_sdk_consumer LANGUAGES CXX) +find_package(Cheryl 4 CONFIG REQUIRED) +enable_testing() +add_executable(sdk-standalone main.cpp) +target_link_libraries(sdk-standalone PRIVATE Cheryl::Cheryl) +add_test(NAME sdk-standalone COMMAND sdk-standalone) +if(TARGET Cheryl::UI::TGUI) + add_executable(sdk-tgui-native native-tgui.cpp) + target_link_libraries(sdk-tgui-native PRIVATE Cheryl::UI::TGUI) + add_test(NAME sdk-tgui-native COMMAND sdk-tgui-native) +endif() diff --git a/.github/ci/sdk-consumer/standalone/main.cpp b/.github/ci/sdk-consumer/standalone/main.cpp new file mode 100644 index 00000000..b5ad6a0c --- /dev/null +++ b/.github/ci/sdk-consumer/standalone/main.cpp @@ -0,0 +1,61 @@ +#include +#include +#include + +#if defined(SPDLOG_VER_MAJOR) || defined(GLFW_VERSION_MAJOR) || defined(GAINPUT_VERSION_MAJOR) || defined(GLAD_GL_H_) || \ + defined(miniaudio_h) +#error A public SDK consumer must not inherit private native implementation headers. +#endif + +#if CHERYL_HAS_UI_TGUI +#include +using ButtonFactory = tgui::Button::Ptr (*)(const tgui::String&); +ButtonFactory volatile widget_factory = &tgui::Button::create; +#endif +#if CHERYL_HAS_UI_RMLUI +#include +#endif + +namespace { + constexpr char category[] = "standalone-sdk"; +} + +int main() { + CE::Logging::ProcessLifetime logging; + CE::Logging::Logger::set_level_logger(CE::Logging::Level::off); + if (CE::Logging::Logger::should_log(CE::Logging::Level::warn)) + return 1; + constexpr CE::Input::Control key{1, CE::Input::KeyboardKey::A}; + constexpr CE::Input::Control mouse{2, CE::Input::MouseButton::Left}; + constexpr CE::Input::Control pad{3, CE::Input::GamepadControl::A}; + if (key.control == CE::Input::invalid_control || mouse.control == CE::Input::invalid_control || + pad.control == CE::Input::invalid_control) + return 2; +#if CHERYL_HAS_OPENGL + using Setter = void (CE::Rendering::OpenGL::Program::*)(const char*, const glm::vec3&); + Setter volatile setter = &CE::Rendering::OpenGL::Program::set_uniform_vec; + if (!setter) + return 3; +#endif +#if CHERYL_HAS_STARTUP + using Factory = CE::Application::Startup (*)(std::string, CE::Application::ApplicationOptions); + Factory volatile factory = &CE::Application::make_startup; + if (!factory) + return 4; +#endif +#if CHERYL_HAS_UI_TGUI + if (!widget_factory) + return 5; +#endif +#if CHERYL_HAS_UI_RMLUI + if (Rml::GetVersion().empty()) + return 6; +#endif +#if CHERYL_HAS_AUDIO_MINIAUDIO + auto audio = CE::Audio::Miniaudio::System::open_offline(); + audio->close(); + if (!audio->closed()) + return 7; +#endif + return 0; +} diff --git a/.github/ci/sdk-consumer/standalone/native-tgui.cpp b/.github/ci/sdk-consumer/standalone/native-tgui.cpp new file mode 100644 index 00000000..5c24c5e5 --- /dev/null +++ b/.github/ci/sdk-consumer/standalone/native-tgui.cpp @@ -0,0 +1,34 @@ +#include +#include + +#include +#include + +namespace { + using MemoryLoader = void (tgui::Texture::*)(const std::uint8_t*, std::size_t, const tgui::UIntRect&, const tgui::UIntRect&, bool); + MemoryLoader volatile memory_loader = &tgui::Texture::loadFromMemory; +} + +int main() { + constexpr std::array image{ + 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 24, 0x20, 0, 0, 255 + }; + tgui::Vector2u size; + const auto pixels = tgui::ImageLoader::loadFromMemory(image.data(), image.size(), size); + if (!pixels) { + std::fputs("Native TGUI consumer: image decoding failed.\n", stderr); + return 1; + } + if (size != tgui::Vector2u{1, 1} || pixels[0] != 255 || pixels[1] != 0 || pixels[2] != 0 || pixels[3] != 255) { + std::fputs("Native TGUI consumer: image decoding returned unexpected dimensions or pixels.\n", stderr); + return 1; + } + tgui::Texture texture; + texture.setColor(tgui::Color::Red); + const tgui::Texture copy{texture}; + if (!memory_loader || copy.getColor() != tgui::Color::Red) { + std::fputs("Native TGUI consumer: texture entry point or color copy check failed.\n", stderr); + return 1; + } + return 0; +} diff --git a/.github/ci/sdk-consumer/state-bridge.cpp b/.github/ci/sdk-consumer/state-bridge.cpp new file mode 100644 index 00000000..0d579b6e --- /dev/null +++ b/.github/ci/sdk-consumer/state-bridge.cpp @@ -0,0 +1,37 @@ +#include "state-bridge.h" + +#include +#include +#include + +const void* sdk_event_system() { + return &CE::Events::EventSystem::get(); +} + +namespace { + constexpr char sdk_category[] = "sdk-custom"; +} + +std::uint16_t sdk_logging_id() { + return CE::Logging::Logger::get().get_log_id(); +} + +const void* sdk_logging_handle() { + return CE::Logging::Logger::get_existing(); +} + +void sdk_logging_quiet() { + CE::Logging::Logger::set_level_logger(CE::Logging::Level::off); +} + +bool sdk_logging_accepts_warning() { + return CE::Logging::Logger::should_log(CE::Logging::Level::warn); +} + +bool sdk_provider_bound(const CE::Rendering::Resources::Provider& provider) { + return CE::Assets::Cache::Context::is_bound_to(provider); +} + +void sdk_throw_exception() { + throw CE::Exceptions::runtime_exception(CE_HERE, "SDK bridge exception"); +} diff --git a/.github/ci/sdk-consumer/state-bridge.h b/.github/ci/sdk-consumer/state-bridge.h new file mode 100644 index 00000000..a95de43a --- /dev/null +++ b/.github/ci/sdk-consumer/state-bridge.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#if defined(SDK_STATE_STATIC) +#define SDK_STATE_API +#elif defined(_WIN32) +#if defined(SDK_STATE_BUILDING) +#define SDK_STATE_API __declspec(dllexport) +#else +#define SDK_STATE_API __declspec(dllimport) +#endif +#else +#define SDK_STATE_API __attribute__((visibility("default"))) +#endif + +namespace CE::Rendering::Resources { + struct Provider; +} + +SDK_STATE_API const void* sdk_event_system(); +SDK_STATE_API std::uint16_t sdk_logging_id(); +SDK_STATE_API const void* sdk_logging_handle(); +SDK_STATE_API void sdk_logging_quiet(); +SDK_STATE_API bool sdk_logging_accepts_warning(); +SDK_STATE_API bool sdk_provider_bound(const CE::Rendering::Resources::Provider& provider); +SDK_STATE_API void sdk_throw_exception(); diff --git a/.github/ci/sdk-consumer/state.cpp b/.github/ci/sdk-consumer/state.cpp new file mode 100644 index 00000000..8da57108 --- /dev/null +++ b/.github/ci/sdk-consumer/state.cpp @@ -0,0 +1,51 @@ +#include "state-bridge.h" + +#include +#include +#include +#include +#include + +#include +#include + +namespace { + constexpr char sdk_category[] = "sdk-custom"; + struct MemoryProvider final : CE::Rendering::Resources::Provider { + std::shared_ptr create_image(const CE::Assets::Images::Decoded&) override { return {}; } + std::shared_ptr + create_font_atlas(std::span, CE::Rendering::Geometry::PixelSize) override { + return {}; + } + std::shared_ptr + upload_geometry(std::span, CE::Rendering::Geometry::Topology) override { + return {}; + } + std::shared_ptr link_program(const std::vector&) override { return {}; } + }; + + struct CacheBinding : CE::Assets::Cache::Context { + using Context::bind_provider; + }; +} + +int main() { + CE::Logging::ProcessLifetime logging_lifetime; + const bool singleton = sdk_event_system() == &CE::Events::EventSystem::get(); + const bool logging_identity = sdk_logging_id() == CE::Logging::Logger::get().get_log_id() && + sdk_logging_handle() == CE::Logging::Logger::get_existing(); + sdk_logging_quiet(); + const bool logging_quiet = !CE::Logging::Logger::should_log(CE::Logging::Level::warn); + CE::Logging::Logger::set_level_logger(CE::Logging::Level::warn); + const bool logging = logging_identity && logging_quiet && sdk_logging_accepts_warning(); + MemoryProvider provider; + CacheBinding::bind_provider(provider); + const bool cache = sdk_provider_bound(provider); + bool exception = false; + try { + sdk_throw_exception(); + } catch (const CE::Exceptions::runtime_exception& error) { + exception = std::string_view{error.what()}.find("SDK bridge exception") != std::string_view::npos; + } + return singleton && logging && cache && exception ? 0 : 1; +} diff --git a/.github/ci/test_cache_cleanup.py b/.github/ci/test_cache_cleanup.py new file mode 100644 index 00000000..48582c0c --- /dev/null +++ b/.github/ci/test_cache_cleanup.py @@ -0,0 +1,605 @@ +"""Check cache retention decisions and API behavior without network access.""" + +from contextlib import redirect_stderr, redirect_stdout +from datetime import datetime, timedelta, timezone +import importlib.util +import io +import os +from pathlib import Path +import unittest +from unittest.mock import Mock, call, patch +from urllib.error import HTTPError + + +spec = importlib.util.spec_from_file_location( + "cache_cleanup", Path(__file__).with_name("cache_cleanup.py") +) +cleanup = importlib.util.module_from_spec(spec) +spec.loader.exec_module(cleanup) + +NOW = datetime(2026, 10, 15, 12, tzinfo=timezone.utc) +GIB = 1024 ** 3 +COMPILER = "ccache-v1-ubuntu-24.04-X64-linux-gcc-hwloc-Release" +LAYERED = COMPILER.replace("ccache-v1-", "ccache-v2-") +POOLED = "ccache-v3-Linux-X64-linux-gcc14" +BUILD_DEPS = "build-deps-v2-Windows-X64-main-modules-binaries-" + "a" * 64 +VCPKG = "vcpkg-v1-Windows-X64-msvc" + + +def cache(identifier, key=None, *, ref="refs/heads/develop", version="v1", + size=100, age=21600, accessed=3600): + def timestamp(seconds): + return (NOW - timedelta(seconds=seconds)).isoformat().replace("+00:00", "Z") + + return { + "id": identifier, + "key": key or f"{COMPILER}-{identifier}-1", + "ref": ref, + "version": version, + "size_in_bytes": size, + "created_at": timestamp(age), + "last_accessed_at": timestamp(accessed), + } + + +def plan(caches, **options): + return cleanup.plan_caches(caches, **{ + "default_branch": "develop", + "closed_prs": set(), + "open_prs": set(), + "active_refs": set(), + "now": NOW, + **options, + }) + + +def removed(result): + return {row["id"]: row["reason"] for row in result["deletions"]} + + +class PlanTests(unittest.TestCase): + def test_empty(self): + result = plan([]) + self.assertEqual(removed(result), {}) + self.assertEqual(result["before_bytes"], 0) + self.assertEqual(result["after_bytes"], 0) + + def test_lineages(self): + for key, expected in ( + (f"{COMPILER}-12345-2", COMPILER), + (f"{LAYERED}-main-12345-2", f"{LAYERED}-main"), + (f"{POOLED}-main-12345-2", f"{POOLED}-main"), + (f"{POOLED}-pr-12345-2", f"{POOLED}-pr"), + ("dependencies-v1-Windows-X64-modules-abcd-12345-2", + "dependencies-v1-Windows-X64-modules-abcd"), + ("pip-v1-Windows-X64-cpython-312-abcd-12345-2", + "pip-v1-Windows-X64-cpython-312-abcd"), + (f"{BUILD_DEPS}-12345-2", BUILD_DEPS), + (f"{VCPKG}-12345-2", VCPKG), + ): + with self.subTest(key=key): + self.assertEqual(cleanup.lineage(key), expected) + for key in ( + "windows-tools-v1-Windows-X64-baseline-ccache-4.14.0", + "submodule-git-v1-Linux-X64-12345-2", + "apt-debs-v1-Linux-X64-12345-2", + "docs-tools-v1-Linux-X64-12345-2", + "unrelated-12345-2", + f"{COMPILER}-12345-retry", + "build-deps-v2-Linux-X64-main-sources-12345-2", + "build-deps-v2-Linux-X64-main-sources-" + "a" * 64, + "actions-env-cache-v2-Linux-X64-modules-identity-tools-12345-2", + ): + with self.subTest(key=key): + self.assertIsNone(cleanup.lineage(key)) + + def test_latest(self): + older = cache(99, age=18000, accessed=1800) + newer = cache(1, age=7200, accessed=3600) + result = plan([older, newer]) + self.assertEqual(removed(result), {99: "superseded snapshot"}) + self.assertEqual(result["after_bytes"], newer["size_in_bytes"]) + + def test_tie(self): + result = plan([cache(1), cache(2)]) + self.assertEqual(removed(result), {1: "superseded snapshot"}) + + def test_isolation(self): + rows = [ + cache(1), + cache(2, ref="refs/heads/main"), + cache(3, version="v2"), + cache(4, key="ccache-v1-ubuntu-24.04-X64-linux-clang-debug-Debug-4-1"), + ] + self.assertEqual(removed(plan(rows)), {}) + + def test_immutable(self): + rows = [cache(1, key="apt-debs-v1-Linux-X64-123-1"), + cache(2, key="apt-debs-v1-Linux-X64-456-1")] + self.assertEqual(removed(plan(rows)), {}) + + def test_build_sources(self): + prefix = "build-deps-v2-Linux-X64-main-sources-" + rows = [cache(1, key=prefix + "a" * 64, ref="refs/heads/main"), + cache(2, key=prefix + "b" * 64, ref="refs/heads/main")] + self.assertEqual(removed(plan(rows)), {}) + + def test_source_priorities(self): + main = "build-deps-v2-Linux-X64-main-sources-" + develop = main.replace("-main-", "-develop-") + rows = [cache(1, key=main + "a" * 64, ref="refs/heads/main", size=GIB, accessed=1800), + cache(2, key=main + "b" * 64, ref="refs/heads/main", size=GIB, age=7200, accessed=18000), + cache(3, key=develop + "a" * 64, size=GIB, accessed=1800), + cache(4, key=develop + "b" * 64, size=GIB, age=7200, accessed=14400), + cache(5, key="ordinary", ref="refs/heads/feature", size=GIB)] + result = plan(rows, default_branch="main", budget_bytes=2 * GIB) + self.assertEqual(removed(result), {1: "least recently used", 3: "least recently used", + 5: "least recently used"}) + result = plan(rows, default_branch="main", budget_bytes=GIB) + self.assertEqual(removed(result), {1: "least recently used", 3: "least recently used", + 5: "least recently used", 4: "develop baseline"}) + + def test_source_isolation(self): + prefix = "build-deps-v2-Linux-X64-main-sources-" + rows = [cache(1, key=prefix + "a" * 64, ref="refs/heads/main", size=GIB), + cache(2, key=prefix + "b" * 64, ref="refs/heads/main", size=GIB, age=7200), + cache(3, key=prefix.replace("Linux-X64", "Windows-X64") + "a" * 64, + ref="refs/heads/main", size=GIB, accessed=18000), + cache(4, key=prefix + "a" * 64, size=GIB, accessed=14400), + cache(5, key=prefix + "a" * 64, ref="refs/heads/main", version="other-path", + size=GIB, accessed=10800)] + result = plan(rows, default_branch="main", budget_bytes=4 * GIB) + self.assertEqual(removed(result), {1: "least recently used"}) + + def test_build_compaction(self): + rows = [cache(1, key=f"{BUILD_DEPS}-1-1", ref="refs/heads/main", age=18000), + cache(2, key=f"{BUILD_DEPS}-2-1", ref="refs/heads/main", age=7200)] + self.assertEqual(removed(plan(rows)), {1: "superseded snapshot"}) + + def test_vcpkg_pool(self): + rows = [cache(1, key=f"{VCPKG}-1-1", ref="refs/heads/main", size=GIB, age=18000), + cache(2, key=f"{VCPKG}-2-1", ref="refs/heads/main", size=GIB, age=7200), + cache(3, key="ordinary", ref="refs/heads/feature", size=GIB)] + result = plan(rows, default_branch="main", budget_bytes=GIB) + self.assertEqual(removed(result), {1: "superseded snapshot", 3: "least recently used"}) + + def test_vcpkg_versions(self): + rows = [cache(1, key=f"{VCPKG}-1-1", ref="refs/heads/main"), + cache(2, key=f"{VCPKG}-2-1", ref="refs/heads/main", version="other-path"), + cache(3, key=f"{VCPKG}-3-1", ref="refs/heads/develop")] + self.assertEqual(removed(plan(rows, default_branch="main")), {}) + + def test_build_isolation(self): + rows = [cache(1, key=f"{BUILD_DEPS}-1-1", ref="refs/heads/main"), + cache(2, key=f"{BUILD_DEPS}-2-1", ref="refs/heads/develop"), + cache(3, key=f"{BUILD_DEPS}-3-1", ref="refs/heads/main", version="other-path"), + cache(4, key=BUILD_DEPS.replace("-modules-", "-headless-") + "-4-1", ref="refs/heads/main"), + cache(5, key=BUILD_DEPS.replace("a" * 64, "b" * 64) + "-5-1", ref="refs/heads/main"), + cache(6, key=f"{LAYERED}-main-6-1", ref="refs/heads/main")] + self.assertEqual(removed(plan(rows)), {}) + + def test_build_priorities(self): + rows = [cache(1, key=f"{BUILD_DEPS}-1-1", ref="refs/heads/main", size=5 * GIB, accessed=18000), + cache(2, key=BUILD_DEPS.replace("-main-", "-develop-") + "-2-1", size=4 * GIB)] + self.assertEqual(removed(plan(rows, default_branch="main")), {2: "develop baseline"}) + rows[1]["ref"] = "refs/pull/3/merge" + for open_prs, expected in (({3}, "open pull request baseline"), (set(), "least recently used")): + with self.subTest(open_prs=open_prs): + self.assertEqual(removed(plan(rows, default_branch="main", open_prs=open_prs)), {2: expected}) + + def test_environment_versions(self): + for revision in (2, 3): + with self.subTest(revision=revision): + rows = [cache(1, key=f"actions-env-cache-v{revision}-Linux-X64-modules-identity-tools", + ref="refs/heads/main", size=5 * GIB, accessed=18000), + cache(2, key="ordinary", ref="refs/heads/feature", size=4 * GIB)] + result = plan(rows, default_branch="main") + self.assertEqual(removed(result), {2: "least recently used"}) + + def test_closed(self): + rows = [cache(1, ref="refs/pull/3/merge"), + cache(2, ref="refs/pull/30/merge"), + cache(3, ref="refs/heads/pull/3/merge"), + cache(4), cache(5)] + result = plan(rows, closed_prs={3}, closed_pr=3) + self.assertEqual(removed(result), {1: "closed pull request"}) + self.assertEqual(result["before_bytes"], 500) + self.assertEqual(result["after_bytes"], 400) + + def test_closed_only(self): + rows = [cache(1, ref="refs/pull/3/merge"), + cache(2, ref="refs/pull/4/merge", size=10 * GIB)] + result = plan(rows, closed_prs={3, 4}, closed_pr=3) + self.assertEqual(removed(result), {1: "closed pull request"}) + + def test_closed_families(self): + keys = [f"{COMPILER}-1-1", f"{LAYERED}-pr-2-1", "dependencies-v1-Windows-X64-modules-hash-3-1", + "pip-v1-Windows-X64-cpython-313-hash-4-1", f"{BUILD_DEPS}-5-1", + "build-deps-v2-Linux-X64-main-sources-hash", "actions-env-cache-v2-Linux-X64-docs-identity-tools", + f"{POOLED}-pr-8-1"] + rows = [cache(index, key=key, ref="refs/pull/3/merge") for index, key in enumerate(keys, 1)] + rows.append(cache(len(keys) + 1, ref="refs/pull/30/merge")) + result = plan(rows, closed_prs={3}, closed_pr=3) + self.assertEqual(removed(result), {index: "closed pull request" for index in range(1, len(keys) + 1)}) + + def test_unconfirmed(self): + result = plan([cache(1, ref="refs/pull/3/merge")], closed_pr=3) + self.assertEqual(removed(result), {}) + + def test_open(self): + self.assertEqual(removed(plan([cache(1, ref="refs/pull/3/merge")])), {}) + + def test_budget(self): + rows = [cache(1, key="other-old", ref="refs/pull/1/merge", + size=3 * GIB, accessed=18000), + cache(2, key="other-middle", ref="refs/pull/2/merge", + size=3 * GIB, accessed=10800), + cache(3, key="other-new", ref="refs/pull/3/merge", + size=3 * GIB, accessed=3600)] + result = plan(rows) + self.assertEqual(removed(result), {1: "least recently used"}) + self.assertEqual(result["after_bytes"], 6 * GIB) + + def test_baseline(self): + rows = [cache(1, size=5 * GIB, accessed=18000), + cache(2, ref="refs/pull/3/merge", size=4 * GIB, accessed=3600)] + result = plan(rows) + self.assertEqual(removed(result), {2: "least recently used"}) + self.assertEqual(result["after_bytes"], 5 * GIB) + + def test_large_baseline(self): + result = plan([cache(1, size=9 * GIB)]) + self.assertEqual(removed(result), {1: "develop baseline"}) + self.assertEqual(result["after_bytes"], 0) + + def test_both_branches(self): + rows = [cache(1, ref="refs/heads/main", size=3 * GIB, accessed=18000), + cache(2, size=3 * GIB, accessed=14400), + cache(3, key="ordinary", ref="refs/heads/feature", size=3 * GIB)] + result = plan(rows) + self.assertEqual(removed(result), {3: "least recently used"}) + self.assertEqual(result["after_bytes"], 6 * GIB) + + def test_open_priority(self): + rows = [cache(1, ref="refs/pull/3/merge", size=5 * GIB, accessed=18000), + cache(2, key="ordinary", ref="refs/heads/feature", size=4 * GIB)] + for open_prs, expected in (({3}, {2}), (set(), {1})): + with self.subTest(open_prs=open_prs): + self.assertEqual(set(removed(plan(rows, open_prs=open_prs))), expected) + + def test_open_compaction(self): + rows = [cache(1, ref="refs/pull/3/merge", age=18000), + cache(2, ref="refs/pull/3/merge", age=7200)] + result = plan(rows, open_prs={3}) + self.assertEqual(removed(result), {1: "superseded snapshot"}) + + def test_open_noncompiler(self): + rows = [cache(1, key="dependencies-v1-Windows-X64-modules-hash-1-1", + ref="refs/pull/3/merge", size=5 * GIB, accessed=18000), + cache(2, key="ordinary", ref="refs/heads/feature", size=4 * GIB)] + result = plan(rows, open_prs={3}) + self.assertEqual(removed(result), {1: "least recently used"}) + + def test_tiers(self): + rows = [cache(1, ref="refs/heads/main", size=3 * GIB, accessed=18000), + cache(2, size=3 * GIB, accessed=14400), + cache(3, ref="refs/pull/3/merge", size=3 * GIB, accessed=10800), + cache(4, key="ordinary", ref="refs/heads/feature", size=GIB)] + for budget, expected in ((9, [4]), (8, [4, 3]), (5, [4, 3, 2]), (2, [4, 3, 2, 1])): + with self.subTest(budget=budget): + result = plan(rows, open_prs={3}, budget_bytes=budget * GIB) + self.assertEqual([row["id"] for row in result["deletions"]], expected) + self.assertLessEqual(result["after_bytes"], budget * GIB) + result = plan(rows, open_prs={3}, budget_bytes=2 * GIB) + self.assertEqual(removed(result), {4: "least recently used", 3: "open pull request baseline", + 2: "develop baseline", 1: "main baseline"}) + + def test_tool_baseline(self): + for branch in ("develop", "main"): + with self.subTest(branch=branch): + rows = [cache(1, key="docs-tools-v1-Linux-X64-identity", ref=f"refs/heads/{branch}", + size=5 * GIB, accessed=18000), + cache(2, ref="refs/pull/3/merge", size=4 * GIB)] + result = plan(rows, default_branch=branch, open_prs={3}) + self.assertEqual(removed(result), {2: "open pull request baseline"}) + + def test_layered_compaction(self): + rows = [cache(1, key=f"{LAYERED}-main-1-1", ref="refs/heads/main", age=18000), + cache(2, key=f"{LAYERED}-main-2-1", ref="refs/heads/main", age=7200), + cache(3, key=f"{LAYERED}-develop-3-1")] + self.assertEqual(removed(plan(rows)), {1: "superseded snapshot"}) + + def test_pool_compaction(self): + rows = [cache(1, key=f"{POOLED}-main-1-1", ref="refs/heads/main", age=18000), + cache(2, key=f"{POOLED}-main-2-1", ref="refs/heads/main", age=7200), + cache(3, key=POOLED.replace("gcc14", "clang18") + "-main-3-1", ref="refs/heads/main"), + cache(4, key=f"{POOLED}-develop-4-1"), + cache(5, key=f"{POOLED}-main-5-1", ref="refs/heads/main", version="other-path"), + cache(6, key=POOLED.replace("X64", "ARM64") + "-main-6-1", ref="refs/heads/main")] + self.assertEqual(removed(plan(rows)), {1: "superseded snapshot"}) + + def test_pool_tiers(self): + rows = [cache(1, key=f"{POOLED}-main-1-1", ref="refs/heads/main", size=3 * GIB, accessed=18000), + cache(2, key=f"{POOLED}-develop-2-1", size=3 * GIB, accessed=14400), + cache(3, key=f"{POOLED}-pr-3-1", ref="refs/pull/3/merge", size=3 * GIB, accessed=10800), + cache(4, key="ordinary", ref="refs/heads/feature", size=GIB)] + result = plan(rows, open_prs={3}, budget_bytes=2 * GIB) + self.assertEqual(removed(result), {4: "least recently used", 3: "open pull request baseline", + 2: "develop baseline", 1: "main baseline"}) + self.assertEqual([row["id"] for row in result["deletions"]], [4, 3, 2, 1]) + + def test_pool_migration(self): + for runner, profile, configuration, operating_system, family in ( + ("ubuntu-24.04", "linux-gcc-hwloc", "Release", "Linux", "linux-gcc14"), + ("ubuntu-24.04", "linux-gcc-neutral", "Release", "Linux", "linux-gcc14"), + ("ubuntu-24.04", "linux-modules", "Release", "Linux", "linux-gcc14"), + ("ubuntu-24.04", "linux-modules-shared", "Release", "Linux", "linux-gcc14"), + ("ubuntu-24.04", "linux-clang-debug", "Debug", "Linux", "linux-clang18"), + ("windows-2025", "windows-msvc-hwloc", "RelWithDebInfo", "Windows", "windows-msvc"), + ("windows-2025", "windows-modules", "RelWithDebInfo", "Windows", "windows-msvc"), + ("windows-2025", "windows-modules-shared", "RelWithDebInfo", "Windows", "windows-msvc"), + ): + with self.subTest(profile=profile): + legacy = f"{runner}-X64-{profile}-{configuration}" + pooled = f"ccache-v3-{operating_system}-X64-{family}" + rows = [cache(1, key=f"ccache-v1-{legacy}-1-1", ref="refs/heads/main"), + cache(2, key=f"ccache-v2-{legacy}-main-2-1", ref="refs/heads/main"), + cache(3, key=f"{pooled}-main-3-1", ref="refs/heads/main")] + self.assertEqual(removed(plan(rows)), {1: "replaced compiler snapshot", 2: "replaced compiler snapshot"}) + + def test_pool_migration_scope(self): + legacy = cache(1, key=f"{LAYERED}-main-1-1", ref="refs/heads/main") + for update in ({"ref": "refs/heads/develop"}, + {"key": POOLED.replace("gcc14", "clang18") + "-main-2-1"}, + {"key": POOLED.replace("X64", "ARM64") + "-main-2-1"}, + {"key": "ccache-v3-Windows-X64-windows-msvc-main-2-1"}): + with self.subTest(update=update): + pooled = {**cache(2, key=f"{POOLED}-main-2-1", ref="refs/heads/main"), **update} + self.assertEqual(removed(plan([legacy, pooled])), {}) + + def test_pool_migration_unknown(self): + keys = [LAYERED.replace("linux-gcc-hwloc", "custom-gcc") + "-main-1-1", + LAYERED.replace("ubuntu-24.04", "ubuntu-26.04") + "-main-2-1", + LAYERED.replace("Release", "Debug") + "-main-3-1", + f"{POOLED}-main-4-1"] + rows = [cache(index, key=key, ref="refs/heads/main") for index, key in enumerate(keys, 1)] + self.assertEqual(removed(plan(rows)), {}) + + def test_pool_migration_protection(self): + for update, options in (({}, {"active_refs": {"refs/heads/main"}}), + ({}, {"unresolved_refs": {"refs/heads/main"}}), + ({"age": 60}, {}), ({"accessed": 60}, {})): + with self.subTest(update=update, options=options): + rows = [cache(1, key=f"{LAYERED}-main-1-1", ref="refs/heads/main", **update), + cache(2, key=f"{POOLED}-main-2-1", ref="refs/heads/main")] + result = plan(rows, **options) + self.assertEqual(removed(result), {}) + self.assertIn(1, result["protected_ids"]) + + def test_migration(self): + rows = [cache(1, ref="refs/heads/main", size=5 * GIB), + cache(2, key=f"{LAYERED}-main-2-1", ref="refs/heads/main", + size=4 * GIB, accessed=18000)] + self.assertEqual(removed(plan(rows)), {1: "least recently used"}) + self.assertEqual(removed(plan(rows, budget_bytes=10 * GIB)), {}) + + def test_migration_scope(self): + rows = [cache(1, ref="refs/heads/main", size=5 * GIB), + cache(2, key=f"{LAYERED}-develop-2-1", size=4 * GIB, accessed=18000)] + self.assertEqual(removed(plan(rows)), {2: "develop baseline"}) + + def test_migration_profile(self): + other = "ccache-v2-ubuntu-24.04-X64-linux-clang-debug-Debug" + rows = [cache(1, ref="refs/heads/main", size=5 * GIB), + cache(2, key=f"{other}-main-2-1", ref="refs/heads/main", + size=4 * GIB, accessed=18000)] + self.assertEqual(removed(plan(rows)), {2: "main baseline"}) + + def test_active(self): + rows = [cache(1, size=5 * GIB), cache(2, size=4 * GIB)] + result = plan(rows, active_refs={"refs/heads/develop"}) + self.assertEqual(removed(result), {}) + self.assertEqual(set(result["protected_ids"]), {1, 2}) + self.assertEqual(result["protected_bytes"], 9 * GIB) + + def test_recent(self): + row = cache(1, ref="refs/pull/3/merge", age=300, accessed=60) + result = plan([row], closed_prs={3}) + self.assertEqual(removed(result), {}) + self.assertIn(1, result["protected_ids"]) + + def test_recent_access(self): + result = plan([cache(1, ref="refs/pull/3/merge", accessed=60)], closed_prs={3}) + self.assertEqual(removed(result), {}) + self.assertIn(1, result["protected_ids"]) + + def test_closed_active(self): + result = plan([cache(1, ref="refs/pull/3/merge")], closed_prs={3}, + closed_pr=3, active_refs={"refs/pull/3/merge"}) + self.assertEqual(removed(result), {}) + + def test_unresolved(self): + result = plan([cache(1, ref="refs/pull/3/merge", size=9 * GIB)], + closed_prs={3}, unresolved_refs={"refs/pull/3/merge"}) + self.assertEqual(removed(result), {}) + self.assertIn(1, result["protected_ids"]) + + def test_copy(self): + rows = [cache(1), cache(2)] + original = [dict(row) for row in rows] + plan(rows) + self.assertEqual(rows, original) + + def test_invalid(self): + for update in ({"id": "invalid"}, {"size_in_bytes": -1}, + {"created_at": "invalid"}, {"last_accessed_at": "invalid"}): + with self.subTest(update=update), self.assertRaises((ValueError, RuntimeError)): + plan([{**cache(1), **update}]) + + +class ApiTests(unittest.TestCase): + def test_dry_run(self): + api = Mock(spec=cleanup.GitHubApi) + with redirect_stdout(io.StringIO()): + result = cleanup.apply_plan(api, plan([cache(1), cache(2)])) + api.request.assert_not_called() + self.assertEqual(result["deleted_ids"], []) + self.assertTrue(result["dry_run"]) + + def test_noop(self): + for rows in ([], [cache(1)]): + with self.subTest(rows=rows): + api = Mock(spec=cleanup.GitHubApi) + with redirect_stdout(io.StringIO()): + result = cleanup.apply_plan(api, plan(rows), apply=True) + api.request.assert_not_called() + self.assertEqual(result["deleted_ids"], []) + + def test_delete(self): + api = Mock(spec=cleanup.GitHubApi) + api.request.return_value = {} + with redirect_stdout(io.StringIO()): + result = cleanup.apply_plan(api, plan([cache(1), cache(2)]), apply=True) + api.request.assert_called_once_with("DELETE", "/actions/caches/1", missing_ok=True) + self.assertEqual(result["deleted_ids"], [1]) + self.assertEqual(result["missing_ids"], []) + self.assertFalse(result["dry_run"]) + + def test_missing(self): + api = Mock(spec=cleanup.GitHubApi) + api.request.return_value = None + with redirect_stdout(io.StringIO()): + result = cleanup.apply_plan(api, plan([cache(1), cache(2)]), apply=True) + self.assertEqual(result["deleted_ids"], []) + self.assertEqual(result["missing_ids"], [1]) + + def test_pages(self): + api = cleanup.GitHubApi("cppcooper/cheryl-engine", "fixture-token") + first = [{"id": identifier} for identifier in range(100)] + last = [{"id": 100}] + api.request = Mock(side_effect=[{"actions_caches": first}, {"actions_caches": last}]) + self.assertEqual(api.pages("/actions/caches", "actions_caches"), first + last) + self.assertEqual(api.request.call_args_list, [ + call("GET", "/actions/caches", per_page=100, page=1), + call("GET", "/actions/caches", per_page=100, page=2), + ]) + + def test_http_missing(self): + api = cleanup.GitHubApi("cppcooper/cheryl-engine", "fixture-token") + error = HTTPError("https://api.github.com", 404, "Not Found", {}, io.BytesIO()) + self.addCleanup(error.close) + api.opener = Mock() + api.opener.open.side_effect = error + self.assertIsNone(api.request("DELETE", "/actions/caches/1", missing_ok=True)) + with self.assertRaisesRegex(RuntimeError, "HTTP 404"): + api.request("DELETE", "/actions/caches/1") + + +class StateTests(unittest.TestCase): + def runs(self, *records, status="in_progress"): + api = Mock(spec=cleanup.GitHubApi) + api.pages.side_effect = lambda path, field, **query: list(records) if query["status"] == status else [] + return api + + def test_pull_states(self): + api = Mock(spec=cleanup.GitHubApi) + api.request.side_effect = [None, {"state": "open"}] + rows = [cache(1, ref="refs/pull/3/merge"), cache(2, ref="refs/pull/30/merge"), + cache(3, ref="refs/pull/3/merge")] + closed, unresolved = cleanup.pull_states(api, rows) + self.assertEqual(closed, set()) + self.assertEqual(unresolved, {"refs/pull/3/merge"}) + self.assertEqual(api.request.call_args_list, [ + call("GET", "/pulls/3", missing_ok=True), + call("GET", "/pulls/30", missing_ok=True), + ]) + result = plan(rows, closed_prs=closed, unresolved_refs=unresolved) + self.assertEqual(removed(result), {}) + self.assertEqual(set(result["protected_ids"]), {1, 3}) + + def test_active_pull(self): + api = self.runs({"id": 1, "head_branch": "feature", "event": "pull_request", + "pull_requests": [{"number": 3, "base": {"ref": "develop"}}]}) + with patch.dict(os.environ, {}, clear=True): + refs = cleanup.active_refs(api, []) + self.assertTrue({"refs/heads/feature", "refs/pull/3/merge", "refs/heads/develop"} <= refs) + + def test_empty_pulls(self): + rows = [cache(1), cache(2, ref="refs/pull/3/merge"), cache(3, ref="refs/heads/main")] + for metadata in ({}, {"pull_requests": []}): + with self.subTest(metadata=metadata): + api = self.runs({"id": 1, "event": "pull_request", "head_branch": "feature", **metadata}) + with patch.dict(os.environ, {}, clear=True): + refs = cleanup.active_refs(api, rows) + self.assertTrue({row["ref"] for row in rows} <= refs) + self.assertEqual(removed(plan(rows, active_refs=refs)), {}) + + def test_active_tag(self): + api = self.runs({"id": 1, "head_branch": "v4.0.0", "event": "push", "pull_requests": []}) + rows = [cache(1, ref="refs/tags/v4.0.0", size=9 * GIB)] + with patch.dict(os.environ, {}, clear=True): + refs = cleanup.active_refs(api, rows) + self.assertIn("refs/tags/v4.0.0", refs) + self.assertEqual(removed(plan(rows, active_refs=refs)), {}) + + def test_missing_ref(self): + rows = [cache(1), cache(2), cache(3, ref="refs/pull/3/merge")] + api = self.runs({"id": 1, "event": "push", "head_branch": None, "pull_requests": []}, + status="waiting") + with patch.dict(os.environ, {}, clear=True): + refs = cleanup.active_refs(api, rows) + self.assertTrue({row["ref"] for row in rows} <= refs) + self.assertEqual(removed(plan(rows, active_refs=refs)), {}) + + def test_maintenance(self): + api = self.runs( + {"id": 1, "head_branch": "develop", "path": ".github/workflows/cache-maintenance.yml@develop"}, + {"id": 2, "head_branch": "main", "name": "Maintain Actions caches"}, + {"id": 3, "head_branch": "current-run"}, + status="queued", + ) + with patch.dict(os.environ, {"GITHUB_RUN_ID": "3"}, clear=True): + self.assertEqual(cleanup.active_refs(api, []), set()) + + def test_unconfirmed_main(self): + api = Mock(spec=cleanup.GitHubApi) + api.request.side_effect = [{"default_branch": "develop"}, None] + api.pages.return_value = [cache(1, ref="refs/pull/3/merge")] + with patch.object(cleanup, "GitHubApi", return_value=api), \ + patch.object(cleanup.sys, "argv", ["cache_cleanup.py", "--apply", "--closed-pr", "3"]), \ + patch.dict(os.environ, {}, clear=True), redirect_stdout(io.StringIO()): + self.assertEqual(cleanup.main(), 0) + self.assertEqual(api.request.call_args_list, [ + call("GET", ""), call("GET", "/pulls/3", missing_ok=True), + ]) + + def test_open_main(self): + api = Mock(spec=cleanup.GitHubApi) + api.request.side_effect = [{"default_branch": "develop"}, {"state": "open"}, + {"state": "closed"}, None] + rows = [cache(number, ref=f"refs/pull/{number}/merge") for number in (3, 4, 5)] + api.pages.side_effect = lambda path, field, **query: rows if path == "/actions/caches" else [] + decision = {"before_bytes": 300, "after_bytes": 300, "protected_bytes": 100, + "deletions": [], "protected_ids": [5]} + with patch.object(cleanup, "GitHubApi", return_value=api), \ + patch.object(cleanup, "plan_caches", return_value=decision) as select, \ + patch.object(cleanup.sys, "argv", ["cache_cleanup.py"]), \ + patch.dict(os.environ, {}, clear=True), redirect_stdout(io.StringIO()): + self.assertEqual(cleanup.main(), 0) + self.assertEqual(select.call_args.kwargs["open_prs"], {3}) + self.assertEqual(select.call_args.kwargs["closed_prs"], {4}) + self.assertEqual(select.call_args.kwargs["unresolved_refs"], {"refs/pull/5/merge"}) + self.assertTrue(all(invocation.args[0] == "GET" for invocation in api.request.call_args_list)) + + def test_invalid_pr(self): + for number in ("0", "-1", "invalid"): + with self.subTest(number=number), patch.object(cleanup, "GitHubApi") as api, \ + patch.object(cleanup.sys, "argv", ["cache_cleanup.py", "--closed-pr", number]), \ + redirect_stderr(io.StringIO()), self.assertRaises(SystemExit) as stopped: + cleanup.main() + self.assertEqual(stopped.exception.code, 2) + api.assert_not_called() + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/ci/test_ci.py b/.github/ci/test_ci.py index ba359ce9..fa53e953 100644 --- a/.github/ci/test_ci.py +++ b/.github/ci/test_ci.py @@ -2,7 +2,9 @@ from contextlib import redirect_stdout import importlib.util +import hashlib import io +from itertools import product import json from pathlib import Path import re @@ -17,9 +19,16 @@ spec = importlib.util.spec_from_file_location("ci_run", Path(__file__).with_name("run.py")) ci = importlib.util.module_from_spec(spec) spec.loader.exec_module(ci) +spec = importlib.util.spec_from_file_location("ci_configure_components", Path(__file__).with_name("configure_components.py")) +configure_components = importlib.util.module_from_spec(spec) +spec.loader.exec_module(configure_components) spec = importlib.util.spec_from_file_location("ci_package", Path(__file__).with_name("package.py")) package = importlib.util.module_from_spec(spec) spec.loader.exec_module(package) +spec = importlib.util.spec_from_file_location("ci_verify_sdk", Path(__file__).with_name("verify_sdk.py")) +verify_sdk = importlib.util.module_from_spec(spec) +with patch.dict("sys.modules", {"package": package}): + spec.loader.exec_module(verify_sdk) class RunnerTests(unittest.TestCase): @@ -32,10 +41,12 @@ def setUp(self): self.source.mkdir() self.build = self.root / "build" self.commands = [] + self.environments = [] def execute(self, command, source, environment): command = list(map(str, command)) self.commands.append(command) + self.environments.append(dict(environment)) self.assertEqual(source, self.source) if command[0] == "ctest": self.assertEqual(environment["CHERYL_STB_ALLOCATION_TTF"], str(self.source / "assets/fonts/DejaVuSans.ttf")) @@ -44,17 +55,92 @@ def execute(self, command, source, environment): '', encoding="utf-8") - def invoke(self, *arguments): + def invoke(self, *arguments, environment=None): output = io.StringIO() - with patch.object(ci, "run", self.execute), patch.dict(ci.os.environ, {}, clear=True), redirect_stdout(output): + with patch.object(ci, "run", self.execute), \ + patch.dict(ci.os.environ, environment or {}, clear=True), redirect_stdout(output): ci.main(["--source", str(self.source), "--build-dir", str(self.build), "--jobs", "2", *arguments]) return output.getvalue() + def test_platform_matrix(self): + cases = product(("linux", "win32"), ("headless", "modules", "consumer"), ("all", "build", "test"), + ("static", "shared"), ("ON", "OFF"), (False, True), ("Release", "Debug", "RelWithDebInfo")) + for index, (platform, profile, phase, linkage, hwloc, sdk, configuration) in enumerate(cases): + with self.subTest(platform=platform, profile=profile, phase=phase, linkage=linkage, + hwloc=hwloc, sdk=sdk, configuration=configuration), \ + patch.object(ci.sys, "platform", platform): + self.build = self.root / f"build {index}" + self.commands.clear() + self.environments.clear() + self.invoke("--profile", profile, "--phase", phase, "--linkage", linkage, "--hwloc", hwloc, + "--build-type", configuration, *(["--sdk"] if sdk else [])) + configure = [command for command in self.commands if command[:2] == ["cmake", "-S"]] + builds = [command for command in self.commands if command[:2] == ["cmake", "--build"]] + checks = [command for command in self.commands if command[0] == "ctest"] + controls = [command for command in self.commands if "--check" in command] + consumers = [command for command in self.commands if len(command) == 1] + self.assertEqual(len(configure), int(phase != "test")) + self.assertEqual(len(builds), int(phase != "test")) + self.assertEqual(len(controls), int(phase != "test" and profile == "modules")) + self.assertEqual(len(checks), int(phase != "build" and profile != "consumer")) + if configure: + self.assertIn(f"-DCHERYL_BUILD_SHARED_LIBS={'ON' if linkage == 'shared' else 'OFF'}", configure[0]) + self.assertIn(f"-DCHERYL_BUILD_SDK={'ON' if sdk else 'OFF'}", configure[0]) + self.assertIn(f"-DCHERYL_BUILD_TESTS={'OFF' if profile == 'consumer' else 'ON'}", configure[0]) + self.assertIn(f"-DCHERYL_BUILD_WORKER_TOPOLOGY_HWLOC={hwloc}", configure[0]) + self.assertIn(f"-DCMAKE_BUILD_TYPE={configuration}", configure[0]) + self.assertEqual(configure[0][2:8], + [str(self.source), "-B", str(self.build), "-G", "Ninja", f"-DCMAKE_BUILD_TYPE={configuration}"]) + self.assertEqual("cheryl-sdk" in builds[0], sdk) + self.assertEqual("tests-worker-topology-composition" in builds[0], hwloc == "ON" and profile != "consumer") + if checks: + pattern = re.compile(checks[0][checks[0].index("-R") + 1]) + self.assertIsNotNone(pattern.search("consumer-worker-topology")) + self.assertEqual(bool(pattern.search("tests-worker-topology-composition.case")), hwloc == "ON") + self.assertEqual(bool(pattern.search("tests-ui-coexist.case")), profile == "modules") + self.assertIsNone(pattern.search("acceptance-worker-topology-hwloc.native.binding")) + self.assertIsNone(pattern.search("all-engine.case")) + expected = [] if phase == "build" else ["cheryl-consumer", "cheryl-startup-consumer"] + if phase != "build" and profile == "modules": + expected += ["cheryl-native-glfw-consumer", "cheryl-opengl-consumer", "cheryl-ui-tgui-consumer", + "cheryl-ui-rmlui-consumer", "cheryl-audio-miniaudio-consumer", "cheryl-ui-tgui-consumer-native"] + suffix = ".exe" if platform == "win32" else "" + self.assertEqual(consumers, [[str(self.build / (name + suffix))] for name in expected]) + if platform == "win32": + self.assertTrue(all(environment["PATH"].split(ci.os.pathsep)[0] == str(self.build) + for environment in self.environments)) + + def test_prepared_environment(self): + for platform, profile, hwloc in product(("linux", "win32"), ("headless", "modules", "consumer"), ("ON", "OFF")): + with self.subTest(platform=platform, profile=profile, hwloc=hwloc), patch.object(ci.sys, "platform", platform): + self.commands.clear() + self.environments.clear() + installed = self.root / "prepared with spaces" + vcpkg = self.root / "vcpkg with spaces" + arguments = ["--vcpkg-root", str(vcpkg), "--vcpkg-installed-dir", str(installed)] if platform == "linux" else [] + self.invoke("--profile", profile, "--phase", "build", "--hwloc", hwloc, *arguments, environment={ + "CHERYL_CI_VCPKG_ROOT": str(vcpkg), "CHERYL_CI_VCPKG_INSTALLED_DIR": str(installed), + "CMAKE_CXX_COMPILER_LAUNCHER": "ccache", "PATH": "existing search path"}) + configure = self.commands[0] + features = (["hwloc"] if hwloc == "ON" else []) + (["native"] if platform == "win32" and profile == "modules" else []) + self.assertIn(f"-DVCPKG_MANIFEST_FEATURES={';'.join(features)}", configure) + self.assertIn(f"-DVCPKG_INSTALLED_DIR={installed.as_posix()}", configure) + self.assertIn("-DVCPKG_MANIFEST_INSTALL=OFF", configure) + self.assertIn(f"-DCMAKE_PROJECT_INCLUDE={self.source / 'tools/dependencies/prepared.cmake'}", configure) + self.assertIn(f"-DVCPKG_TARGET_TRIPLET={'x64-windows' if platform == 'win32' else 'x64-linux-dynamic'}", configure) + self.assertEqual("-DCMAKE_POLICY_DEFAULT_CMP0141=NEW" in configure, platform == "win32") + embedded = "-DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=$<$:Embedded>" + self.assertEqual(embedded in configure, platform == "win32") + python = f"-DPython_EXECUTABLE:FILEPATH={Path(ci.sys.executable).resolve().as_posix()}" + self.assertEqual(python in configure, platform == "win32" and profile == "modules") + search = self.environments[0]["PATH"].split(ci.os.pathsep) + self.assertEqual(search, [str(self.build), "existing search path"] if platform == "win32" else ["existing search path"]) + def test_headless(self): output = self.invoke("--profile", "headless", "--hwloc", "ON") command = next(command for command in self.commands if command[0] == "ctest") pattern = re.compile(command[command.index("-R") + 1]) - for name in ("tests-engine.text_runs.pages", "acceptance-engine.frame.done", + for name in ("tests-engine.text_runs.pages", "acceptance-engine.frame.done", "acceptance-logging.process_lifetime", "tests-worker-topology-composition.workers.config", "consumer-worker-topology"): self.assertIsNotNone(pattern.search(name), name) for name in ("all-engine.text_runs.pages", "acceptance-worker-topology-hwloc.native.binding", @@ -62,18 +148,22 @@ def test_headless(self): self.assertIsNone(pattern.search(name), name) self.assertIn("Completed 1 checks; 1 skipped.", output) self.assertIn("Not accepted (skipped): fixture", output) + self.assertIn("-DCHERYL_BUILD_SHARED_LIBS=OFF", self.commands[0]) self.assertFalse(any(command[0].endswith("demo") for command in self.commands)) def test_modules(self): self.invoke("--profile", "modules") self.assertIn("-DCHERYL_BUILD_FIRST_GAME=ON", self.commands[0]) + self.assertEqual(self.commands[1][1:], [str(self.source / "tools/input/generate_controls.py"), "--check"]) build = next(command for command in self.commands if command[:2] == ["cmake", "--build"]) self.assertIn("first-game", build) pattern = re.compile(next(command for command in self.commands if command[0] == "ctest")[-1]) for owner in ("native-glfw", "opengl", "ui-tgui", "ui-rmlui", "audio-miniaudio", "ui-coexist"): self.assertIsNotNone(pattern.search(f"tests-{owner}.case"), owner) - consumers = [Path(command[0]).name for command in self.commands if len(command) == 1] - self.assertEqual(len(consumers), 6) + consumers = [Path(command[0]).stem for command in self.commands if len(command) == 1] + self.assertEqual(len(consumers), 8) + self.assertIn("consumer-module-ui-tgui-native", build) + self.assertIn("cheryl-ui-tgui-consumer-native", consumers) self.assertFalse(any(name.startswith("demo") for name in consumers)) def test_consumer(self): @@ -81,7 +171,51 @@ def test_consumer(self): self.assertFalse(any(command[0] == "ctest" for command in self.commands)) self.assertIn("-DCHERYL_BUILD_TESTS=OFF", self.commands[0]) self.assertEqual(self.commands[1][self.commands[1].index("--target") + 1:self.commands[1].index("--parallel")], - ["consumer-cengine"]) + ["consumer-cengine", "consumer-headers-cheryl", "consumer-module-startup"]) + + def test_build_phase(self): + self.invoke("--profile", "headless", "--phase", "build", "--hwloc", "ON") + self.assertEqual(len(self.commands), 2) + self.assertEqual(self.commands[0][:2], ["cmake", "-S"]) + self.assertEqual(self.commands[1][:2], ["cmake", "--build"]) + for target in ("consumer-cengine", "tests-engine", "acceptance-engine", "acceptance-logging", + "tests-worker-topology-composition"): + self.assertIn(target, self.commands[1]) + + def test_test_phase(self): + self.invoke("--profile", "modules", "--phase", "test", "--hwloc", "ON", "--sdk") + self.assertEqual(self.commands[0][0], "ctest") + self.assertFalse(any(command[0] == "cmake" for command in self.commands)) + pattern = re.compile(self.commands[0][-1]) + for name in ("acceptance-engine.runtime_adapter.close_before_update", + "acceptance-logging.process_lifetime", + "tests-worker-topology-composition.workers.config", "tests-ui-coexist.case"): + self.assertIsNotNone(pattern.search(name), name) + self.assertEqual(len(self.commands), 9) + self.assertTrue(all(len(command) == 1 for command in self.commands[1:])) + + def test_consumer_phase(self): + self.invoke("--profile", "consumer", "--phase", "test") + self.assertEqual(len(self.commands), 2) + self.assertEqual(Path(self.commands[0][0]).stem, "cheryl-consumer") + + def test_shared(self): + self.invoke("--profile", "modules", "--linkage", "shared") + self.assertIn("-DCHERYL_BUILD_SHARED_LIBS=ON", self.commands[0]) + self.assertFalse(any(option.startswith(("-DBUILD_SHARED_LIBS=", "-DTGUI_SHARED_LIBS=")) + for option in self.commands[0])) + + def test_sdk_preserves_linkage(self): + self.invoke("--profile", "consumer", "--phase", "build", "--sdk") + self.assertIn("-DCHERYL_BUILD_SHARED_LIBS=OFF", self.commands[0]) + self.assertIn("-DCHERYL_BUILD_SDK=ON", self.commands[0]) + self.assertFalse(any(option.startswith(("-DBUILD_SHARED_LIBS=", "-DTGUI_SHARED_LIBS=", "-DCHERYL_SDK_NATIVE_PREFIX=")) + for option in self.commands[0])) + + def test_windows_shared(self): + with patch.object(ci.sys, "platform", "win32"): + self.invoke("--profile", "consumer", "--phase", "test", "--linkage", "shared") + self.assertIn(str(self.build), self.environments[-1]["PATH"].split(ci.os.pathsep)) def test_windows(self): with patch.object(ci.sys, "platform", "win32"): @@ -89,7 +223,36 @@ def test_windows(self): "--vcpkg-root", str(self.root / "vcpkg")) self.assertIn("-DVCPKG_MANIFEST_FEATURES=hwloc", self.commands[0]) self.assertIn("-DVCPKG_TARGET_TRIPLET=x64-windows", self.commands[0]) - self.assertTrue(self.commands[-1][0].endswith("cheryl-consumer.exe")) + self.assertNotIn("-DVCPKG_MANIFEST_INSTALL=OFF", self.commands[0]) + self.assertTrue(self.commands[-1][0].endswith("cheryl-startup-consumer.exe")) + + def test_windows_preinstalled(self): + installed = self.build / "vcpkg_installed" + with patch.object(ci.sys, "platform", "win32"): + self.invoke("--profile", "modules", "--phase", "build", "--hwloc", "ON", + "--vcpkg-root", str(self.root / "vcpkg"), "--vcpkg-installed-dir", str(installed)) + self.assertIn(f"-DVCPKG_INSTALLED_DIR={installed.as_posix()}", self.commands[0]) + self.assertIn("-DVCPKG_MANIFEST_INSTALL=OFF", self.commands[0]) + self.assertIn("-DVCPKG_MANIFEST_FEATURES=hwloc;native", self.commands[0]) + self.assertEqual(len(self.commands), 3) + self.assertEqual(self.commands[1][1:], [str(self.source / "tools/input/generate_controls.py"), "--check"]) + + def test_preinstalled_requires_root(self): + with redirect_stdout(io.StringIO()), patch("sys.stderr", new_callable=io.StringIO) as errors, \ + self.assertRaises(SystemExit) as raised: + self.invoke("--profile", "headless", "--vcpkg-installed-dir", str(self.build / "vcpkg_installed")) + self.assertEqual(raised.exception.code, 2) + self.assertIn("--vcpkg-installed-dir requires --vcpkg-root", errors.getvalue()) + self.assertEqual(self.commands, []) + + def test_invalid_jobs(self): + for jobs in ("0", "-1"): + with self.subTest(jobs=jobs), patch("sys.stderr", new_callable=io.StringIO) as errors, \ + self.assertRaises(SystemExit) as raised: + self.invoke("--profile", "headless", "--jobs", jobs) + self.assertEqual(raised.exception.code, 2) + self.assertIn("--jobs must be positive", errors.getvalue()) + self.assertEqual(self.commands, []) def test_build_failure(self): calls = [] @@ -103,6 +266,116 @@ def fail_build(command, source, environment): ci.main(["--source", str(self.source), "--build-dir", str(self.build), "--profile", "headless"]) self.assertEqual(len(calls), 2) + def test_phase_failures(self): + for platform, stage in product(("linux", "win32"), ("configure", "controls", "build", "ctest", "consumer")): + with self.subTest(platform=platform, stage=stage), patch.object(ci.sys, "platform", platform): + self.commands.clear() + self.build = self.root / f"{platform} {stage}" + suffix = ".exe" if platform == "win32" else "" + failure = { + "configure": lambda command: command[:2] == ["cmake", "-S"], + "controls": lambda command: "--check" in command, + "build": lambda command: command[:2] == ["cmake", "--build"], + "ctest": lambda command: command[0] == "ctest", + "consumer": lambda command: command == [str(self.build / ("cheryl-consumer" + suffix))], + }[stage] + + def execute(command, source, environment): + if failure(list(map(str, command))): + self.commands.append(list(map(str, command))) + raise subprocess.CalledProcessError(7, command) + self.execute(command, source, environment) + + with patch.object(ci, "run", execute), patch.dict(ci.os.environ, {}, clear=True), \ + redirect_stdout(io.StringIO()), self.assertRaises(subprocess.CalledProcessError) as raised: + ci.main(["--source", str(self.source), "--build-dir", str(self.build), "--profile", "modules"]) + self.assertEqual(raised.exception.returncode, 7) + self.assertTrue(failure(self.commands[-1])) + + def test_command_failure(self): + command = ["cmake", "--build", self.build] + environment = {"PATH": "prepared runtime search"} + failure = subprocess.CalledProcessError(3, command) + with patch.object(ci.subprocess, "run", side_effect=failure) as execute, redirect_stdout(io.StringIO()), \ + self.assertRaises(subprocess.CalledProcessError) as raised: + ci.run(command, self.source, environment) + self.assertIs(raised.exception, failure) + execute.assert_called_once_with(command, cwd=self.source, env=environment, check=True) + + +class SdkPhaseTests(unittest.TestCase): + def setUp(self): + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + self.root = Path(temporary.name).resolve() + self.build = self.root / "consumer" + self.packages = self.root / "packages" + self.packages.mkdir() + standalone = self.root / ".github/ci/sdk-consumer/standalone" + standalone.mkdir(parents=True) + (standalone / "CMakeLists.txt").write_text("project(independent)", encoding="utf-8") + self.metadata = {"system": "Windows", "configuration": "RelWithDebInfo", "linkage": "shared"} + archive = self.packages / "sdk.zip" + with zipfile.ZipFile(archive, "w") as output: + output.writestr("sdk/SDK-MANIFEST.json", json.dumps(self.metadata)) + digest = hashlib.sha256(archive.read_bytes()).hexdigest() + (self.packages / "SHA256SUMS").write_text(f"{digest} {archive.name}", encoding="utf-8") + + def invoke(self, phase=None): + arguments = ["verify_sdk.py", "--source", str(self.root), "--packages", str(self.packages), + "--build-dir", str(self.build), "--producer-build", str(self.root / "producer"), "--jobs", "2"] + if phase: + arguments += ["--phase", phase] + with patch("sys.argv", arguments), patch.object(verify_sdk, "check_contents"), \ + patch.object(verify_sdk, "check_runtime_imports"), \ + patch.object(verify_sdk.subprocess, "run") as command, redirect_stdout(io.StringIO()): + verify_sdk.main() + return command.call_args_list + + def test_split(self): + commands = self.invoke("build") + self.assertEqual([call.args[0][:2] for call in commands], + [["cmake", "-S"], ["cmake", "-S"], ["cmake", "--build"], ["cmake", "--build"]]) + standalone = commands[1].args[0] + self.assertIn("-DCMAKE_DISABLE_FIND_PACKAGE_ICU=TRUE", standalone) + self.assertNotIn(f"-DCHERYL_REPOSITORY_ROOT={self.root.as_posix()}", standalone) + self.assertTrue((self.root / "consumer-standalone-source/CMakeLists.txt").is_file()) + # Testing uses the already extracted SDK and never opens the package again. + (self.packages / "sdk.zip").unlink() + (self.packages / "SHA256SUMS").unlink() + commands = self.invoke("test") + self.assertEqual(len(commands), 2) + self.assertEqual(commands[0].args[0][0], "ctest") + sdk = self.build.parent / "consumer-relocated-sdk/sdk" + search = commands[0].kwargs["env"]["PATH"].split(verify_sdk.os.pathsep) + self.assertEqual(search[0], str(sdk / "bin")) + + def test_combined(self): + commands = self.invoke() + self.assertEqual([call.args[0][0] for call in commands], ["cmake", "cmake", "cmake", "cmake", "ctest", "ctest"]) + + def test_missing_build(self): + with self.assertRaises(FileNotFoundError): + self.invoke("test") + + +class ConfigureMatrixTests(unittest.TestCase): + def test_stops_and_preserves_failure(self): + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + arguments = ["--build-root", str(root), "--vcpkg-root", str(root / "tools"), + "--installed-dir", str(root / "installed"), "--case", "native", "--case", "audio"] + failure = subprocess.CompletedProcess([], 1, stdout="configure output\n", stderr="failure detail\n") + with patch.object(configure_components.subprocess, "run", return_value=failure) as command, \ + redirect_stdout(io.StringIO()), self.assertRaises(subprocess.CalledProcessError): + configure_components.main(arguments) + command.assert_called_once() + self.assertEqual((root / "sdk-config-native/configure.log").read_text(encoding="utf-8"), + "configure output\nfailure detail\n") + self.assertFalse((root / "sdk-config-audio").exists()) + with self.assertRaisesRegex(ValueError, "preserving existing files"): + configure_components.main(arguments) + class PackageTests(unittest.TestCase): def setUp(self): @@ -110,11 +383,14 @@ def setUp(self): self.addCleanup(self.temporary.cleanup) self.root = Path(self.temporary.name) self.stage = self.root / "sdk" - for name in ("include/cheryl/core.h", "lib/libcheryl-engine.a", "lib/cmake/Cheryl/CherylConfig.cmake", + self.metadata = {"version": "4.0.0", "component_versions": {"engine": "4.0.0"}, "linkage": "static", + "engine_library": "lib/libcheryl-engine-4.0.0.a", "engine_import_library": "", "system": "Linux"} + for name in ("include/cheryl/core.h", self.metadata["engine_library"], "lib/cmake/Cheryl/CherylConfig.cmake", "share/licenses/cheryl/LICENSE", "share/licenses/stb/LICENSE"): path = self.stage / name path.parent.mkdir(parents=True, exist_ok=True) path.write_bytes(name.encode()) + (self.stage / "SDK-MANIFEST.json").write_text(json.dumps(self.metadata), encoding="utf-8") def test_archive(self): package.check_contents(self.stage) @@ -141,23 +417,315 @@ def test_contents(self): with self.assertRaisesRegex(ValueError, "application or implementation"): package.check_contents(self.stage) path.unlink() - (self.stage / "lib/libcheryl-engine.a").unlink() + (self.stage / self.metadata["engine_library"]).unlink() + (self.stage / "lib/libcheryl-engine-other.a").write_bytes(b"unrelated library") with self.assertRaisesRegex(ValueError, "compiled Engine"): package.check_contents(self.stage) - def test_native_files(self): - prefix = self.root / "native" - kept = ("include/dependency.h", "lib/dependency.lib", "lib/cmake/dependency-release.cmake", - "bin/dependency.dll", "share/dependency/copyright", "share/dependency/config.cmake") - omitted = ("debug/lib/dependency.lib", "lib/cmake/dependency-debug.cmake", "tools/demo.exe", "share/dependency/source.c") - for name in (*kept, *omitted): - path = prefix / name - path.parent.mkdir(parents=True, exist_ok=True) - path.write_bytes(name.encode()) - package.copy_native_dependencies(prefix, self.stage) - actual = {path.relative_to(self.stage / "dependencies").as_posix() - for path in (self.stage / "dependencies").rglob("*") if path.is_file()} - self.assertEqual(set(kept), actual) + def test_shared_artifacts(self): + for system, library, import_library in ( + ("Linux", "lib/libcheryl-engine.so.4.0.0", ""), + ("Windows", "bin/cheryl-engine-4.0.0.dll", "lib/cheryl-engine-4.0.0.lib"), + ): + with self.subTest(system=system): + metadata = {**self.metadata, "system": system, "linkage": "shared", + "engine_library": library, "engine_import_library": import_library} + for name in (library, import_library): + if name: + path = self.stage / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(b"compiled artifact") + package.check_contents(self.stage, metadata) + (self.stage / (import_library or library)).unlink() + with self.assertRaisesRegex(ValueError, "compiled Engine"): + package.check_contents(self.stage, metadata) + + def test_artifact_paths(self): + for name in ("../outside.a", "/outside.a", "C:/outside.lib", r"lib\engine.lib"): + with self.subTest(name=name), self.assertRaisesRegex(ValueError, "relative compiled Engine"): + package.check_contents(self.stage, {**self.metadata, "engine_library": name}) + + def make_link(self, path, target): + try: + path.symlink_to(target) + except (NotImplementedError, OSError) as error: + self.skipTest(f"Symbolic-link fixture unavailable: {error}") + + def test_shared_links(self): + library = self.stage / "lib/libcheryl-engine.so.4.0.0" + library.write_bytes(b"shared Engine") + soname = library.with_name("libcheryl-engine.so.4") + linker = library.with_name("libcheryl-engine.so") + self.make_link(soname, library.name) + self.make_link(linker, soname.name) + metadata = {**self.metadata, "linkage": "shared", "engine_library": "lib/" + library.name} + (self.stage / "SDK-MANIFEST.json").write_text(json.dumps(metadata), encoding="utf-8") + package.check_contents(self.stage, metadata) + destination = self.root / "shared.tar.gz" + package.archive_sdk(self.stage, destination) + with tarfile.open(destination) as archive: + member = archive.getmember("sdk/lib/" + linker.name) + self.assertTrue(member.issym()) + self.assertEqual(member.linkname, soname.name) + archive.extractall(self.root / "relocated", filter="data") + relocated = self.root / "relocated/sdk/lib" / linker.name + self.assertTrue(relocated.is_symlink()) + self.assertEqual(relocated.read_bytes(), b"shared Engine") + with self.assertRaisesRegex(ValueError, "supported by the archive"): + package.archive_sdk(self.stage, self.root / "shared.zip") + + def test_unsafe_links(self): + outside = self.root / "outside" + outside.write_bytes(b"outside SDK") + link = self.stage / "lib/unsafe" + for target, message in ((self.stage / self.metadata["engine_library"], "relative"), + (Path("../../outside"), "escapes"), (Path("missing"), "broken")): + with self.subTest(target=target): + self.make_link(link, target) + with self.assertRaisesRegex(ValueError, message): + package.check_contents(self.stage) + link.unlink() + + def test_shared_runtime_payload(self): + metadata = {**self.metadata, "shared_runtime_dependencies": [ + {"dependency": "native", "consumer": "owner", "artifact": "libnative.so.2", "deployment": "sdk"}]} + with self.assertRaisesRegex(ValueError, "missing a declared shared runtime"): + package.check_contents(self.stage, metadata) + (self.stage / "lib/libnative.so.2").write_bytes(b"shared native library") + package.check_contents(self.stage, metadata) + + def test_shared_privacy(self): + metadata = {**self.metadata, "linkage": "shared"} + for name in ("include/spdlog/spdlog.h", "include/GLFW/glfw3.h", "include/miniaudio.h", "dependencies/lib/native.a"): + with self.subTest(name=name): + path = self.stage / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(b"private") + with self.assertRaisesRegex(ValueError, "private dependenc"): + package.check_contents(self.stage, metadata) + path.unlink() + while path.parent != self.stage and not any(path.parent.iterdir()): + path = path.parent + path.rmdir() + exports = self.stage / "lib/cmake/Cheryl/CherylTargets.cmake" + exports.write_text('INTERFACE_LINK_LIBRARIES "ICU::uc"', encoding="utf-8") + with self.assertRaisesRegex(ValueError, "exports a private dependency"): + package.check_contents(self.stage, metadata) + + def test_shared_runtime_hints(self): + metadata = {**self.metadata, "linkage": "shared", "component_versions": {"engine": "4.0.0", "opengl": "2.0.0"}, + "shared_runtime_dependencies": [ + {"dependency": "harfbuzz::harfbuzz", "deployment": "system"}, + {"dependency": "glfw", "sdk_target": "Cheryl::Dependency_glfw", "deployment": "system"}]} + exports = self.stage / "lib/cmake/Cheryl/CherylTargets-release.cmake" + exports.write_text(' IMPORTED_LINK_DEPENDENT_LIBRARIES_RELEASE "harfbuzz::harfbuzz;Cheryl::Dependency_glfw;Cheryl::OpenGL"\n', + encoding="utf-8") + package.check_contents(self.stage, metadata) + for target in ("ICU::uc", "spdlog::spdlog", "undeclared_runtime", "Cheryl::Workers"): + with self.subTest(target=target): + exports.write_text(f' IMPORTED_LINK_DEPENDENT_LIBRARIES_RELEASE "{target}"\n', encoding="utf-8") + with self.assertRaisesRegex(ValueError, "undeclared dependent runtime"): + package.check_contents(self.stage, metadata) + exports.write_text(' INTERFACE_LINK_LIBRARIES "harfbuzz::harfbuzz"\n', encoding="utf-8") + with self.assertRaisesRegex(ValueError, "exports a private dependency"): + package.check_contents(self.stage, metadata) + + def test_owner_link_evidence(self): + build = self.root / "producer" + maps = build / "link-maps" + maps.mkdir(parents=True) + archive = build / "libprivate.a" + binary = build / "libowner.so" + archive.write_bytes(b"archive") + binary.write_bytes(b"owner") + inventory = {"schema": 1, "dependencies": [{"dependency": "private", "target_owner": "owner", + "archive": str(archive), "binary": str(binary)}]} + (build / "cheryl-dependency-ownership.json").write_text(json.dumps(inventory), encoding="utf-8") + metadata = {**self.metadata, "linkage": "shared", "dependency_ownership": [{"dependency": "private", "owner": "owner"}]} + (maps / "owner.map").write_text(str(archive) + "(member.o)", encoding="utf-8") + evidence = package.ownership_evidence(build, metadata) + self.assertEqual(evidence[0]["archive"], archive.name) + self.assertNotIn(str(build), json.dumps(evidence)) + (maps / "other.map").write_text(str(archive) + "(member.o)", encoding="utf-8") + with self.assertRaisesRegex(ValueError, "two owners"): + package.ownership_evidence(build, metadata) + + def test_runtime_imports(self): + library = self.stage / "lib/libcheryl-owner.so" + library.write_bytes(b"binary") + dependency = self.stage / "lib/libcheryl-module-native-glfw.so.2" + dependency.write_bytes(b"component") + metadata = {**self.metadata, "linkage": "shared"} + with patch.object(package, "runtime_imports", side_effect=lambda path, system: + [dependency.name] if path == library else []): + evidence = package.check_runtime_imports(self.stage, metadata) + self.assertEqual(next(entry for entry in evidence if entry["binary"] == library.name)["imports"], [dependency.name]) + for dependency in ("libfreetype.so.6", "libhidapi-hidraw.so.0"): + with self.subTest(dependency=dependency), patch.object(package, "runtime_imports", return_value=[dependency]): + with self.assertRaisesRegex(ValueError, "missing a required shared runtime"): + package.check_runtime_imports(self.stage, metadata) + + def test_declared_shared_imports(self): + library = self.stage / "lib/libcheryl-owner.so" + library.write_bytes(b"binary") + (self.stage / "lib/libfreetype.so.6").write_bytes(b"native runtime") + metadata = {**self.metadata, "linkage": "shared", "shared_runtime_dependencies": [ + {"dependency": "Freetype::Freetype", "consumer": "owner", "runtime_name": "libfreetype.so.6", "deployment": "sdk"}]} + with patch.object(package, "runtime_imports", side_effect=lambda path, system: + ["libfreetype.so.6"] if path == library else []): + self.assertEqual(package.check_runtime_imports(self.stage, metadata)[0]["imports"], ["libfreetype.so.6"]) + for name, message in (("libfreetype.so.7", "missing a required shared runtime"), + ("libspdlog.so.1", "embedded private dependency")): + with self.subTest(name=name), patch.object(package, "runtime_imports", return_value=[name]): + with self.assertRaisesRegex(ValueError, message): + package.check_runtime_imports(self.stage, metadata) + + def test_static_sdk_checks_shared_imports(self): + library = self.stage / "lib/libtgui.so.1" + library.write_bytes(b"binary") + metadata = {**self.metadata, "shared_runtime_dependencies": [ + {"dependency": "tgui", "consumer": "adapter", "runtime_name": "libtgui.so.1", "deployment": "sdk"}]} + with patch.object(package.subprocess, "check_output", return_value='(NEEDED) Shared library: [libspdlog.so.1]'): + with self.assertRaisesRegex(ValueError, "embedded private dependency"): + package.check_runtime_imports(self.stage, metadata) + + def test_native_dll_closure(self): + directory = self.stage / "bin" + directory.mkdir() + for name in ("freetype.dll", "png.dll", "obsolete-icu78.dll"): + (directory / name).write_bytes(b"fixture") + metadata = {**self.metadata, "system": "Windows", "shared_runtime_dependencies": [ + {"dependency": "freetype", "consumer": "engine", "artifact": "freetype.dll", + "runtime_name": "freetype.dll", "deployment": "sdk"}]} + with patch.object(package, "runtime_imports", side_effect=lambda library, system: + ["png.dll", "KERNEL32.dll"] if library.name == "freetype.dll" else ["KERNEL32.dll"]): + package.shared_runtime_closure(self.stage, metadata) + records = metadata["shared_runtime_dependencies"] + self.assertEqual([record["artifact"] for record in records], ["freetype.dll", "png.dll"]) + self.assertEqual(records[1]["consumer_artifact"], "freetype.dll") + with patch.object(package, "runtime_imports", return_value=[]), \ + self.assertRaisesRegex(ValueError, "undeclared shared runtime: obsolete-icu78.dll"): + package.check_runtime_imports(self.stage, metadata) + + def test_linux_native_closure(self): + imports = { + "libcheryl-engine.so.4": ["libfreetype.so.6", "libdw.so.1", "libstdc++.so.6", "libGL.so.1", "libudev.so.1"], + "libfreetype.so.6": ["libbrotlidec.so.1", "libc.so.6"], + "libbrotlidec.so.1": ["libbrotlicommon.so.1", "libc.so.6"], + "libbrotlicommon.so.1": ["libc.so.6"], + "libdw.so.1": ["libelf.so.1", "libz.so.1", "libc.so.6"], + "libelf.so.1": ["libz.so.1", "libc.so.6"], + "libz.so.1": ["libc.so.6"], + } + for name in imports: + (self.stage / "lib" / name).write_bytes(b"runtime fixture") + metadata = {**self.metadata, "shared_runtime_dependencies": [ + {"dependency": "freetype", "consumer": "engine", "artifact": "libfreetype.so.6", + "runtime_name": "libfreetype.so.6", "deployment": "sdk"}]} + with patch.object(package, "runtime_imports", side_effect=lambda path, system: imports[path.name]): + package.shared_runtime_closure(self.stage, metadata) + evidence = package.check_runtime_imports(self.stage, metadata) + records = metadata["shared_runtime_dependencies"] + self.assertEqual({entry["artifact"] for entry in records}, set(imports) - {"libcheryl-engine.so.4"}) + self.assertEqual(len(evidence), len(imports)) + self.assertEqual(next(entry for entry in records if entry["artifact"] == "libelf.so.1")["consumer_artifact"], "libdw.so.1") + + def test_missing_native_closure(self): + library = self.stage / "lib/libcheryl-engine.so.4" + library.write_bytes(b"Engine") + (self.stage / "lib/libdw.so.1").write_bytes(b"diagnostics") + metadata = {**self.metadata, "shared_runtime_dependencies": []} + with patch.object(package, "runtime_imports", side_effect=lambda path, system: + ["libdw.so.1"] if path == library else ["libelf.so.1"]): + with self.assertRaisesRegex(ValueError, "missing a required shared runtime: libdw.so.1: libelf.so.1"): + package.shared_runtime_closure(self.stage, metadata) + with patch.object(package, "runtime_imports", return_value=["libpng16.so.16"]): + with self.assertRaisesRegex(ValueError, "missing a required shared runtime"): + package.check_runtime_imports(self.stage, metadata) + + def test_runtime_baseline(self): + for name in ("ld-linux-x86-64.so.2", "libc.so.6", "libmvec.so.1", "libstdc++.so.6", "libnss_dns.so.2", "libGL.so.1", + "libX11.so.6", "libxcb-xinput.so.0", "libwayland-client.so.0", "libxkbcommon.so.0", + "libdecor-0.so.0", "libdrm_amdgpu.so.1", "libgbm.so.1", "libasound.so.2", "libpulsecommon-17.0.so", + "libjack.so.0", "libudev.so.1"): + with self.subTest(name=name): + self.assertTrue(package.system_runtime(name, "Linux")) + for name in ("libglib-2.0.so.0", "libgobject-2.0.so.0", "libgraphite2.so.3", "libpng16.so.16", + "libbrotlidec.so.1", "libdw.so.1", "libelf.so.1", "libicuuc.so.78", "libffi.so.8"): + with self.subTest(name=name): + self.assertFalse(package.system_runtime(name, "Linux")) + self.assertTrue(package.system_runtime("icu.dll", "Windows")) + self.assertTrue(package.system_runtime("KERNEL32.dll", "Windows")) + self.assertFalse(package.system_runtime("icuuc78.dll", "Windows")) + + def test_external_native_runtime(self): + metadata = {**self.metadata, "shared_runtime_dependencies": [ + {"dependency": "freetype", "artifact": "libfreetype.so.6", "deployment": "system"}]} + with self.assertRaisesRegex(ValueError, "leaves a non-system shared runtime external"): + package.check_contents(self.stage, metadata) + + def test_sdk_names(self): + for system in ("Linux", "Windows"): + for linkage in ("static", "shared"): + with self.subTest(system=system, linkage=linkage): + expected = f"cheryl-SDK-v4.0.0-{system.lower()}" + ("-static" if linkage == "static" else "") + self.assertEqual(package.sdk_name({**self.metadata, "system": system, "linkage": linkage}), expected) + + def test_shared_runtime_evidence(self): + build = self.root / "producer" + build.mkdir() + library = build / "libnative.so" + library.write_bytes(b"shared") + inventory = {"schema": 1, "dependencies": [{"dependency": "native", "consumer": "owner", + "library": str(library), "system": False, "owned": True}]} + (build / "cheryl-shared-dependencies.json").write_text(json.dumps(inventory), encoding="utf-8") + metadata = {**self.metadata, "shared_runtime_dependencies": [{"dependency": "native", "consumer": "owner", + "artifact": "libnative.so", "deployment": "sdk"}]} + with patch.object(package.subprocess, "check_output", return_value='(SONAME) Library soname: [libnative.so.2]'): + evidence = package.shared_runtime_evidence(build, metadata) + self.assertEqual(evidence[0]["runtime_name"], "libnative.so.2") + self.assertNotIn(str(build), json.dumps(evidence)) + metadata["shared_runtime_dependencies"][0]["consumer"] = "another" + with self.assertRaisesRegex(ValueError, "does not match"): + package.shared_runtime_evidence(build, metadata) + + def test_msvc_owner_map(self): + build = self.root / "producer" + build.mkdir() + archive = build / "private.lib" + binary = build / "owner.dll" + archive.write_bytes(b"archive") + binary.write_bytes(b"owner") + inventory = {"schema": 1, "dependencies": [{"dependency": "private", "target_owner": "owner", + "archive": str(archive), "binary": str(binary)}]} + (build / "cheryl-dependency-ownership.json").write_text(json.dumps(inventory), encoding="utf-8") + (build / "owner.map").write_text("private:member.obj", encoding="utf-8") + metadata = {**self.metadata, "system": "Windows", "linkage": "shared", + "dependency_ownership": [{"dependency": "private", "owner": "owner"}]} + evidence = package.ownership_evidence(build, metadata) + self.assertEqual(evidence[0]["evidence"], "owner link map archive members") + (build / "other.map").write_text("private:member.obj", encoding="utf-8") + with self.assertRaisesRegex(ValueError, "two owners"): + package.ownership_evidence(build, metadata) + + def test_unused_msvc_archive(self): + build = self.root / "producer" + build.mkdir() + archive, binary = build / "unused.lib", build / "owner.dll" + archive.write_bytes(b"archive") + binary.write_bytes(b"owner") + inventory = {"schema": 1, "dependencies": [{"dependency": "unused", "target_owner": "owner", + "archive": str(archive), "binary": str(binary)}]} + (build / "cheryl-dependency-ownership.json").write_text(json.dumps(inventory), encoding="utf-8") + metadata = {**self.metadata, "system": "Windows", "linkage": "shared", + "dependency_ownership": [{"dependency": "unused", "owner": "owner"}]} + ninja = build / "build.ninja" + ninja.write_text('build owner.dll owner.lib: LINKER objects\n LINK_LIBRARIES = "C:/native/unused.lib"\n', encoding="utf-8") + evidence = package.ownership_evidence(build, metadata) + self.assertEqual(evidence[0]["evidence"], "owner Ninja link archive input; no members required") + ninja.write_text('build another.dll: LINKER objects\n LINK_LIBRARIES = "C:/native/unused.lib"\n', encoding="utf-8") + with self.assertRaisesRegex(ValueError, "no dependency archive input"): + package.ownership_evidence(build, metadata) def test_manifest(self): source = self.root / "source" @@ -167,7 +735,7 @@ def test_manifest(self): build.mkdir() identity = {"revision": "a" * 40, "submodules": {"extern/library": "b" * 40}} (source / "SOURCE-MANIFEST.json").write_text(json.dumps(identity), encoding="utf-8") - (build / "cheryl-sdk.json").write_text(json.dumps({"version": "4.0.0", "system": "Linux", + (build / "cheryl-sdk.json").write_text(json.dumps({**self.metadata, "architecture": "x86_64", "compiler": "GNU", "compiler_version": "14.2.0", "configuration": "Release"}), encoding="utf-8") @@ -180,9 +748,11 @@ def install(command, check): patch.object(package.platform, "freedesktop_os_release", return_value={"ID": "ubuntu", "VERSION_ID": "24.04"}), \ redirect_stdout(io.StringIO()): archive = package.bundle(source, build, output) - self.assertIn("4.0.0-sdk-linux-ubuntu24.04-x64-gnu14.2.0-release-aaaaaaaaaaaa", archive.name) + self.assertEqual("cheryl-SDK-v4.0.0-linux-static.tar.gz", archive.name) manifest = json.loads((output / "SDK-MANIFEST.json").read_text(encoding="utf-8")) self.assertEqual(identity["submodules"], manifest["submodules"]) + self.assertEqual(manifest["component_versions"], {"engine": "4.0.0"}) + self.assertEqual(manifest["linkage"], "static") with tarfile.open(archive) as contents: names = contents.getnames() self.assertFalse(any(name.endswith((".cpp", ".c", ".exe")) for name in names)) diff --git a/.github/ci/test_compiler_cache.py b/.github/ci/test_compiler_cache.py new file mode 100644 index 00000000..f4ea675d --- /dev/null +++ b/.github/ci/test_compiler_cache.py @@ -0,0 +1,675 @@ +"""Check compiler cache layers, worker artifacts and family publication.""" + +import importlib.util +import io +import os +from pathlib import Path, PurePosixPath, PureWindowsPath +import tarfile +import tempfile +import unittest +from unittest.mock import patch + + +spec = importlib.util.spec_from_file_location( + "compiler_cache", Path(__file__).with_name("compiler_cache.py") +) +compiler = importlib.util.module_from_spec(spec) +spec.loader.exec_module(compiler) + +PREFIX = "ccache-v3-Linux-X64-linux-gcc14" +ENTRY_KEY = "ab/" + "c" * 38 +OTHER_KEY = "ab/" + "d" * 38 +THIRD_KEY = "de/" + "a" * 38 +MANIFEST_KEY = "ef/" + "1" * 38 +# Native paths emitted by ccache 4.9.1 during the isolated integration probe. +LEGACY_ENTRY_KEY = "b2/896vhhiqfc3do1apbsusjkkq4kaku90" +LEGACY_MANIFEST_KEY = "78/11h1krhvauruo5qojg5tqov17loqsk2" +# The same 20-byte result digest encoded by ccache 4.14's file backend. +MODERN_EQUIVALENT_KEY = "b2/8937e31969ec1b7015657cf7274a6894553c90" +RESULT_HEADER = b"\xcc\xac\x01\x00" +MANIFEST_HEADER = b"\xcc\xac\x01\x01" +ENVIRONMENT = { + "GITHUB_EVENT_NAME": "push", + "GITHUB_REF": "refs/heads/main", + "GITHUB_RUN_ID": "12345", + "GITHUB_RUN_ATTEMPT": "2", + "CHERYL_CCACHE_PREFIX": PREFIX, +} + + +def pull_environment(number=3): + return {**ENVIRONMENT, "GITHUB_EVENT_NAME": "pull_request", "GITHUB_REF": f"refs/pull/{number}/merge"} + + +def pull_event(base="develop", number=3): + return {"number": number, "pull_request": {"number": number, "base": {"ref": base}}} + + +def write(directory, name, contents, *, mtime=None): + path = directory / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(contents) + if mtime is not None: + os.utime(path, (mtime, mtime)) + return path + + +def snapshot(directory): + return {name: (path.read_bytes(), path.stat().st_mtime_ns) + for name, path in compiler._files(directory).items()} + + +def archive(path, entries): + """Write (name, bytes, mtime) entries or explicit TarInfo fixtures.""" + path.parent.mkdir(parents=True, exist_ok=True) + with tarfile.open(path, "w", format=tarfile.PAX_FORMAT) as output: + for entry in entries: + if isinstance(entry, tarfile.TarInfo): + output.addfile(entry) + continue + name, contents, mtime = entry + member = tarfile.TarInfo(name) + member.size = len(contents) + member.mtime = mtime + output.addfile(member, io.BytesIO(contents)) + return path + + +class MetadataTests(unittest.TestCase): + def setUp(self): + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + self.root = Path(temporary.name).resolve() + + def test_roles(self): + for ref, role, restore_main in ( + ("refs/heads/main", "main", False), + ("refs/heads/develop", "develop", True), + ("refs/heads/feature", "branch", True), + ("refs/tags/v4.0.0", "branch", True), + ): + with self.subTest(ref=ref): + result = compiler.select_role({**ENVIRONMENT, "GITHUB_REF": ref}, {}) + self.assertEqual(result["role"], role) + self.assertEqual(result["restore-main"], restore_main) + self.assertFalse(result["restore-develop"]) + + def test_pull_base(self): + for base, restore_develop in (("main", False), ("develop", True)): + with self.subTest(base=base): + result = compiler.select_role(pull_environment(), pull_event(base)) + self.assertEqual(result["role"], "pr") + self.assertTrue(result["restore-main"]) + self.assertEqual(result["restore-develop"], restore_develop) + + def test_pull_mismatch(self): + with self.assertRaises(ValueError): + compiler.select_role(pull_environment(3), pull_event(number=30)) + + def test_paths(self): + main = compiler.metadata(PREFIX, environment=ENVIRONMENT, event={}, root=self.root) + develop = compiler.metadata(PREFIX, environment={**ENVIRONMENT, "GITHUB_REF": "refs/heads/develop"}, + event={}, root=self.root) + pull = compiler.metadata(PREFIX, environment=pull_environment(), event=pull_event(), root=self.root) + self.assertEqual(main["current-path"], pull["main-path"]) + self.assertEqual(develop["current-path"], pull["develop-path"]) + self.assertEqual(Path(main["current-path"]), self.root / "build/cache/compiler/main") + self.assertEqual(Path(develop["current-path"]), self.root / "build/cache/compiler/develop") + self.assertEqual(Path(pull["current-path"]), self.root / "build/cache/compiler/pr") + self.assertEqual(Path(pull["delta-path"]), self.root / "build/cache/compiler/delta") + self.assertEqual(Path(pull["delta-archive"]), self.root / "build/cache/compiler-artifacts/delta.tar") + + def test_keys(self): + result = compiler.metadata(PREFIX, environment=pull_environment(), event=pull_event(), root=self.root) + self.assertEqual(result["current-key"], f"{PREFIX}-pr-12345-2") + self.assertTrue(result["current-key"].startswith(result["current-prefix"])) + self.assertNotEqual(result["current-prefix"], result["main-prefix"]) + self.assertNotEqual(result["current-prefix"], result["develop-prefix"]) + self.assertEqual(result["restore-main"], "true") + self.assertEqual(result["restore-develop"], "true") + + def test_prefix(self): + for prefix in ("ccache-v1-profile", "ccache-v2-ubuntu-24.04-X64-linux-gcc-hwloc-Release", + "ccache-v3-../escape", "ccache-v3-profile|read-only", "ccache-v3-profile with spaces", + "ccache-v3-Windows-X64-linux-gcc14", "ccache-v3-Linux-X64-windows-msvc"): + with self.subTest(prefix=prefix), self.assertRaises(ValueError): + compiler.metadata(prefix, environment=ENVIRONMENT, event={}, root=self.root) + + def test_families(self): + for prefix in (PREFIX, "ccache-v3-Linux-X64-linux-clang18", "ccache-v3-Windows-X64-windows-msvc"): + with self.subTest(prefix=prefix): + result = compiler.metadata(prefix, environment=ENVIRONMENT, event={}, root=self.root) + self.assertEqual(result["current-key"], f"{prefix}-main-12345-2") + + def test_run_identity(self): + for name, value in (("GITHUB_RUN_ID", "0"), ("GITHUB_RUN_ID", "../escape"), + ("GITHUB_RUN_ATTEMPT", "0"), ("GITHUB_RUN_ATTEMPT", "retry")): + with self.subTest(name=name, value=value), self.assertRaises(ValueError): + compiler.metadata(PREFIX, environment={**ENVIRONMENT, name: value}, event={}, root=self.root) + + +class StorageTests(unittest.TestCase): + def setUp(self): + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + self.root = Path(temporary.name).resolve() + self.current = self.root / "current" + self.parent = self.root / "parent" + self.current.mkdir() + self.parent.mkdir() + + def test_file_urls(self): + self.assertEqual(compiler.file_url(PurePosixPath("/tmp/cache layer#1")), + "file:///tmp/cache%20layer%231") + self.assertEqual(compiler.file_url(PureWindowsPath(r"D:\build files\cache#1")), + "file:///D:/build%20files/cache%231") + + def test_storage_order(self): + main = self.root / "main" + result = compiler.storage_configuration(self.current, [self.parent, main]) + self.assertEqual(result, " ".join((compiler.file_url(self.current) + "|update-mtime=true", + compiler.file_url(self.parent) + "|read-only", + compiler.file_url(main) + "|read-only"))) + + def test_fingerprint(self): + path = write(self.current, "entries/result", b"compiled result") + initial = compiler.fingerprint(self.current) + os.utime(path, (1_600_000_000, 1_600_000_000)) + self.assertEqual(compiler.fingerprint(self.current), initial) + path.write_bytes(b"modified result") + os.utime(path, (1_600_000_000, 1_600_000_000)) + self.assertNotEqual(compiler.fingerprint(self.current), initial) + + def test_entry_names(self): + path = write(self.current, "entries/result", b"compiled result") + initial = compiler.fingerprint(self.current) + path.rename(path.with_name("renamed")) + self.assertNotEqual(compiler.fingerprint(self.current), initial) + + def test_deduplicate(self): + duplicate = write(self.current, "entries/result", b"compiled result") + unique = write(self.current, "entries/new", b"new result") + parent = write(self.parent, "entries/result", b"compiled result") + self.assertEqual(compiler.deduplicate(self.current, [self.parent]), 1) + self.assertFalse(duplicate.exists()) + self.assertEqual(unique.read_bytes(), b"new result") + self.assertEqual(parent.read_bytes(), b"compiled result") + + def test_changed_manifest(self): + current = write(self.current, "entries/manifest", b"new refs") + parent = write(self.parent, "entries/manifest", b"old refs") + self.assertEqual(compiler.deduplicate(self.current, [self.parent]), 0) + self.assertEqual(current.read_bytes(), b"new refs") + self.assertEqual(parent.read_bytes(), b"old refs") + + def test_result_identity(self): + for key in (ENTRY_KEY, LEGACY_ENTRY_KEY): + with self.subTest(key=key): + current = write(self.current, key, RESULT_HEADER + b"current serialization metadata") + parent = write(self.parent, key, RESULT_HEADER + b"parent metadata") + self.assertEqual(compiler.deduplicate(self.current, [self.parent]), 1) + self.assertFalse(current.exists()) + self.assertEqual(parent.read_bytes(), RESULT_HEADER + b"parent metadata") + + def test_unknown_format(self): + for index, (name, current_bytes, parent_bytes) in enumerate(( + (ENTRY_KEY, b"\xcc\xac\x01", RESULT_HEADER + b"parent"), + (ENTRY_KEY, b"\xcc\xac\x02\x00current", b"\xcc\xac\x02\x00parent"), + (ENTRY_KEY, b"\xcc\xac\x01\x02current", b"\xcc\xac\x01\x02parent"), + (ENTRY_KEY, RESULT_HEADER + b"current", b"unknown parent"), + ("entries/result", RESULT_HEADER + b"current", RESULT_HEADER + b"parent"), + )): + with self.subTest(name=name, header=current_bytes[:4]): + current = self.current / str(index) + parent = self.parent / str(index) + entry = write(current, name, current_bytes) + write(parent, name, parent_bytes) + self.assertEqual(compiler.deduplicate(current, [parent]), 0) + self.assertEqual(entry.read_bytes(), current_bytes) + + def test_manifest_shadow(self): + main = self.root / "main" + selected = MANIFEST_HEADER + b"main refs" + entry = write(self.current, ENTRY_KEY, selected) + develop = write(self.parent, ENTRY_KEY, MANIFEST_HEADER + b"other refs") + baseline = write(main, ENTRY_KEY, selected) + self.assertEqual(compiler.deduplicate(self.current, [self.parent, main]), 0) + self.assertEqual(entry.read_bytes(), selected) + self.assertEqual(develop.read_bytes(), MANIFEST_HEADER + b"other refs") + self.assertEqual(baseline.read_bytes(), selected) + + def test_parent_miss(self): + main = self.root / "main" + entry = write(self.current, ENTRY_KEY, MANIFEST_HEADER + b"main refs") + write(main, ENTRY_KEY, MANIFEST_HEADER + b"main refs") + self.assertEqual(compiler.deduplicate(self.current, [self.parent, main]), 1) + self.assertFalse(entry.exists()) + + def test_other_path(self): + path = write(self.current, "entries/result", b"same bytes") + write(self.parent, "elsewhere/result", b"same bytes") + self.assertEqual(compiler.deduplicate(self.current, [self.parent]), 0) + self.assertTrue(path.exists()) + + def test_missing_parents(self): + path = write(self.current, "entries/result", b"own result") + self.assertEqual(compiler.deduplicate(self.current, [self.root / "missing"]), 0) + self.assertEqual(path.read_bytes(), b"own result") + + def make_link(self, path, target): + try: + path.symlink_to(target) + except (NotImplementedError, OSError) as error: + self.skipTest(f"Symbolic-link fixture unavailable on this host: {error}") + + def test_symlink(self): + target = write(self.root, "outside", b"outside layer") + self.make_link(self.current / "link", target) + with self.assertRaises(ValueError): + compiler.fingerprint(self.current) + self.assertEqual(target.read_bytes(), b"outside layer") + + def test_validate_before_delete(self): + duplicate = write(self.current, "entries/result", b"compiled result") + write(self.parent, "entries/result", b"compiled result") + outside = write(self.root, "outside", b"outside layer") + self.make_link(self.parent / "unsafe", outside) + with self.assertRaises(ValueError): + compiler.deduplicate(self.current, [self.parent]) + self.assertEqual(duplicate.read_bytes(), b"compiled result") + self.assertEqual(outside.read_bytes(), b"outside layer") + + +class WorkflowTests(unittest.TestCase): + def setUp(self): + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + self.root = Path(temporary.name).resolve() + self.environment = pull_environment() + self.event = pull_event() + self.metadata = compiler.metadata(PREFIX, environment=self.environment, event=self.event, root=self.root) + self.current = Path(self.metadata["current-path"]) + self.delta = Path(self.metadata["delta-path"]) + self.output = Path(self.metadata["delta-archive"]) + self.develop = Path(self.metadata["develop-path"]) + self.main = Path(self.metadata["main-path"]) + self.deltas = self.root / "artifacts" + + def configure(self): + return compiler.configure(environment=self.environment, event=self.event, root=self.root) + + def pack(self): + return compiler.pack(self.output, environment=self.environment, event=self.event, root=self.root) + + def publish(self): + return compiler.publish(self.deltas, environment=self.environment, event=self.event, root=self.root) + + def artifact(self, name, entries): + return archive(self.deltas / name / "delta.tar", entries) + + def test_configure(self): + write(self.current, ENTRY_KEY, RESULT_HEADER + b"previous PR result", mtime=10) + write(self.develop, OTHER_KEY, RESULT_HEADER + b"develop result", mtime=20) + write(self.main, THIRD_KEY, RESULT_HEADER + b"main result", mtime=30) + before = {path: snapshot(path) for path in (self.current, self.develop, self.main)} + result = self.configure() + self.assertIn(result["CCACHE_REMOTE_ONLY"], ("1", "true")) + self.assertNotEqual(Path(result["CCACHE_DIR"]), self.current) + self.assertEqual(result["CCACHE_REMOTE_STORAGE"], compiler.storage_configuration( + self.delta, [self.current, self.develop, self.main])) + self.assertEqual(compiler._entries(self.delta), {}) + for path, contents in before.items(): + self.assertEqual(snapshot(path), contents) + + def test_dirty_delta(self): + entry = write(self.delta, ENTRY_KEY, RESULT_HEADER + b"unfinished worker") + with self.assertRaises(ValueError): + self.configure() + self.assertEqual(entry.read_bytes(), RESULT_HEADER + b"unfinished worker") + + def test_pack(self): + self.configure() + write(self.current, ENTRY_KEY, RESULT_HEADER + b"restored result", mtime=10) + write(self.develop, OTHER_KEY, RESULT_HEADER + b"shared result", mtime=20) + write(self.main, THIRD_KEY, RESULT_HEADER + b"main result", mtime=30) + write(self.develop, MANIFEST_KEY, MANIFEST_HEADER + b"old refs", mtime=40) + before = {path: snapshot(path) for path in (self.current, self.develop, self.main)} + write(self.delta, ENTRY_KEY, RESULT_HEADER + b"other serialization") + write(self.delta, OTHER_KEY, RESULT_HEADER + b"shared serialization") + write(self.delta, THIRD_KEY, RESULT_HEADER + b"main serialization") + write(self.delta, MANIFEST_KEY, MANIFEST_HEADER + b"new refs", mtime=50) + result = self.pack() + self.assertEqual(result, {"has-content": "true", "removed": "3", "trimmed": "0"}) + with tarfile.open(self.output, "r:") as packed: + self.assertEqual(packed.getnames(), [MANIFEST_KEY]) + self.assertEqual(packed.extractfile(MANIFEST_KEY).read(), MANIFEST_HEADER + b"new refs") + for path, contents in before.items(): + self.assertEqual(snapshot(path), contents) + + def test_pack_mtime(self): + self.configure() + timestamp = 1_600_000_000.125 + original = write(self.delta, ENTRY_KEY, RESULT_HEADER + b"new result", mtime=timestamp) + original_mtime = original.stat().st_mtime_ns + self.assertEqual(self.pack()["has-content"], "true") + with tarfile.open(self.output, "r:") as packed: + self.assertEqual(packed.getmember(ENTRY_KEY).mtime, timestamp) + target = self.deltas / "worker" / "delta.tar" + target.parent.mkdir(parents=True) + self.output.rename(target) + self.assertEqual(self.publish()["changed"], "true") + self.assertEqual((self.current / ENTRY_KEY).stat().st_mtime_ns, original_mtime) + + def test_native_formats(self): + self.configure() + contents = { + ENTRY_KEY: RESULT_HEADER + b"modern result", + LEGACY_ENTRY_KEY: RESULT_HEADER + b"legacy result", + LEGACY_MANIFEST_KEY: MANIFEST_HEADER + b"legacy refs", + } + for name, value in contents.items(): + write(self.delta, name, value, mtime=1_600_000_000.125) + self.assertEqual(self.pack()["has-content"], "true") + with tarfile.open(self.output, "r:") as packed: + self.assertEqual(set(packed.getnames()), set(contents)) + target = self.deltas / "worker" / "delta.tar" + target.parent.mkdir(parents=True) + self.output.rename(target) + self.assertEqual(self.publish()["archives"], "1") + self.assertEqual({name: path.read_bytes() for name, path in compiler._entries(self.current).items()}, contents) + + def test_native_names(self): + invalid = ( + "ab/" + "cd" + "w" * 29, + "ab/" + "gg" + "0" * 29, + "ab/" + "cd" + "0" * 28, + "ab/" + "cd" + "0" * 30, + # Use a distinct bucket so case-insensitive filesystems preserve its uppercase name. + "CD/" + "0" * 38, + "ab/CACHEDIR.TAG", + LEGACY_ENTRY_KEY + ".tmp", + ) + for index, name in enumerate(invalid): + with self.subTest(name=name): + directory = self.root / str(index) + write(directory, ENTRY_KEY, RESULT_HEADER + b"valid modern result") + write(directory, LEGACY_ENTRY_KEY, RESULT_HEADER + b"valid legacy result") + write(directory, name, b"unexpected file") + before = snapshot(directory) + with self.assertRaises(ValueError) as rejected: + compiler._native_entries(directory) + self.assertIn(repr(name), str(rejected.exception)) + self.assertEqual(snapshot(directory), before) + + def test_distinct_encodings(self): + self.configure() + write(self.current, LEGACY_ENTRY_KEY, RESULT_HEADER + b"same digest", mtime=10) + before = snapshot(self.current) + write(self.delta, MODERN_EQUIVALENT_KEY, RESULT_HEADER + b"same digest", mtime=20) + self.assertEqual(self.pack()["removed"], "0") + with tarfile.open(self.output, "r:") as packed: + self.assertEqual(packed.getnames(), [MODERN_EQUIVALENT_KEY]) + self.assertEqual(snapshot(self.current), before) + + def test_pack_shadow(self): + self.configure() + write(self.current, MANIFEST_KEY, MANIFEST_HEADER + b"current refs", mtime=10) + write(self.develop, MANIFEST_KEY, MANIFEST_HEADER + b"selected refs", mtime=10) + write(self.delta, MANIFEST_KEY, MANIFEST_HEADER + b"selected refs", mtime=20) + self.assertEqual(self.pack()["removed"], "0") + with tarfile.open(self.output, "r:") as packed: + self.assertEqual(packed.extractfile(MANIFEST_KEY).read(), MANIFEST_HEADER + b"selected refs") + + def test_pack_empty(self): + self.configure() + write(self.output.parent, self.output.name, b"stale archive") + result = self.pack() + self.assertEqual(result, {"has-content": "false", "removed": "0", "trimmed": "0"}) + self.assertFalse(self.output.exists()) + + def test_pack_path(self): + self.configure() + outside = self.root / "outside.tar" + with self.assertRaises(ValueError): + compiler.pack(outside, environment=self.environment, event=self.event, root=self.root) + self.assertFalse(outside.exists()) + + def test_pack_format(self): + self.configure() + write(self.delta, "unexpected/file", b"not a native cache entry") + with self.assertRaises(ValueError): + self.pack() + self.assertFalse(self.output.exists()) + + def test_publish_union(self): + original = write(self.current, ENTRY_KEY, RESULT_HEADER + b"original result", mtime=10) + write(self.current, MANIFEST_KEY, MANIFEST_HEADER + b"old refs", mtime=10) + self.artifact("a-worker", [ + (ENTRY_KEY, RESULT_HEADER + b"other serialization", 30), + (OTHER_KEY, RESULT_HEADER + b"first job", 20), + (MANIFEST_KEY, MANIFEST_HEADER + b"newest complete refs", 40), + ]) + self.artifact("z-worker", [ + (THIRD_KEY, RESULT_HEADER + b"second job", 25), + (MANIFEST_KEY, MANIFEST_HEADER + b"older complete refs", 35), + ]) + result = self.publish() + self.assertEqual(result["changed"], "true") + self.assertEqual(result["archives"], "2") + self.assertEqual(set(compiler._entries(self.current)), {ENTRY_KEY, OTHER_KEY, THIRD_KEY, MANIFEST_KEY}) + self.assertEqual(original.read_bytes(), RESULT_HEADER + b"original result") + self.assertEqual(original.stat().st_mtime_ns, 30_000_000_000) + self.assertEqual((self.current / OTHER_KEY).read_bytes(), RESULT_HEADER + b"first job") + self.assertEqual((self.current / THIRD_KEY).read_bytes(), RESULT_HEADER + b"second job") + self.assertEqual((self.current / MANIFEST_KEY).read_bytes(), MANIFEST_HEADER + b"newest complete refs") + + def test_manifest_tie(self): + self.artifact("z-worker", [(MANIFEST_KEY, MANIFEST_HEADER + b"z refs", 20)]) + self.artifact("a-worker", [(MANIFEST_KEY, MANIFEST_HEADER + b"a refs", 20)]) + self.publish() + self.assertEqual((self.current / MANIFEST_KEY).read_bytes(), MANIFEST_HEADER + b"z refs") + + def test_publish_parents(self): + write(self.develop, ENTRY_KEY, RESULT_HEADER + b"parent result", mtime=10) + write(self.main, THIRD_KEY, RESULT_HEADER + b"main result", mtime=20) + before = {path: snapshot(path) for path in (self.develop, self.main)} + self.artifact("worker", [ + (ENTRY_KEY, RESULT_HEADER + b"other serialization", 30), + (THIRD_KEY, RESULT_HEADER + b"main serialization", 30), + (OTHER_KEY, RESULT_HEADER + b"own result", 30), + ]) + result = self.publish() + self.assertEqual(result["changed"], "true") + self.assertEqual(result["removed"], "2") + self.assertEqual(set(compiler._entries(self.current)), {OTHER_KEY}) + for path, contents in before.items(): + self.assertEqual(snapshot(path), contents) + + def test_publish_trim(self): + write(self.current, ENTRY_KEY, RESULT_HEADER + b"old", mtime=10) + newest = RESULT_HEADER + b"newest" + self.artifact("worker", [(OTHER_KEY, newest, 20)]) + trim = compiler.trim + with patch.object(compiler, "trim", side_effect=lambda directory: trim(directory, limit=len(newest))): + result = self.publish() + self.assertEqual(result["trimmed"], "1") + self.assertEqual(result["changed"], "true") + self.assertEqual(set(compiler._entries(self.current)), {OTHER_KEY}) + self.assertEqual((self.current / OTHER_KEY).read_bytes(), newest) + + def test_unchanged(self): + write(self.current, ENTRY_KEY, RESULT_HEADER + b"own result", mtime=10) + before = snapshot(self.current) + result = self.publish() + self.assertEqual(result["changed"], "false") + self.assertEqual(result["has-content"], "true") + self.assertEqual(result["archives"], "0") + self.assertEqual(snapshot(self.current), before) + + def test_empty_layer(self): + result = self.publish() + self.assertEqual(result["changed"], "true") + self.assertEqual(result["has-content"], "true") + self.assertTrue((self.current / compiler.EMPTY_MARKER).exists()) + again = self.publish() + self.assertEqual(again["changed"], "false") + self.assertEqual(again["has-content"], "true") + + def test_empty_replacement(self): + write(self.develop, ENTRY_KEY, RESULT_HEADER + b"parent result", mtime=10) + write(self.current, ENTRY_KEY, RESULT_HEADER + b"own serialization", mtime=20) + before = snapshot(self.develop) + result = self.publish() + self.assertEqual(result["changed"], "true") + self.assertEqual(result["has-content"], "true") + self.assertEqual(result["removed"], "1") + self.assertTrue((self.current / compiler.EMPTY_MARKER).is_file()) + self.assertEqual(compiler._entries(self.current), {}) + self.assertEqual(snapshot(self.develop), before) + + def test_restored_empty(self): + marker = write(self.current, compiler.EMPTY_MARKER, b"restored tombstone", mtime=10) + result = self.publish() + self.assertEqual(result["changed"], "false") + self.assertEqual(result["has-content"], "true") + self.assertTrue(marker.is_file()) + + def test_new_entries(self): + marker = write(self.current, compiler.EMPTY_MARKER, b"") + self.artifact("worker", [(ENTRY_KEY, RESULT_HEADER + b"new result", 20)]) + result = self.publish() + self.assertEqual(result["changed"], "true") + self.assertFalse(marker.exists()) + self.assertEqual((self.current / ENTRY_KEY).read_bytes(), RESULT_HEADER + b"new result") + + def test_trim(self): + write(self.current, LEGACY_ENTRY_KEY, b"12345", mtime=10) + write(self.current, OTHER_KEY, b"123456", mtime=20) + write(self.current, THIRD_KEY, b"1234567", mtime=30) + self.assertEqual(compiler.trim(self.current, limit=13), 1) + self.assertEqual(set(compiler._entries(self.current)), {OTHER_KEY, THIRD_KEY}) + + def test_legacy_plan(self): + prefix = "ccache-v2-ubuntu-24.04-X64-linux-gcc-hwloc-Release" + result = compiler.migration_plan(prefix, environment=self.environment, event=self.event, root=self.root) + self.assertEqual(result, {"path": str(self.current), "prefix": prefix + "-pr-", + "key": prefix + "-pr-12345-2"}) + self.assertEqual(self.environment["CHERYL_CCACHE_PREFIX"], PREFIX) + + def test_legacy_family(self): + for prefix in ("ccache-v2-ubuntu-24.04-X64-linux-clang-debug-Debug", + "ccache-v2-windows-2025-X64-windows-msvc-hwloc-RelWithDebInfo", + "ccache-v2-ubuntu-24.04-ARM64-linux-gcc-hwloc-Release", + "ccache-v2-ubuntu-24.04-X64-unknown-Release"): + with self.subTest(prefix=prefix), self.assertRaises(ValueError): + compiler.migration_plan(prefix, environment=self.environment, event=self.event, root=self.root) + + def test_legacy_profiles(self): + profiles = ( + (PREFIX, "ubuntu-24.04", "linux-gcc-hwloc-Release"), + (PREFIX, "ubuntu-24.04", "linux-gcc-neutral-Release"), + (PREFIX, "ubuntu-24.04", "linux-modules-Release"), + (PREFIX, "ubuntu-24.04", "linux-modules-shared-Release"), + ("ccache-v3-Linux-X64-linux-clang18", "ubuntu-24.04", "linux-clang-debug-Debug"), + ("ccache-v3-Windows-X64-windows-msvc", "windows-2025", "windows-msvc-hwloc-RelWithDebInfo"), + ("ccache-v3-Windows-X64-windows-msvc", "windows-2025", "windows-modules-RelWithDebInfo"), + ("ccache-v3-Windows-X64-windows-msvc", "windows-2025", "windows-modules-shared-RelWithDebInfo"), + ) + for family, runner, profile in profiles: + with self.subTest(family=family, profile=profile): + prefix = f"ccache-v2-{runner}-X64-{profile}" + environment = {**self.environment, "CHERYL_CCACHE_PREFIX": family} + result = compiler.migration_plan(prefix, environment=environment, event=self.event, root=self.root) + self.assertEqual(result["path"], str(self.current)) + self.assertEqual(result["prefix"], prefix + "-pr-") + + def test_legacy_union(self): + write(self.develop, THIRD_KEY, RESULT_HEADER + b"develop result", mtime=5) + parent = snapshot(self.develop) + write(self.current, LEGACY_ENTRY_KEY, RESULT_HEADER + b"first profile", mtime=10) + write(self.current, LEGACY_MANIFEST_KEY, MANIFEST_HEADER + b"first refs", mtime=10) + first = compiler.import_legacy(environment=self.environment, event=self.event, root=self.root) + self.assertEqual(first["imported"], "2") + self.assertEqual(compiler._entries(self.current), {}) + write(self.current, OTHER_KEY, RESULT_HEADER + b"second profile", mtime=20) + write(self.current, LEGACY_MANIFEST_KEY, MANIFEST_HEADER + b"second refs", mtime=20) + second = compiler.import_legacy(environment=self.environment, event=self.event, root=self.root) + self.assertEqual(second["imported"], "2") + self.assertEqual(compiler._entries(self.current), {}) + result = self.publish() + self.assertEqual(result["changed"], "true") + self.assertEqual(result["archives"], "0") + self.assertEqual(set(compiler._entries(self.current)), {LEGACY_ENTRY_KEY, OTHER_KEY, LEGACY_MANIFEST_KEY}) + self.assertEqual((self.current / LEGACY_MANIFEST_KEY).read_bytes(), MANIFEST_HEADER + b"second refs") + self.assertEqual(snapshot(self.develop), parent) + + def test_legacy_empty(self): + marker = write(self.current, compiler.EMPTY_MARKER, b"old empty snapshot") + result = compiler.import_legacy(environment=self.environment, event=self.event, root=self.root) + self.assertEqual(result["imported"], "0") + self.assertFalse(marker.exists()) + published = self.publish() + self.assertEqual(published["changed"], "true") + self.assertEqual(published["has-content"], "true") + + def test_archive_links(self): + write(self.develop, OTHER_KEY, RESULT_HEADER + b"parent result", mtime=10) + protected = snapshot(self.develop) + outside = write(self.root, "outside", b"protected outside file") + for kind in (tarfile.SYMTYPE, tarfile.LNKTYPE): + with self.subTest(kind=kind): + member = tarfile.TarInfo(ENTRY_KEY) + member.type = kind + member.linkname = str(outside) + self.artifact("unsafe", [member]) + with self.assertRaises(ValueError): + self.publish() + self.assertEqual(outside.read_bytes(), b"protected outside file") + self.assertEqual(snapshot(self.develop), protected) + self.assertEqual(compiler._entries(self.current), {}) + + def test_archive_paths(self): + outside = write(self.root, "outside", b"protected outside file") + for name in ("../outside", "/outside", r"C:\outside", ENTRY_KEY + "/../outside"): + with self.subTest(name=name): + self.artifact("unsafe", [(OTHER_KEY, RESULT_HEADER + b"valid", 10), (name, b"bad", 10)]) + with self.assertRaises(ValueError): + self.publish() + self.assertEqual(outside.read_bytes(), b"protected outside file") + self.assertEqual(compiler._entries(self.current), {}) + + def test_archive_duplicate(self): + self.artifact("unsafe", [(ENTRY_KEY, b"first", 10), (ENTRY_KEY, b"second", 20)]) + with self.assertRaises(ValueError): + self.publish() + self.assertEqual(compiler._entries(self.current), {}) + + def test_archive_size(self): + self.artifact("unsafe", [(ENTRY_KEY, b"12345", 10), (OTHER_KEY, b"67890", 20)]) + with patch.object(compiler, "LAYER_LIMIT", 9), self.assertRaises(ValueError): + self.publish() + self.assertEqual(compiler._entries(self.current), {}) + + def test_archive_count(self): + self.artifact("unsafe", [(ENTRY_KEY, b"first", 10), (OTHER_KEY, b"second", 20)]) + with patch.object(compiler, "ARCHIVE_ENTRY_LIMIT", 1), self.assertRaises(ValueError): + self.publish() + self.assertEqual(compiler._entries(self.current), {}) + + def test_validate_archives(self): + write(self.current, ENTRY_KEY, RESULT_HEADER + b"restored result", mtime=5) + before = snapshot(self.current) + self.artifact("a-valid", [(OTHER_KEY, RESULT_HEADER + b"new result", 10)]) + self.artifact("z-unsafe", [("../outside", b"bad", 10)]) + with self.assertRaises(ValueError): + self.publish() + self.assertEqual(snapshot(self.current), before) + + def test_artifact_name(self): + write(self.deltas, "unexpected.txt", b"not a worker archive") + with self.assertRaises(ValueError): + self.publish() + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/ci/test_components.py b/.github/ci/test_components.py new file mode 100644 index 00000000..88eecd89 --- /dev/null +++ b/.github/ci/test_components.py @@ -0,0 +1,649 @@ +"""Exercise component selection and dependency ownership without building the engine.""" + +from pathlib import Path +import subprocess +import tempfile +import unittest +from unittest.mock import patch + + +ROOT = Path(__file__).resolve().parents[2] + + +class ComponentTests(unittest.TestCase): + def configure(self, body): + with tempfile.TemporaryDirectory(prefix="cheryl-components-") as directory: + root = Path(directory) + (root / "CMakeLists.txt").write_text( + 'cmake_minimum_required(VERSION 3.28)\nproject(probe LANGUAGES NONE)\n' + f'include("{ROOT.as_posix()}/cmake/CherylLibraries.cmake")\n' + f'include("{ROOT.as_posix()}/cmake/CherylPublicHeaders.cmake")\n' + + body, encoding="utf-8") + return subprocess.run(["cmake", "-S", str(root), "-B", str(root / "out"), "-G", "Ninja"], + text=True, capture_output=True, check=False) + + def test_runtime_copies(self): + result = self.configure(f''' +include("{ROOT.as_posix()}/cmake/CherylRuntimeDependencies.cmake") +file(WRITE "${{CMAKE_BINARY_DIR}}/first/freetype.dll" "same runtime") +file(WRITE "${{CMAKE_BINARY_DIR}}/second/freetype.dll" "same runtime") +cheryl_runtime_dependency_location(selected freetype.dll + "${{CMAKE_BINARY_DIR}}/first/freetype.dll" "${{CMAKE_BINARY_DIR}}/second/freetype.dll") +if(NOT selected STREQUAL "${{CMAKE_BINARY_DIR}}/first/freetype.dll") + message(FATAL_ERROR "Identical DLL copies were not resolved to the first location") +endif() +''') + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def test_runtime_conflict(self): + result = self.configure(f''' +include("{ROOT.as_posix()}/cmake/CherylRuntimeDependencies.cmake") +file(WRITE "${{CMAKE_BINARY_DIR}}/first/freetype.dll" "one runtime") +file(WRITE "${{CMAKE_BINARY_DIR}}/second/freetype.dll" "another runtime") +cheryl_runtime_dependency_location(selected freetype.dll + "${{CMAKE_BINARY_DIR}}/first/freetype.dll" "${{CMAKE_BINARY_DIR}}/second/freetype.dll") +''') + self.assertNotEqual(result.returncode, 0) + self.assertIn("Native DLL dependencies have conflicting locations: freetype.dll", result.stderr) + + def runtime_classification(self, windows, excluded, bundled): + expected_system = ";".join(excluded) + expected_payload = ";".join(bundled) + return self.configure(f''' +set(WIN32 {"TRUE" if windows else "FALSE"}) +set(ENV{{SystemRoot}} "${{CMAKE_BINARY_DIR}}/Win.dows+[Host]") +include("{ROOT.as_posix()}/cmake/CherylRuntimeDependencies.cmake") +function(file operation) + if(NOT operation STREQUAL "GET_RUNTIME_DEPENDENCIES") + if(operation STREQUAL "TO_CMAKE_PATH") + _file(TO_CMAKE_PATH "${{ARGV1}}" converted) + set(${{ARGV2}} "${{converted}}" PARENT_SCOPE) + else() + _file(${{ARGV}}) + endif() + return() + endif() + cmake_parse_arguments(probe "" "RESOLVED_DEPENDENCIES_VAR;UNRESOLVED_DEPENDENCIES_VAR;CONFLICTING_DEPENDENCIES_PREFIX" + "LIBRARIES;DIRECTORIES;PRE_EXCLUDE_REGEXES;POST_EXCLUDE_REGEXES" ${{ARGN}}) + set(system_names "{expected_system}") + set(payload_names "{expected_payload}") + foreach(name IN LISTS system_names payload_names) + set(excluded FALSE) + foreach(pattern IN LISTS probe_PRE_EXCLUDE_REGEXES) + if(name MATCHES "${{pattern}}") + set(excluded TRUE) + endif() + endforeach() + if(name IN_LIST system_names AND NOT excluded) + message(FATAL_ERROR "OS runtime would be packaged: ${{name}}") + elseif(name IN_LIST payload_names AND excluded) + message(FATAL_ERROR "Third-party runtime would be omitted: ${{name}}") + endif() + endforeach() + if(WIN32) + file(TO_CMAKE_PATH "$ENV{{SystemRoot}}" system_root) + set(host_paths "${{system_root}}/System32/SHELL32.dll" "${{system_root}}/kernel32.dll") + string(TOUPPER "${{system_root}}/System32/USER32.dll" alternate_case) + list(APPEND host_paths "${{alternate_case}}") + set(native_paths "${{system_root}}-private/freetype.dll" "${{CMAKE_BINARY_DIR}}/native/harfbuzz.dll") + foreach(library IN LISTS host_paths native_paths) + set(excluded FALSE) + foreach(pattern IN LISTS probe_POST_EXCLUDE_REGEXES) + if(library MATCHES "${{pattern}}") + set(excluded TRUE) + endif() + endforeach() + if(library IN_LIST host_paths AND NOT excluded) + message(FATAL_ERROR "OS imports would be scanned recursively: ${{library}}") + elseif(library IN_LIST native_paths AND excluded) + message(FATAL_ERROR "Third-party runtime path would be omitted: ${{library}}") + endif() + endforeach() + elseif(probe_POST_EXCLUDE_REGEXES) + message(FATAL_ERROR "Windows path exclusions leaked into Linux runtime scanning") + endif() + set(${{probe_RESOLVED_DEPENDENCIES_VAR}} "${{payload_names}}" PARENT_SCOPE) + set(${{probe_UNRESOLVED_DEPENDENCIES_VAR}} "" PARENT_SCOPE) + set(${{probe_CONFLICTING_DEPENDENCIES_PREFIX}}_FILENAMES "" PARENT_SCOPE) +endfunction() +cheryl_runtime_dependencies(actual seed-runtime) +if(NOT actual STREQUAL "seed-runtime;{expected_payload}") + message(FATAL_ERROR "Native closure was lost: ${{actual}}") +endif() +''') + + def test_linux_runtime_baseline(self): + result = self.runtime_classification(False, + ["ld-linux-x86-64.so.2", "libc.so.6", "libmvec.so.1", "libstdc++.so.6", "libgcc_s.so.1", + "libnss_files.so.2", "libX11.so.6", "libxcb.so.1", "libwayland-client.so.0", "libxkbcommon.so.0", + "libGLX.so.0", "libOpenGL.so.0", "libEGL.so.1", "libdrm_amdgpu.so.1", "libgbm.so.1", + "libasound.so.2", "libpulse-simple.so.0", "libjack.so.0", "libudev.so.1"], + ["libicuuc.so.78", "libicui18n.so.78", "libicudata.so.78", "libfreetype.so.6", "libharfbuzz.so.0", + "libhwloc.so.15", "libdw.so.1", "libelf.so.1", "libbrotlidec.so.1", "libglib-2.0.so.0", + "libgraphite2.so.3", "libpcre2-8.so.0", "libz.so.1", "libzstd.so.1", "libusb-1.0.so.0"]) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def test_windows_runtime_baseline(self): + result = self.runtime_classification(True, + ["icu.dll", "ICU.DLL", "opengl32.dll", "api-ms-win-crt-runtime-l1-1-0.dll", + "ext-ms-win-ntuser-window-l1-1-0.dll", "VCRUNTIME140_1.dll", "MSVCP140.dll", "ucrtbase.dll"], + ["icuuc78.dll", "freetype.dll", "harfbuzz.dll", "hwloc-15.dll", "hidapi.dll", "libpng16.dll"]) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def test_runtime_empty(self): + result = self.configure(f''' +include("{ROOT.as_posix()}/cmake/CherylRuntimeDependencies.cmake") +function(file operation) + message(FATAL_ERROR "Empty runtime closure should not inspect artifacts") +endfunction() +cheryl_runtime_dependencies(actual) +if(actual) + message(FATAL_ERROR "Empty runtime closure returned artifacts") +endif() +''') + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def test_runtime_missing(self): + result = self.configure(f''' +include("{ROOT.as_posix()}/cmake/CherylRuntimeDependencies.cmake") +function(file operation) + if(operation STREQUAL "TO_CMAKE_PATH") + _file(TO_CMAKE_PATH "${{ARGV1}}" converted) + set(${{ARGV2}} "${{converted}}" PARENT_SCOPE) + return() + endif() + cmake_parse_arguments(probe "" "RESOLVED_DEPENDENCIES_VAR;UNRESOLVED_DEPENDENCIES_VAR" + "LIBRARIES;DIRECTORIES;PRE_EXCLUDE_REGEXES;POST_EXCLUDE_REGEXES;CONFLICTING_DEPENDENCIES_PREFIX" ${{ARGN}}) + set(${{probe_RESOLVED_DEPENDENCIES_VAR}} "" PARENT_SCOPE) + set(${{probe_UNRESOLVED_DEPENDENCIES_VAR}} "libmissing.so.1" PARENT_SCOPE) +endfunction() +cheryl_runtime_dependencies(actual seed-runtime) +''') + self.assertNotEqual(result.returncode, 0) + self.assertIn("Cannot resolve required native runtime dependencies: libmissing.so.1", result.stderr) + + def runtime_install_probe(self, soname="libfixture.so.2", conflict=False): + return self.configure(f''' +set(WIN32 FALSE) +include("{ROOT.as_posix()}/cmake/CherylRuntimeDependencies.cmake") +# This exercises Linux staging even when the host uses Windows drive paths. +set(CMAKE_INSTALL_PREFIX "/prefix") +# Match the runtime patcher's canonical paths, including Windows 8.3 names. +file(REAL_PATH "${{CMAKE_BINARY_DIR}}" build_root) +set(ENV{{DESTDIR}} "${{build_root}}/stage") +set(staged "$ENV{{DESTDIR}}${{CMAKE_INSTALL_PREFIX}}") +file(WRITE "${{CMAKE_BINARY_DIR}}/source/libfixture.so.2.7" "runtime bytes") +if({"TRUE" if conflict else "FALSE"}) + file(WRITE "${{staged}}/lib/libfixture.so.2" "conflicting bytes") +endif() +function(cheryl_runtime_dependencies result) + set(${{result}} "${{ARGN}}" PARENT_SCOPE) +endfunction() +set(CHERYL_PATCHELF_EXECUTABLE "${{CMAKE_COMMAND}}") +set(CHERYL_NOTICE_PYTHON "${{CMAKE_COMMAND}}") +function(execute_process) + cmake_parse_arguments(probe "" "RESULT_VARIABLE;OUTPUT_VARIABLE;ERROR_VARIABLE" "COMMAND" ${{ARGV}}) + if("--print-soname" IN_LIST probe_COMMAND) + set(${{probe_OUTPUT_VARIABLE}} "{soname}" PARENT_SCOPE) + elseif("--set-rpath" IN_LIST probe_COMMAND) + list(GET probe_COMMAND -1 artifact) + if(NOT artifact STREQUAL "${{staged}}/lib/libfixture.so.2.7" OR NOT EXISTS "${{artifact}}") + message(FATAL_ERROR "Runtime patch missed DESTDIR: ${{artifact}}") + endif() + set_property(GLOBAL APPEND PROPERTY patched "${{artifact}}") + elseif("--destination" IN_LIST probe_COMMAND) + list(FIND probe_COMMAND "--destination" index) + math(EXPR index "${{index}} + 1") + list(GET probe_COMMAND ${{index}} notice_destination) + if(NOT notice_destination STREQUAL "${{staged}}/share/licenses/native") + message(FATAL_ERROR "Native notices missed DESTDIR: ${{notice_destination}}") + endif() + set_property(GLOBAL PROPERTY notices_collected TRUE) + else() + message(FATAL_ERROR "Unexpected external command") + endif() + set(${{probe_RESULT_VARIABLE}} 0 PARENT_SCOPE) +endfunction() +cheryl_sdk_install_runtimes("${{CMAKE_INSTALL_PREFIX}}/lib" "${{CMAKE_BINARY_DIR}}/source/libfixture.so.2.7") +get_property(patched GLOBAL PROPERTY patched) +get_property(notices_collected GLOBAL PROPERTY notices_collected) +if(NOT patched STREQUAL "${{staged}}/lib/libfixture.so.2.7" OR NOT notices_collected) + message(FATAL_ERROR "Runtime staging did not patch and collect exactly once") +endif() +file(READ_SYMLINK "${{staged}}/lib/libfixture.so.2" soname_target) +if(NOT soname_target STREQUAL "libfixture.so.2.7") + message(FATAL_ERROR "Versioned runtime did not get a relative SONAME alias") +endif() +''') + + def test_runtime_destdir(self): + result = self.runtime_install_probe() + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def test_runtime_alias(self): + with tempfile.TemporaryDirectory(prefix="cheryl-alias-") as directory: + root = Path(directory).resolve() + workspace = root / "real workspace" + workspace.mkdir() + alias = root / "alias workspace" + try: + alias.symlink_to(workspace, target_is_directory=True) + except (NotImplementedError, OSError) as error: + self.skipTest(f"Directory-link fixture unavailable: {error}") + with patch.object(tempfile, "TemporaryDirectory") as temporary: + temporary.return_value.__enter__.return_value = str(alias) + result = self.runtime_install_probe() + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def test_runtime_soname_conflict(self): + result = self.runtime_install_probe(conflict=True) + self.assertNotEqual(result.returncode, 0) + self.assertIn("Conflicting SDK runtime SONAME: libfixture.so.2", result.stderr) + + def test_runtime_soname_path(self): + result = self.runtime_install_probe(soname="../escape.so") + self.assertNotEqual(result.returncode, 0) + self.assertIn("Unsafe SDK runtime SONAME: ../escape.so", result.stderr) + + def test_windows_runtime_install(self): + result = self.configure(f''' +set(WIN32 TRUE) +unset(ENV{{DESTDIR}}) +include("{ROOT.as_posix()}/cmake/CherylRuntimeDependencies.cmake") +set(CMAKE_INSTALL_PREFIX "${{CMAKE_BINARY_DIR}}/prefix") +file(WRITE "${{CMAKE_BINARY_DIR}}/source/fixture.dll" "runtime bytes") +function(cheryl_runtime_dependencies result) + set(${{result}} "${{ARGN}}" PARENT_SCOPE) +endfunction() +function(execute_process) + message(FATAL_ERROR "Windows runtime installation invoked a Linux tool") +endfunction() +cheryl_sdk_install_runtimes("${{CMAKE_INSTALL_PREFIX}}/bin" "${{CMAKE_BINARY_DIR}}/source/fixture.dll") +file(READ "${{CMAKE_INSTALL_PREFIX}}/bin/fixture.dll" installed) +if(NOT installed STREQUAL "runtime bytes") + message(FATAL_ERROR "The Windows runtime was not installed intact") +endif() +''') + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def test_shared_runtime(self): + result = self.configure(''' +add_library(first INTERFACE) +add_library(second INTERFACE) +add_library(runtime SHARED IMPORTED) +set_target_properties(runtime PROPERTIES IMPORTED_LOCATION "/unused/libruntime.so") +cheryl_shared_dependency(runtime OWNER first) +cheryl_shared_dependency(runtime OWNER second) +cheryl_shared_dependency(runtime OWNER first) +get_property(declarations GLOBAL PROPERTY CHERYL_SHARED_DEPENDENCIES) +if(NOT declarations STREQUAL "runtime|first;runtime|second") + message(FATAL_ERROR "Shared runtime consumers were lost or repeated: ${declarations}") +endif() +''') + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def test_shared_rejects_archive(self): + result = self.configure(''' +add_library(owner INTERFACE) +add_library(archive STATIC IMPORTED) +cheryl_shared_dependency(archive OWNER owner) +''') + self.assertNotEqual(result.returncode, 0) + self.assertIn("requires a shared implementation", result.stderr) + + def test_shared_wrapper(self): + result = self.configure(''' +add_library(owner INTERFACE) +add_library(runtime UNKNOWN IMPORTED) +set_target_properties(runtime PROPERTIES IMPORTED_LOCATION "/unused/libruntime.so.2") +add_library(wrapper INTERFACE IMPORTED) +set_target_properties(wrapper PROPERTIES INTERFACE_LINK_LIBRARIES runtime) +cheryl_shared_dependency(wrapper OWNER owner) +get_property(declarations GLOBAL PROPERTY CHERYL_SHARED_DEPENDENCIES) +if(NOT declarations STREQUAL "runtime|owner") + message(FATAL_ERROR "The wrapper did not resolve to its runtime: ${declarations}") +endif() +''') + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def test_embedded_uses_shared(self): + result = self.configure(''' +add_library(owner INTERFACE) +add_library(runtime SHARED IMPORTED) +set_target_properties(runtime PROPERTIES IMPORTED_LOCATION "/unused/libruntime.so") +add_library(archive STATIC IMPORTED) +set_target_properties(archive PROPERTIES IMPORTED_LOCATION "/unused/libarchive.a" + INTERFACE_LINK_LIBRARIES runtime) +cheryl_shared_dependency(runtime OWNER owner) +cheryl_private_dependency(archive OWNER owner) +get_property(embedded GLOBAL PROPERTY CHERYL_PRIVATE_DEPENDENCIES) +if(NOT embedded STREQUAL "archive|owner") + message(FATAL_ERROR "Shared runtime was treated as an embedded archive: ${embedded}") +endif() +''') + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def test_system_interface(self): + result = self.configure(''' +add_library(owner INTERFACE) +add_library(system_api INTERFACE IMPORTED) +set_target_properties(system_api PROPERTIES INTERFACE_LINK_LIBRARIES system_api_import) +cheryl_shared_dependency(system_api OWNER owner SYSTEM) +get_target_property(system system_api CHERYL_SHARED_DEPENDENCY_SYSTEM) +if(NOT system) + message(FATAL_ERROR "The operating-system dependency was not identified") +endif() +''') + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def test_selection(self): + result = self.configure(''' +add_library(cengine INTERFACE) +add_library(Cheryl::Engine ALIAS cengine) +cheryl_register_public_component(cengine) +add_library(optional INTERFACE) +cheryl_register_public_component(optional FEATURE CHERYL_HAS_OPTIONAL) +get_target_property(before cheryl_assembly INTERFACE_LINK_LIBRARIES) +if("optional" IN_LIST before) + message(FATAL_ERROR "Availability selected the optional component") +endif() +cheryl_select_public_component(optional) +cheryl_select_public_component(optional) +get_target_property(after cheryl_assembly INTERFACE_LINK_LIBRARIES) +if(NOT after STREQUAL "Cheryl::Engine;optional") + message(FATAL_ERROR "Selection was omitted or repeated: ${after}") +endif() +''') + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def test_shared_rejected(self): + result = self.configure(''' +add_library(owner INTERFACE) +add_library(dependency SHARED IMPORTED) +cheryl_private_dependency(dependency OWNER owner) +''') + self.assertNotEqual(result.returncode, 0) + self.assertIn("requires a static dependency", result.stderr) + + def test_owner_collision(self): + result = self.configure(''' +add_library(first INTERFACE) +add_library(second INTERFACE) +add_library(dependency STATIC IMPORTED) +add_library(alias ALIAS dependency) +cheryl_private_dependency(dependency OWNER first) +cheryl_private_dependency(alias OWNER second) +''') + self.assertNotEqual(result.returncode, 0) + self.assertIn("already belongs to first", result.stderr) + + def test_imported_archive(self): + result = self.configure(''' +add_library(owner INTERFACE) +add_library(dependency UNKNOWN IMPORTED) +set_target_properties(dependency PROPERTIES IMPORTED_LOCATION "/unused/libdependency.a") +cheryl_private_dependency(dependency OWNER owner) +get_target_property(pic dependency POSITION_INDEPENDENT_CODE) +if(pic) + message(FATAL_ERROR "Supplied dependency settings were changed") +endif() +''') + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def test_interface_owner(self): + result = self.configure(''' +add_library(first INTERFACE) +add_library(second INTERFACE) +add_library(archive STATIC IMPORTED) +add_library(wrapper INTERFACE IMPORTED) +set_target_properties(wrapper PROPERTIES INTERFACE_LINK_LIBRARIES archive) +cheryl_private_dependency(wrapper OWNER first) +cheryl_private_dependency(archive OWNER second) +''') + self.assertNotEqual(result.returncode, 0) + self.assertIn("already belongs to first", result.stderr) + + def test_archive_aliases(self): + result = self.configure(''' +add_library(first INTERFACE) +add_library(second INTERFACE) +add_library(a STATIC IMPORTED) +add_library(b UNKNOWN IMPORTED) +set_target_properties(a b PROPERTIES IMPORTED_LOCATION "/unused/libimplementation.a") +cheryl_private_dependency(a OWNER first) +cheryl_private_dependency(b OWNER second) +''') + self.assertNotEqual(result.returncode, 0) + self.assertIn("already belongs to first", result.stderr) + + def test_public_wrapper(self): + result = self.configure(''' +set(CMAKE_SYSTEM_NAME Linux) +add_library(owner SHARED IMPORTED) +add_library(archive STATIC IMPORTED) +add_library(wrapper INTERFACE IMPORTED) +set_target_properties(wrapper PROPERTIES INTERFACE_LINK_LIBRARIES archive) +cheryl_private_dependency(wrapper OWNER owner PUBLIC_API) +string(SHA256 identity archive) +get_property(exported GLOBAL PROPERTY "CHERYL_DEPENDENCY_PUBLIC_API_${identity}") +if(NOT exported) + message(FATAL_ERROR "The wrapper lost its native authoring exports") +endif() +''') + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def test_objects_rejected(self): + result = self.configure(''' +add_library(owner INTERFACE) +add_library(dependency OBJECT IMPORTED) +cheryl_private_dependency(dependency OWNER owner) +''') + self.assertNotEqual(result.returncode, 0) + self.assertIn("OBJECT_LIBRARY dependency is not supported", " ".join(result.stderr.split())) + + def test_transitive_shared(self): + for kind in ("STATIC", "UNKNOWN"): + with self.subTest(kind=kind): + result = self.configure(f''' +add_library(owner INTERFACE) +add_library(root {kind} IMPORTED) +set_target_properties(root PROPERTIES IMPORTED_LOCATION "/unused/libroot.a" + INTERFACE_LINK_LIBRARIES "$>>") +add_library(child SHARED IMPORTED) +cheryl_private_dependency(root OWNER owner) +''') + self.assertNotEqual(result.returncode, 0) + self.assertIn("requires a static child", result.stderr) + + def test_transitive_owner(self): + result = self.configure(''' +add_library(first INTERFACE) +add_library(second INTERFACE) +add_library(root STATIC IMPORTED) +add_library(child STATIC IMPORTED) +set_target_properties(root PROPERTIES INTERFACE_LINK_LIBRARIES "$") +cheryl_private_dependency(root OWNER first) +cheryl_private_dependency(child OWNER second) +''') + self.assertNotEqual(result.returncode, 0) + self.assertIn("already belongs to first", result.stderr) + + def test_conditional_shared(self): + result = self.configure(''' +add_library(owner INTERFACE) +add_library(root STATIC IMPORTED) +add_library(child SHARED IMPORTED) +set_target_properties(root PROPERTIES INTERFACE_LINK_LIBRARIES "$<$:child>") +cheryl_private_dependency(root OWNER owner) +''') + self.assertNotEqual(result.returncode, 0) + self.assertIn("requires a static child", result.stderr) + + def test_cycle_and_headers(self): + result = self.configure(''' +add_library(owner INTERFACE) +add_library(root STATIC IMPORTED) +add_library(child STATIC IMPORTED) +add_library(headers INTERFACE IMPORTED) +set_target_properties(root PROPERTIES INTERFACE_LINK_LIBRARIES "child;headers") +set_target_properties(child PROPERTIES INTERFACE_LINK_LIBRARIES root) +set_target_properties(headers PROPERTIES INTERFACE_LINK_LIBRARIES "$") +cheryl_private_dependency(root OWNER owner) +get_property(dependencies GLOBAL PROPERTY CHERYL_PRIVATE_DEPENDENCIES) +list(LENGTH dependencies count) +if(NOT count EQUAL 2) + message(FATAL_ERROR "The cycle lost or duplicated an implementation: ${dependencies}") +endif() +''') + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def test_empty_wrapper(self): + result = self.configure(''' +add_library(owner INTERFACE) +add_library(headers INTERFACE IMPORTED) +cheryl_private_dependency(headers OWNER owner) +''') + self.assertNotEqual(result.returncode, 0) + self.assertIn("Cannot establish a static implementation behind headers", result.stderr) + + def test_public_and_system_links(self): + result = self.configure(''' +add_library(cengine SHARED IMPORTED) +add_library(Cheryl::Engine ALIAS cengine) +cheryl_register_public_component(cengine) +add_library(Threads::Threads INTERFACE IMPORTED) +add_library(X11::X11 SHARED IMPORTED) +add_library(OpenGL::GL SHARED IMPORTED) +add_library(owner INTERFACE) +add_library(root STATIC IMPORTED) +set_target_properties(root PROPERTIES INTERFACE_LINK_LIBRARIES + "Cheryl::Engine;Threads::Threads;X11::X11;OpenGL::GL;dl;pthread;-Wl,--as-needed;/unused/libsystem.so") +cheryl_private_dependency(root OWNER owner) +get_property(dependencies GLOBAL PROPERTY CHERYL_PRIVATE_DEPENDENCIES) +if(NOT dependencies STREQUAL "root|owner") + message(FATAL_ERROR "Public or system libraries were given a private owner: ${dependencies}") +endif() +''') + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def test_raw_archive(self): + result = self.configure(''' +add_library(owner INTERFACE) +add_library(root STATIC IMPORTED) +set_target_properties(root PROPERTIES INTERFACE_LINK_LIBRARIES "$") +cheryl_private_dependency(root OWNER owner) +''') + self.assertNotEqual(result.returncode, 0) + self.assertIn("links unidentified archive /unused/libchild.a", result.stderr) + self.assertIn("Supply an imported STATIC or UNKNOWN target", " ".join(result.stderr.split())) + + def test_system_archive(self): + result = self.configure(''' +set(UNIX TRUE) +set(CMAKE_SYSROOT /target-sdk) +add_library(owner INTERFACE) +add_library(root STATIC IMPORTED) +set_target_properties(root PROPERTIES INTERFACE_LINK_LIBRARIES + "/usr/lib/librt.a;/lib64/libdl.a;/target-sdk/usr/lib/libpthread.a") +cheryl_private_dependency(root OWNER owner) +''') + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + for archive in ("/private/lib/librt.a", "/usr/lib/libfreetype.a", "/usr/library/librt.a", "/private/child.Lib"): + with self.subTest(archive=archive): + result = self.configure(f''' +set(UNIX TRUE) +add_library(owner INTERFACE) +add_library(root STATIC IMPORTED) +set_target_properties(root PROPERTIES INTERFACE_LINK_LIBRARIES "{archive}") +cheryl_private_dependency(root OWNER owner) +''') + self.assertNotEqual(result.returncode, 0) + self.assertIn("links unidentified archive", result.stderr) + + def test_windows_system_archive(self): + result = self.configure(''' +set(UNIX FALSE) +set(WIN32 TRUE) +cheryl_is_system_archive(sdk "/SDK/Windows Kits/10/Lib/10.0.26100.0/um/x64/User32.Lib") +cheryl_is_system_archive(private "/private/User32.Lib") +cheryl_is_system_archive(unknown "/SDK/Windows Kits/10/Lib/10.0.26100.0/um/x64/freetype.lib") +cheryl_is_system_archive(escaped "/SDK/Windows Kits/10/Lib/../../../private/user32.lib") +if(NOT sdk OR private OR unknown OR escaped) + message(FATAL_ERROR "Windows SDK exception was missing or too broad") +endif() +''') + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def toolkit_fixture(self, directory, toolkit): + root = Path(directory) + upstream = root / "upstream" + upstream.mkdir() + if toolkit == "rmlui": + body = ''' +project(RmlUi VERSION 6.3 LANGUAGES NONE) +set(RMLUI_FONT_ENGINE freetype CACHE STRING "Font engine") +add_library(rmlui_core SHARED) +''' + font_source = upstream / "Source/Core/FontEngineDefault" + font_source.mkdir(parents=True) + for source in ("FontEngineInterfaceDefault", "FontFace", "FontFaceHandleDefault", "FontFaceLayer", "FontFamily", "FontProvider"): + (font_source / f"{source}.cpp").write_text("", encoding="utf-8") + expected = {"RMLUI_FONT_ENGINE": "none"} + setup = ''' +function(cheryl_fix_rmlui_placeholder) +endfunction() +''' + helper = "RmlUi" + else: + body = f''' +include("{ROOT.as_posix()}/extern/tgui/cmake/Macros.cmake") +tgui_set_option(TGUI_BACKEND SFML_GRAPHICS STRING "Backend") +tgui_set_option(TGUI_CUSTOM_BACKEND_HAS_FONT_FREETYPE FALSE BOOL "FreeType") +set(TGUI_VERSION 1.13.0) +add_library(tgui SHARED) +set_property(TARGET tgui PROPERTY SOURCES "${{CMAKE_CURRENT_SOURCE_DIR}}/Loading/ImageLoader.cpp") +''' + expected = {"TGUI_BACKEND": "Custom", "TGUI_CUSTOM_BACKEND_HAS_FONT_FREETYPE": "OFF"} + setup = "" + helper = "TGUI" + (upstream / "CMakeLists.txt").write_text(body, encoding="utf-8") + checks = "\n".join( + f'if(NOT "$CACHE{{{name}}}" STREQUAL "{value}")\n' + f' message(FATAL_ERROR "Owned {name} cache drifted: $CACHE{{{name}}}")\nendif()' + for name, value in expected.items()) + (root / "CMakeLists.txt").write_text(f''' +cmake_minimum_required(VERSION 3.28) +project(toolkit_probe LANGUAGES CXX) +set(CHERYL_BUILD_SHARED_LIBS OFF) +set(CHERYL_{toolkit.upper()}_SOURCE "{upstream.as_posix()}") +include("{ROOT.as_posix()}/projects/modules/ui/{toolkit}/cmake/{helper}.cmake") +{setup} +cheryl_add_{toolkit}_source() +{checks} +''', encoding="utf-8") + return ["cmake", "-S", str(root), "-B", str(root / "out"), "-G", "Ninja"] + + def test_toolkit_reconfigure(self): + for toolkit in ("rmlui", "tgui"): + with self.subTest(toolkit=toolkit), tempfile.TemporaryDirectory(prefix="cheryl-toolkit-") as directory: + command = self.toolkit_fixture(directory, toolkit) + for iteration in range(2): + result = subprocess.run(command, text=True, capture_output=True, check=False) + self.assertEqual(result.returncode, 0, f"Configure {iteration + 1}:\n" + result.stdout + result.stderr) + + def test_toolkit_cache_preserved(self): + incompatible = (("rmlui", "RMLUI_FONT_ENGINE", "freetype"), + ("tgui", "TGUI_BACKEND", "SFML_GRAPHICS"), + ("tgui", "TGUI_CUSTOM_BACKEND_HAS_FONT_FREETYPE", "ON")) + for toolkit, name, value in incompatible: + with self.subTest(option=name), tempfile.TemporaryDirectory(prefix="cheryl-toolkit-") as directory: + command = self.toolkit_fixture(directory, toolkit) + result = subprocess.run([*command, f"-D{name}:STRING={value}"], text=True, capture_output=True, check=False) + self.assertNotEqual(result.returncode, 0) + self.assertIn("Cheryl UI needs", result.stderr) + cache = (Path(directory) / "out/CMakeCache.txt").read_text(encoding="utf-8") + self.assertIn(f"{name}:STRING={value}\n", cache) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/ci/test_dependencies.py b/.github/ci/test_dependencies.py new file mode 100644 index 00000000..32035233 --- /dev/null +++ b/.github/ci/test_dependencies.py @@ -0,0 +1,1144 @@ +"""Check dependency and shared environment cache rules without installing packages.""" + +from copy import deepcopy +from contextlib import redirect_stdout +import importlib.util +import io +import json +from pathlib import Path +import re +import shutil +import subprocess +import sys +import tempfile +import unittest +from unittest.mock import patch + + +spec = importlib.util.spec_from_file_location("ci_dependencies", Path(__file__).with_name("dependencies.py")) +dependencies = importlib.util.module_from_spec(spec) +spec.loader.exec_module(dependencies) + +CONFIGURATION = { + "cache_version": 2, + "windows": {"ccache_version": "4.14", "ccache_package_version": "4.14.0"}, + "linux": { + "apt_common": ["ccache", "ninja-build"], + "apt_extra": {"headless": ["clang-18"], "modules": ["libgl-dev"]}, + }, +} +ENVIRONMENT = { + "RUNNER_OS": "Linux", "RUNNER_ARCH": "X64", "ImageOS": "ubuntu24", + "ImageVersion": "20261012.1.0", "GITHUB_REPOSITORY": "cppcooper/cheryl-engine", + "GITHUB_EVENT_NAME": "push", "GITHUB_REF": "refs/heads/main", + "GITHUB_RUN_ID": "12345", "GITHUB_RUN_ATTEMPT": "2", "GITHUB_SHA": "a" * 40, +} +BASELINE = "b" * 40 +REQUIREMENTS = b"Jinja2==3.1.6\n" +PYTHON_TAG = "cpython-313" +URIS = ["'https://archive.example.test/a_1_amd64.deb' a_1_amd64.deb 42 MD5Sum:abc", + "'https://archive.example.test/b_2_amd64.deb' b_2_amd64.deb 64 MD5Sum:def"] +Y_FIELDS = ("environment-prefix", "tools-key", "pip-key") + + +def keys(profile="headless", *, environment=None, configuration=None, + baseline=BASELINE, requirements=REQUIREMENTS, python_tag=PYTHON_TAG): + return dependencies.environment_keys( + profile, baseline, requirements, python_tag=python_tag, + environment=ENVIRONMENT if environment is None else environment, + configuration=CONFIGURATION if configuration is None else configuration) + + +def apt_key(profile="headless", uris=URIS, *, environment=None, configuration=None): + return dependencies.apt_cache_key( + profile, uris, environment=ENVIRONMENT if environment is None else environment, + configuration=CONFIGURATION if configuration is None else configuration) + + +class IdentityTests(unittest.TestCase): + def test_prefix(self): + result = keys() + prefix = result["environment-prefix"] + self.assertTrue(prefix.startswith("actions-env-cache-v2-Linux-X64-headless-")) + for name in ("tools-key", "pip-key"): + self.assertTrue(result[name].startswith(prefix), name) + self.assertTrue(apt_key().startswith(prefix)) + + def test_revision(self): + changed = deepcopy(CONFIGURATION) + changed["cache_version"] = 3 + original = keys() + revised = keys(configuration=changed) + for name in Y_FIELDS: + self.assertNotEqual(original[name], revised[name], name) + self.assertTrue(revised[name].startswith("actions-env-cache-v3-"), name) + self.assertNotEqual(apt_key(), apt_key(configuration=changed)) + + def test_runner(self): + original = keys() + for name, value in (("RUNNER_OS", "Windows"), ("RUNNER_ARCH", "ARM64"), + ("ImageOS", "ubuntu26"), ("ImageVersion", "20261013.1.0")): + with self.subTest(name=name): + environment = {**ENVIRONMENT, name: value} + changed = keys(environment=environment) + for field in Y_FIELDS: + self.assertNotEqual(original[field], changed[field], field) + self.assertNotEqual(apt_key(), apt_key(environment=environment)) + + def test_composition(self): + prefixes = {keys(profile)["environment-prefix"] for profile in ("headless", "modules", "docs")} + self.assertEqual(len(prefixes), 3) + self.assertNotEqual(apt_key(), apt_key("modules")) + + def test_package_inputs(self): + changed = deepcopy(CONFIGURATION) + changed["linux"]["apt_common"].append("pkg-config") + original = keys() + revised = keys(configuration=changed) + for name in Y_FIELDS: + self.assertNotEqual(original[name], revised[name], name) + self.assertNotEqual(apt_key(), apt_key(configuration=changed)) + + def test_run_independent(self): + original = keys() + changes = {"GITHUB_REF": "refs/pull/42/merge", "GITHUB_EVENT_NAME": "pull_request", + "GITHUB_RUN_ID": "67890", "GITHUB_RUN_ATTEMPT": "9", "GITHUB_SHA": "c" * 40} + revised = keys(environment={**ENVIRONMENT, **changes}) + for name in Y_FIELDS: + self.assertEqual(original[name], revised[name], name) + self.assertEqual(apt_key(), apt_key(environment={**ENVIRONMENT, **changes})) + + def test_tool_versions(self): + environment = {**ENVIRONMENT, "RUNNER_OS": "Windows", "ImageOS": "win25"} + original = keys(environment=environment)["tools-key"] + self.assertNotEqual(original, keys(environment=environment, baseline="d" * 40)["tools-key"]) + for field, value in (("ccache_version", "4.15"), ("ccache_package_version", "4.15.0")): + with self.subTest(field=field): + changed = deepcopy(CONFIGURATION) + changed["windows"][field] = value + self.assertNotEqual(original, keys(environment=environment, configuration=changed)["tools-key"]) + + def test_python_inputs(self): + original = keys()["pip-key"] + self.assertNotEqual(original, keys(requirements=b"Jinja2==3.1.7\n")["pip-key"]) + self.assertNotEqual(original, keys(python_tag="cpython-314")["pip-key"]) + + def test_apt_downloads(self): + original = apt_key() + self.assertEqual(original, apt_key(uris=list(reversed(URIS)))) + self.assertNotEqual(original, apt_key(uris=[URIS[0]])) + changed = [URIS[0].replace("a_1_", "a_2_"), URIS[1]] + self.assertNotEqual(original, apt_key(uris=changed)) + self.assertNotEqual(original, apt_key(uris=[URIS[0].replace("MD5Sum:abc", "MD5Sum:xyz"), URIS[1]])) + + def test_pure_metadata(self): + configuration = deepcopy(CONFIGURATION) + environment = dict(ENVIRONMENT) + with patch.object(dependencies.subprocess, "run", side_effect=AssertionError("Unexpected command")), \ + patch.object(dependencies.subprocess, "check_output", side_effect=AssertionError("Unexpected Git query")): + keys(environment=environment, configuration=configuration) + apt_key(environment=environment, configuration=configuration) + self.assertEqual(configuration, CONFIGURATION) + self.assertEqual(environment, ENVIRONMENT) + + def test_invalid_revision(self): + for revision in (0, -1, True, "2", None): + with self.subTest(revision=revision): + configuration = {**CONFIGURATION, "cache_version": revision} + with self.assertRaises(ValueError): + keys(configuration=configuration) + + def test_invalid_runner(self): + for name, value in (("RUNNER_OS", "Linux\nextra=value"), ("RUNNER_ARCH", "../X64"), + ("RUNNER_OS", ""), ("RUNNER_ARCH", None)): + with self.subTest(name=name, value=value), self.assertRaises(ValueError): + keys(environment={**ENVIRONMENT, name: value}) + + def test_invalid_component(self): + for changes in ({"baseline": ""}, {"requirements": "Jinja2==3.1.6"}, + {"python_tag": ""}, {"python_tag": "cpython-313\nextra=value"}): + with self.subTest(changes=changes), self.assertRaises(ValueError): + keys(**changes) + for uris in ("one URI", [URIS[0], None]): + with self.subTest(uris=uris), self.assertRaises(ValueError): + apt_key(uris=uris) + + +class WriterTests(unittest.TestCase): + def writer(self, **changes): + return dependencies.environment_metadata( + "headless", environment={**ENVIRONMENT, **changes}, configuration=CONFIGURATION)["environment-cache-writer"] + + def test_main(self): + self.assertEqual(self.writer(), "true") + self.assertEqual(self.writer(GITHUB_EVENT_NAME="workflow_dispatch"), "true") + + def test_other_refs(self): + for ref in ("refs/heads/develop", "refs/heads/feature", "refs/pull/3/merge", "refs/tags/main", "main"): + with self.subTest(ref=ref): + self.assertEqual(self.writer(GITHUB_REF=ref), "false") + + def test_untrusted_events(self): + for event in ("pull_request", "pull_request_target", "workflow_run", "issue_comment", "repository_dispatch"): + with self.subTest(event=event): + self.assertEqual(self.writer(GITHUB_EVENT_NAME=event), "false") + + def test_other_repositories(self): + for repository in ("someone/cheryl-engine", "cppcooper/fork", ""): + with self.subTest(repository=repository): + self.assertEqual(self.writer(GITHUB_REPOSITORY=repository), "false") + + +class DependencyScopeTests(unittest.TestCase): + def scope(self, **changes): + return dependencies.dependency_scope(environment={**ENVIRONMENT, **changes}) + + def test_branch_writers(self): + for branch in ("main", "develop"): + for event in ("push", "workflow_dispatch"): + with self.subTest(branch=branch, event=event): + result = self.scope(GITHUB_REF=f"refs/heads/{branch}", GITHUB_EVENT_NAME=event) + self.assertEqual(result["dependency-role"], branch) + self.assertEqual(result["dependency-cache-writer"], "true") + + def test_pull_base(self): + for base in ("main", "develop"): + with self.subTest(base=base): + result = self.scope(GITHUB_EVENT_NAME="pull_request", GITHUB_REF="refs/pull/3/merge", + GITHUB_BASE_REF=base) + self.assertEqual(result["dependency-role"], base) + self.assertEqual(result["dependency-cache-writer"], "false") + + def test_no_pull_writer(self): + result = self.scope(GITHUB_EVENT_NAME="pull_request", GITHUB_REF="refs/heads/main", GITHUB_BASE_REF="main") + self.assertEqual(result["dependency-cache-writer"], "false") + + def test_no_other_writer(self): + changes = ({"GITHUB_REPOSITORY": "someone/cheryl-engine"}, + {"GITHUB_REF": "refs/heads/feature"}, {"GITHUB_REF": "refs/tags/main"}, + {"GITHUB_EVENT_NAME": "workflow_run"}) + for change in changes: + with self.subTest(change=change): + self.assertEqual(self.scope(**change)["dependency-cache-writer"], "false") + + +class CommandTests(unittest.TestCase): + def setUp(self): + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + self.root = Path(temporary.name).resolve() + directory = self.root / ".github/ci" + directory.mkdir(parents=True) + (directory / "environment.json").write_text(json.dumps(CONFIGURATION), encoding="utf-8") + (directory / "vcpkg.json").write_text(json.dumps({"builtin-baseline": BASELINE}), encoding="utf-8") + (directory / "requirements-modules.txt").write_bytes(REQUIREMENTS) + (self.root / ".gitmodules").write_text('[submodule "extern/example"]\n', encoding="utf-8") + self.environment = {**ENVIRONMENT, "RUNNER_TEMP": str(self.root / "runner-temp"), + "CHERYL_CI_DEPENDENCY_CACHE_DIR": str(self.root / "build/cache/dependencies")} + + def metadata(self, *, gitlinks=b"160000 commit aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\textern/example\0", changes=None, + profile="headless"): + emissions = {} + + def emit(variable, values): + emissions.setdefault(variable, {}).update(values) + + with patch.object(dependencies, "ROOT", self.root), \ + patch.dict(dependencies.os.environ, {**self.environment, **(changes or {})}, clear=True), \ + patch.object(dependencies.subprocess, "check_output", return_value=gitlinks), \ + patch.object(dependencies, "run", return_value=".git/modules\n"), \ + patch.object(dependencies, "emit", side_effect=emit): + dependencies.metadata(profile) + self.emissions = emissions + return emissions["GITHUB_OUTPUT"] + + def test_submodules_independent(self): + original = self.metadata() + (self.root / ".gitmodules").write_text('[submodule "extern/changed"]\n', encoding="utf-8") + changed = self.metadata(gitlinks=b"160000 commit dddddddddddddddddddddddddddddddddddddddd\textern/changed\0") + self.assertNotEqual(original["submodule-key"], changed["submodule-key"]) + for name in Y_FIELDS: + self.assertEqual(original[name], changed[name], name) + + def test_branch_separation(self): + main = self.metadata() + develop = self.metadata(changes={"GITHUB_REF": "refs/heads/develop"}) + self.assertTrue(main["submodule-key"].startswith("build-deps-v2-Linux-X64-main-sources-")) + self.assertTrue(develop["submodule-key"].startswith("build-deps-v2-Linux-X64-develop-sources-")) + self.assertEqual(main["submodule-main-prefix"], develop["submodule-main-prefix"]) + for name in Y_FIELDS: + self.assertEqual(main[name], develop[name], name) + self.assertEqual(develop["dependency-cache-writer"], "true") + self.assertEqual(develop["environment-cache-writer"], "false") + + def test_pull_metadata(self): + for base in ("main", "develop"): + with self.subTest(base=base): + branch = self.metadata(changes={"GITHUB_REF": f"refs/heads/{base}"}) + pull = self.metadata(changes={"GITHUB_EVENT_NAME": "pull_request", "GITHUB_REF": "refs/pull/3/merge", + "GITHUB_BASE_REF": base}) + self.assertEqual(branch["submodule-key"], pull["submodule-key"]) + self.assertEqual(pull["dependency-cache-writer"], "false") + self.assertEqual(pull["environment-cache-writer"], "false") + + def test_manifest_dependencies(self): + original = self.metadata() + manifest = {"builtin-baseline": BASELINE, "dependencies": ["freetype", "harfbuzz"]} + (self.root / ".github/ci/vcpkg.json").write_text(json.dumps(manifest), encoding="utf-8") + changed = self.metadata() + self.assertEqual(original["submodule-key"], changed["submodule-key"]) + for name in Y_FIELDS: + self.assertEqual(original[name], changed[name], name) + + def test_windows_pool(self): + original = None + for profile in ("headless", "modules"): + for ref in ("refs/heads/main", "refs/heads/develop", "refs/pull/3/merge"): + with self.subTest(profile=profile, ref=ref): + metadata = self.metadata(profile=profile, changes={"RUNNER_OS": "Windows", "GITHUB_REF": ref}) + environment = self.emissions["GITHUB_ENV"] + pool = self.root / "build/cache/vcpkg" + installed = self.root / "build/ci/vcpkg_installed" + values = {name: environment[name] for name in ( + "CHERYL_CI_VCPKG_CACHE_DIR", "VCPKG_BINARY_SOURCES", "VCPKG_DOWNLOADS")} + self.assertEqual(values["CHERYL_CI_VCPKG_CACHE_DIR"], pool.as_posix()) + self.assertEqual(values["VCPKG_BINARY_SOURCES"], f"clear;files,{pool.as_posix()}/archives,readwrite") + self.assertEqual(values["VCPKG_DOWNLOADS"], (pool / "downloads").as_posix()) + self.assertTrue((pool / "archives").is_dir()) + self.assertTrue((pool / "downloads").is_dir()) + self.assertEqual(metadata["vcpkg-installed-dir"], installed.as_posix()) + self.assertEqual(environment["CHERYL_CI_VCPKG_INSTALLED_DIR"], installed.as_posix()) + self.assertNotIn("CHERYL_CI_DEPENDENCY_CACHE_KEY", environment) + self.assertTrue({"dependency-key", "dependency-exact-prefix", "dependency-profile-prefix", + "dependency-runner-prefix"}.isdisjoint(metadata)) + if original is None: + original = values + else: + self.assertEqual(original, values) + + def test_requirements_inputs(self): + original = self.metadata() + (self.root / ".github/ci/requirements-modules.txt").write_bytes(b"Jinja2==3.1.7\n") + changed = self.metadata() + self.assertNotEqual(original["pip-key"], changed["pip-key"]) + for name in ("environment-prefix", "tools-key", "submodule-key"): + self.assertEqual(original[name], changed[name], name) + + def test_environment_image(self): + original = self.metadata() + changed = self.metadata(changes={"ImageVersion": "20261013.1.0"}) + self.assertEqual(original["submodule-key"], changed["submodule-key"]) + for name in Y_FIELDS: + self.assertNotEqual(original[name], changed[name], name) + + def test_metadata_runs(self): + original = self.metadata() + changed = self.metadata(changes={"GITHUB_REF": "refs/heads/develop", "GITHUB_RUN_ID": "99999", + "GITHUB_RUN_ATTEMPT": "3"}) + for name in Y_FIELDS: + self.assertEqual(original[name], changed[name], name) + self.assertEqual(original["environment-cache-writer"], "true") + self.assertEqual(changed["environment-cache-writer"], "false") + + def test_apt_plan(self): + output = "Reading package lists...\n" + "\n".join(reversed(URIS)) + "\n" + with patch.object(dependencies, "ROOT", self.root), \ + patch.dict(dependencies.os.environ, self.environment, clear=True), \ + patch.object(dependencies, "run", return_value=output) as command, \ + patch.object(dependencies, "emit") as emit: + dependencies.apt_plan("headless") + self.assertIn("--print-uris", command.call_args.args[0]) + self.assertEqual(command.call_args.kwargs["environment"]["LC_ALL"], "C") + self.assertEqual(emit.call_args.args, ("GITHUB_OUTPUT", { + "needed": "true", "key": apt_key(environment=self.environment)})) + + def test_apt_empty(self): + with patch.object(dependencies, "ROOT", self.root), \ + patch.dict(dependencies.os.environ, self.environment, clear=True), \ + patch.object(dependencies, "run", return_value="No packages to download.\n"), \ + patch.object(dependencies, "emit") as emit: + dependencies.apt_plan("modules") + self.assertEqual(emit.call_args.args[1]["needed"], "false") + + def test_windows_install(self): + vcpkg = self.root / "vcpkg with spaces" + for profile in ("headless", "modules"): + with self.subTest(profile=profile), patch.object(dependencies, "ROOT", self.root), \ + patch.dict(dependencies.os.environ, {"CHERYL_CI_VCPKG_ROOT": str(vcpkg)}, clear=True), \ + patch.object(dependencies, "run") as command: + dependencies.windows_install(profile) + expected = [str(vcpkg / "vcpkg.exe"), "install", "--triplet=x64-windows", "--host-triplet=x64-windows", + f"--x-manifest-root={self.root.as_posix()}/.github/ci", + f"--x-install-root={self.root.as_posix()}/build/ci/vcpkg_installed", + "--x-no-default-features", "--x-feature=hwloc"] + if profile == "modules": + expected.append("--x-feature=native") + command.assert_called_once_with(expected, cwd=self.root) + + def test_windows_profile(self): + with patch.object(dependencies, "run") as command, self.assertRaises(ValueError): + dependencies.windows_install("docs") + command.assert_not_called() + + def test_linux_install(self): + environment = {"CHERYL_CI_VCPKG_ROOT": str(self.root / "vcpkg"), + "CHERYL_CI_VCPKG_INSTALLED_DIR": str(self.root / "installed")} + with patch.object(dependencies, "ROOT", self.root), \ + patch.dict(dependencies.os.environ, environment, clear=True), patch.object(dependencies, "run") as command: + dependencies.linux_install("modules") + arguments = command.call_args.args[0] + self.assertEqual(arguments[1], str(self.root / "tools/dependencies/prepare.py")) + self.assertNotIn("--native", arguments) + + def test_cache_does_not_select_packages(self): + cache = self.root / "build/cache/vcpkg/archives" + (cache / "aa").mkdir(parents=True) + (cache / "aa" / ("a" * 64 + ".zip")).write_bytes(b"older cached package") + manifest = {"builtin-baseline": BASELINE, "dependencies": ["freetype", "zlib"], + "features": {"hwloc": {"dependencies": ["hwloc"]}, "native": {"dependencies": ["hidapi"]}}} + manifest_path = self.root / ".github/ci/vcpkg.json" + manifest_path.write_text(json.dumps(manifest), encoding="utf-8") + with patch.object(dependencies, "ROOT", self.root), \ + patch.dict(dependencies.os.environ, { + "CHERYL_CI_VCPKG_ROOT": str(self.root / "vcpkg"), + "VCPKG_BINARY_SOURCES": f"clear;files,{cache.as_posix()},readwrite", + }, clear=True), patch.object(dependencies, "run") as command: + dependencies.windows_install("modules") + command.assert_called_once() + arguments = command.call_args.args[0] + self.assertEqual(arguments[1], "install") + self.assertIn(f"--x-manifest-root={manifest_path.parent.as_posix()}", arguments) + self.assertFalse(any(argument.startswith(("--only-binarycaching", "--no-downloads", "--only-downloads")) + for argument in arguments)) + self.assertEqual(json.loads(manifest_path.read_text(encoding="utf-8")), manifest) + + def test_windows_operation(self): + with patch("sys.argv", ["dependencies.py", "windows-install", "--profile", "modules"]), \ + patch.object(dependencies, "windows_install") as install: + dependencies.main() + install.assert_called_once_with("modules") + + +class WindowsToolsTests(unittest.TestCase): + def setUp(self): + output = redirect_stdout(io.StringIO()) + output.__enter__() + self.addCleanup(output.__exit__, None, None, None) + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + self.root = Path(temporary.name).resolve() / "checkout with spaces" + configuration = self.root / ".github/ci/environment.json" + configuration.parent.mkdir(parents=True) + configuration.write_text(json.dumps(CONFIGURATION), encoding="utf-8") + configuration.with_name("vcpkg.json").write_text(json.dumps({"builtin-baseline": BASELINE}), encoding="utf-8") + vcpkg = self.root / "vcpkg" + (vcpkg / ".git").mkdir(parents=True) + (vcpkg / "vcpkg.exe").write_bytes(b"vcpkg fixture") + self.tools = self.root / "cached tools" + chocolatey = self.root / "chocolatey" + self.package = chocolatey / "lib/ccache/tools/bin/ccache.exe" + self.package.parent.mkdir(parents=True) + self.package.write_bytes(b"ccache fixture") + self.github_path = self.root / "github-path.txt" + self.github_path.write_text("existing path\n", encoding="utf-8") + self.commands = [] + self.install_results = [] + self.version = CONFIGURATION["windows"]["ccache_version"] + self.install = ["choco", "install", "ccache", "--version=4.14.0", "--yes", "--no-progress"] + self.addCleanup(patch.stopall) + patch.object(dependencies, "ROOT", self.root).start() + patch.dict(dependencies.os.environ, { + "CHERYL_CI_VCPKG_ROOT": str(vcpkg), "CHERYL_CI_CCACHE_TOOLS": str(self.tools), + "ChocolateyInstall": str(chocolatey), "GITHUB_PATH": str(self.github_path), + }, clear=True).start() + patch.object(dependencies.subprocess, "run", side_effect=self.execute).start() + self.sleep = patch("time.sleep").start() + + def execute(self, command, **options): + self.commands.append(command) + if command == self.install: + result = self.install_results.pop(0) if self.install_results else 0 + return subprocess.CompletedProcess(command, result) + return subprocess.CompletedProcess(command, 0, stdout=f"ccache version {self.version}\n") + + def assert_published(self): + self.assertEqual((self.tools / "ccache.exe").read_bytes(), b"ccache fixture") + self.assertEqual(self.github_path.read_text(encoding="utf-8").splitlines(), ["existing path", str(self.tools)]) + + def test_install(self): + dependencies.windows_tools() + self.assertEqual(self.commands.count(self.install), 1) + self.sleep.assert_not_called() + self.assert_published() + + def test_retry_install(self): + self.install_results = [1, 1, 0] + dependencies.windows_tools() + self.assertEqual(self.commands.count(self.install), 3) + self.assertEqual([entry.args[0] for entry in self.sleep.call_args_list], [10, 20]) + self.assert_published() + + def test_install_failure(self): + self.install_results = [1, 1, 1] + with self.assertRaises(subprocess.CalledProcessError) as failure: + dependencies.windows_tools() + self.assertEqual(failure.exception.cmd, self.install) + self.assertEqual(self.commands.count(self.install), 3) + self.assertEqual([entry.args[0] for entry in self.sleep.call_args_list], [10, 20]) + self.assertFalse(self.tools.exists()) + self.assertEqual(self.github_path.read_text(encoding="utf-8"), "existing path\n") + + def test_cached(self): + self.tools.mkdir() + (self.tools / "ccache.exe").write_bytes(b"ccache fixture") + dependencies.windows_tools() + self.assertNotIn(self.install, self.commands) + self.sleep.assert_not_called() + self.assert_published() + + def test_wrong_version(self): + self.version = "unexpected" + with self.assertRaisesRegex(RuntimeError, "Unexpected ccache version"): + dependencies.windows_tools() + self.assertEqual(self.commands.count(self.install), 1) + self.sleep.assert_not_called() + self.assertEqual(self.github_path.read_text(encoding="utf-8"), "existing path\n") + + +class PreparedTests(unittest.TestCase): + def setUp(self): + output = redirect_stdout(io.StringIO()) + output.__enter__() + self.addCleanup(output.__exit__, None, None, None) + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + self.root = Path(temporary.name).resolve() / "checkout" + self.manifest = { + "builtin-baseline": BASELINE, + "dependencies": ["icu", {"name": "freetype", "default-features": False}, "harfbuzz"], + "features": {"hwloc": {"dependencies": ["hwloc"]}, "native": {"dependencies": ["hidapi"]}}, + } + self.manifest_path = self.root / ".github/ci/vcpkg.json" + self.manifest_path.parent.mkdir(parents=True) + self.manifest_path.write_text(json.dumps(self.manifest), encoding="utf-8") + self.installed = self.root / "build/ci/vcpkg_installed" + self.status = self.installed / "vcpkg/status" + self.status.parent.mkdir(parents=True) + self.status.write_text("\n\n".join(self.package(name) for name in ( + "icu", "freetype", "harfbuzz", "hwloc", "hidapi", "vcpkg-cmake", "vcpkg-cmake-config")) + "\n", + encoding="utf-8") + for directory, filename in (("include", "unicode/utypes.h"), ("lib", "icuuc.lib"), + ("bin", "icuuc.dll"), ("share", "icu/vcpkg_abi_info.txt"), + ("debug/lib", "icuucd.lib"), ("debug/bin", "icuucd.dll")): + file = self.installed / "x64-windows" / directory / filename + file.parent.mkdir(parents=True, exist_ok=True) + file.write_bytes(b"prepared dependency") + self.state_path = self.installed / dependencies.PREPARED_STATE + self.addCleanup(patch.stopall) + patch.object(dependencies, "ROOT", self.root).start() + patch.object(dependencies, "run", side_effect=AssertionError("Verification must not install dependencies")).start() + patch.dict(dependencies.os.environ, ENVIRONMENT, clear=True).start() + + @staticmethod + def package(name, *, triplet="x64-windows", status="install ok installed", feature=None): + fields = [f"Package: {name}", "Version: 1.2.3", f"Architecture: {triplet}", f"Status: {status}"] + if feature: + fields.append(f"Feature: {feature}") + return "\n".join(fields) + + def state(self, changes): + state = json.loads(self.state_path.read_text(encoding="utf-8")) + state.update(changes) + self.state_path.write_text(json.dumps(state), encoding="utf-8") + + def test_shared_installation(self): + dependencies.windows_stamp("modules") + original = self.state_path.read_bytes() + for profile in ("headless", "modules"): + with self.subTest(profile=profile): + dependencies.windows_verify(profile) + self.assertEqual(self.state_path.read_bytes(), original) + + def test_relocated(self): + dependencies.windows_stamp("modules") + relocated = self.root.parent / "consumer checkout" + shutil.copytree(self.root, relocated) + with patch.object(dependencies, "ROOT", relocated): + dependencies.windows_verify("modules") + + def test_linux_transfer(self): + (self.installed / "x64-windows").rename(self.installed / "x64-linux-dynamic") + self.status.write_text(self.status.read_text(encoding="utf-8").replace("x64-windows", "x64-linux-dynamic"), encoding="utf-8") + dependencies.native_stamp("modules", triplet=dependencies.LINUX_TRIPLET) + dependencies.native_verify("headless", triplet=dependencies.LINUX_TRIPLET) + state = json.loads(self.state_path.read_text(encoding="utf-8")) + self.assertEqual(state["target_triplet"], "x64-linux-dynamic") + self.assertEqual(state["features"], ["hwloc"]) + + def test_headless_subset(self): + self.status.write_text("\n\n".join(self.package(name) for name in ( + "icu", "freetype", "harfbuzz", "hwloc")), encoding="utf-8") + dependencies.windows_stamp("headless") + dependencies.windows_verify("headless") + with self.assertRaisesRegex(ValueError, "features"): + dependencies.windows_verify("modules") + + def test_manifest_change(self): + dependencies.windows_stamp("modules") + self.manifest["dependencies"].append("zlib") + self.manifest_path.write_text(json.dumps(self.manifest), encoding="utf-8") + with self.assertRaisesRegex(ValueError, "manifest"): + dependencies.windows_verify("modules") + + def test_new_package_installed(self): + self.manifest["dependencies"].append("zlib") + self.manifest_path.write_text(json.dumps(self.manifest), encoding="utf-8") + original = self.status.read_bytes() + + def complete_install(*arguments, **options): + self.assertIn(f"--x-manifest-root={self.manifest_path.parent.as_posix()}", arguments[0]) + with self.status.open("a", encoding="utf-8") as output: + output.write("\n" + self.package("zlib") + "\n") + (self.installed / "x64-windows/include/zlib.h").write_bytes(b"new dependency") + + with patch.dict(dependencies.os.environ, {"CHERYL_CI_VCPKG_ROOT": str(self.root / "vcpkg")}), \ + patch.object(dependencies, "run", side_effect=complete_install) as command: + dependencies.windows_install("modules") + command.assert_called_once() + dependencies.windows_stamp("modules") + dependencies.windows_verify("modules") + self.assertNotEqual(self.status.read_bytes(), original) + self.assertEqual(json.loads(self.state_path.read_text(encoding="utf-8"))["status_sha256"], + dependencies.digest(self.status.read_bytes())) + + def test_conditional_dependency(self): + self.manifest["dependencies"].append({"name": "linux-only", "platform": "linux"}) + self.manifest_path.write_text(json.dumps(self.manifest), encoding="utf-8") + dependencies.windows_stamp("modules") + dependencies.windows_verify("modules") + + def test_identity_mismatch(self): + for changes in ({"baseline": "c" * 40}, {"target_triplet": "x64-windows-static"}, + {"host_triplet": "arm64-windows"}, {"manifest_sha256": "a" * 64}): + with self.subTest(changes=changes): + dependencies.windows_stamp("modules") + self.state(changes) + with self.assertRaisesRegex(ValueError, "manifest or triplet"): + dependencies.windows_verify("modules") + + def test_source_identity(self): + dependencies.windows_stamp("modules") + for environment in ({"GITHUB_SHA": "c" * 40}, {"GITHUB_RUN_ID": "67890"}): + with self.subTest(environment=environment), patch.dict(dependencies.os.environ, environment): + with self.assertRaisesRegex(ValueError, "source revision, or workflow run"): + dependencies.windows_verify("modules") + with patch.dict(dependencies.os.environ, {"GITHUB_RUN_ATTEMPT": "3"}): + dependencies.windows_verify("modules") + + def test_invalid_source(self): + for environment in ({"GITHUB_SHA": ""}, {"GITHUB_SHA": "not-a-revision"}, {"GITHUB_RUN_ID": "0"}, + {"GITHUB_RUN_ID": "01"}, {"GITHUB_RUN_ID": "-1"}, {"GITHUB_RUN_ID": ""}): + with self.subTest(environment=environment), patch.dict(dependencies.os.environ, environment): + with self.assertRaisesRegex(ValueError, "source"): + dependencies.windows_stamp("modules") + + def test_metadata_schema(self): + for changes in ({"schema": 2}, {"schema": True}, {"extra": "unknown"}): + with self.subTest(changes=changes): + dependencies.windows_stamp("modules") + self.state(changes) + with self.assertRaisesRegex(ValueError, "metadata"): + dependencies.windows_verify("modules") + for state in ([], None, {"schema": 1}): + with self.subTest(state=state): + self.state_path.write_text(json.dumps(state), encoding="utf-8") + with self.assertRaisesRegex(ValueError, "metadata"): + dependencies.windows_verify("modules") + + def test_unknown_features(self): + for features in ([], ["hwloc", "native", "unknown"], "modules", None): + with self.subTest(features=features): + dependencies.windows_stamp("modules") + self.state({"features": features}) + with self.assertRaisesRegex(ValueError, "features"): + dependencies.windows_verify("headless") + + def test_missing_metadata(self): + with self.assertRaises(FileNotFoundError): + dependencies.windows_verify("modules") + + def test_missing_status(self): + self.status.unlink() + with self.assertRaises(FileNotFoundError): + dependencies.windows_stamp("modules") + self.assertFalse(self.state_path.exists()) + + def test_empty_tree(self): + for directory in ("include", "lib", "share", "debug/lib", "bin", "debug/bin"): + with self.subTest(directory=directory): + path = self.installed / "x64-windows" / directory + preserved = self.installed / "preserved" + path.rename(preserved) + path.mkdir() + with self.assertRaisesRegex(ValueError, "missing files"): + dependencies.windows_stamp("modules") + path.rmdir() + preserved.rename(path) + + def test_incomplete_packages(self): + original = self.status.read_text(encoding="utf-8") + for package in (self.package("hidapi", status="deinstall ok half-installed"), + self.package("hidapi", status="install ok half-installed", feature="extra")): + with self.subTest(package=package): + self.status.write_text(original.replace(self.package("hidapi"), package), encoding="utf-8") + with self.assertRaisesRegex(ValueError, "incomplete installed package: hidapi"): + dependencies.windows_stamp("modules") + self.assertFalse(self.state_path.exists()) + + def test_no_target_packages(self): + for status in ("", self.package("hidapi", triplet="arm64-windows"), + self.package("hidapi", feature="extra")): + with self.subTest(status=status): + self.status.write_text(status, encoding="utf-8") + with self.assertRaisesRegex(ValueError, "no installed core packages for x64-windows"): + dependencies.windows_stamp("modules") + + def test_status_changed(self): + dependencies.windows_stamp("modules") + with self.status.open("a", encoding="utf-8") as output: + output.write("\n" + self.package("unrelated-package") + "\n") + with self.assertRaisesRegex(ValueError, "status changed"): + dependencies.windows_verify("modules") + + def test_status_records(self): + original = self.status.read_text(encoding="utf-8") + self.status.write_bytes((original + "\n" + self.package("harfbuzz", feature="freetype") + "\n").replace( + "\n", "\r\n").encode()) + dependencies.windows_stamp("modules") + dependencies.windows_verify("modules") + + def test_hold_and_removed_packages(self): + original = self.status.read_text(encoding="utf-8") + for want in ("deinstall", "purge"): + with self.subTest(want=want): + status = original.replace(self.package("hidapi"), self.package("hidapi", status="hold ok installed")) + status += "\n" + self.package("old-package", status=f"{want} ok not-installed") + "\n" + status += "\n" + self.package("zlib", status="install ok installed") + "\n" + self.status.write_text(status, encoding="utf-8") + dependencies.windows_stamp("modules") + dependencies.windows_verify("modules") + installed = dependencies.installed_status(self.status.read_bytes()) + self.assertIn("hidapi", installed) + self.assertIn("zlib", installed) + self.assertNotIn("old-package", installed) + + def test_malformed_status_line(self): + original = self.status.read_text(encoding="utf-8") + for status in ("install", "unknown ok installed", "install error installed", "install ok unknown", + "install ok installed extra"): + with self.subTest(status=status): + self.status.write_text(original.replace(self.package("hidapi"), self.package("hidapi", status=status)), + encoding="utf-8") + with self.assertRaisesRegex(ValueError, "Malformed vcpkg installed package status: hidapi"): + dependencies.windows_stamp("modules") + + def test_invalid_status(self): + self.status.write_text(self.package("icu") + "\nPackage: duplicate\n", encoding="utf-8") + with self.assertRaisesRegex(ValueError, "Malformed"): + dependencies.windows_stamp("modules") + + def test_prepared_operations(self): + for operation, name in (("windows-stamp", "windows_stamp"), ("windows-verify", "windows_verify")): + with self.subTest(operation=operation), patch("sys.argv", ["dependencies.py", operation, "--profile", "modules"]), \ + patch.object(dependencies, name) as command: + dependencies.main() + command.assert_called_once_with("modules") + + +class PreparedCmakeTests(unittest.TestCase): + def test_harfbuzz_imports(self): + helper = Path(__file__).resolve().parents[2] / "tools/dependencies/prepared.cmake" + for debug in (False, True): + with self.subTest(debug=debug), tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + prefix = root / "installed/x64-windows" + for name in ("include/harfbuzz/hb.h", "lib/harfbuzz.lib", "bin/harfbuzz.dll"): + path = prefix / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(b"fixture") + if debug: + for name in ("debug/lib/harfbuzz.lib", "debug/bin/harfbuzz.dll"): + path = prefix / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(b"debug fixture") + (root / "CMakeLists.txt").write_text(f'''cmake_minimum_required(VERSION 3.28) +set(CMAKE_SYSTEM_NAME Windows) +project(cheryl LANGUAGES NONE) +set(CMAKE_FIND_LIBRARY_PREFIXES "") +set(CMAKE_FIND_LIBRARY_SUFFIXES .lib) +set(VCPKG_INSTALLED_DIR "{(root / 'installed').as_posix()}") +set(VCPKG_TARGET_TRIPLET x64-windows) +set(CHERYL_BUILD_WORKERS OFF) +set(CHERYL_NATIVE_INPUT OFF) +include("{helper.as_posix()}") +get_target_property(kind harfbuzz::harfbuzz TYPE) +get_target_property(import_library harfbuzz::harfbuzz IMPORTED_IMPLIB_RELEASE) +if(NOT kind STREQUAL "SHARED_LIBRARY" OR NOT import_library MATCHES "/lib/harfbuzz.lib$") + message(FATAL_ERROR "The prepared HarfBuzz DLL/import pair is incomplete") +endif() +file(GENERATE OUTPUT "${{CMAKE_BINARY_DIR}}/runtime.txt" CONTENT "$") +file(GENERATE OUTPUT "${{CMAKE_BINARY_DIR}}/link.txt" CONTENT "$") +''', encoding="utf-8") + for configuration in ("Release", "Debug", "RelWithDebInfo"): + build = root / configuration + result = subprocess.run(["cmake", "-S", str(root), "-B", str(build), "-G", "Ninja", + f"-DCMAKE_BUILD_TYPE={configuration}"], capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + directory = prefix / "debug" if debug and configuration == "Debug" else prefix + self.assertEqual(Path((build / "runtime.txt").read_text(encoding="utf-8")), directory / "bin/harfbuzz.dll") + self.assertEqual(Path((build / "link.txt").read_text(encoding="utf-8")), directory / "lib/harfbuzz.lib") + + def test_harfbuzz_supplied(self): + helper = Path(__file__).resolve().parents[2] / "tools/dependencies/prepared.cmake" + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + (root / "installed/x64-windows/lib").mkdir(parents=True) + (root / "CMakeLists.txt").write_text(f'''cmake_minimum_required(VERSION 3.28) +set(CMAKE_SYSTEM_NAME Windows) +project(cheryl LANGUAGES NONE) +set(VCPKG_INSTALLED_DIR "{(root / 'installed').as_posix()}") +set(VCPKG_TARGET_TRIPLET x64-windows) +set(CHERYL_BUILD_WORKERS OFF) +set(CHERYL_NATIVE_INPUT OFF) +add_library(harfbuzz INTERFACE) +set_property(TARGET harfbuzz PROPERTY SUPPLIED_MARKER preserved) +include("{helper.as_posix()}") +get_target_property(marker harfbuzz SUPPLIED_MARKER) +if(TARGET cheryl_prepared_harfbuzz OR NOT marker STREQUAL "preserved") + message(FATAL_ERROR "A supplied HarfBuzz target was replaced") +endif() +''', encoding="utf-8") + result = subprocess.run(["cmake", "-S", str(root), "-B", str(root / "build"), "-G", "Ninja"], + capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def test_shared_hwloc_closure(self): + for system in ("Linux", "Windows"): + with self.subTest(system=system): + self.check_hwloc_configurations(system, debug=False) + + def test_debug_hwloc_closure(self): + for system in ("Linux", "Windows"): + with self.subTest(system=system): + self.check_hwloc_configurations(system, debug=True) + + def test_windows_hwloc_debug_pair(self): + for debug in ("library", "runtime"): + with self.subTest(debug=debug): + self.check_hwloc_configurations("Windows", debug=debug) + + def test_windows_hwloc_names(self): + for runtime in ("hwloc-15.dll", "libhwloc-15.dll", "libhwloc.dll"): + with self.subTest(runtime=runtime): + self.check_hwloc_configurations("Windows", debug=True, runtime=runtime) + + def check_hwloc_configurations(self, system, *, debug, runtime=None): + helper = Path(__file__).resolve().parents[2] / "tools/dependencies/prepared.cmake" + windows = system == "Windows" + triplet = "x64-windows" if windows else "x64-linux-dynamic" + runtime = runtime or ("hwloc.dll" if windows else "libhwloc.so") + runtime_directory = "bin" if windows else "lib" + library = "hwloc.lib" if windows else "libhwloc.so" + complete_debug = debug is True + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + prefix = root / "installed" / triplet + (prefix / "include").mkdir(parents=True) + (prefix / "lib/pkgconfig").mkdir(parents=True) + (prefix / runtime_directory).mkdir(exist_ok=True) + (prefix / "include/hwloc.h").write_bytes(b"fixture") + (prefix / "lib/libhwloc.a").write_bytes(b"wrong static fixture") + (prefix / "lib" / library).write_bytes(b"shared link fixture") + (prefix / runtime_directory / runtime).write_bytes(b"shared runtime fixture") + (prefix / "lib/pkgconfig/hwloc.pc").write_text("Libs: -L${libdir} -lhwloc -lm\nLibs.private: -lpthread\n", encoding="utf-8") + if windows: + (prefix / "include/harfbuzz").mkdir() + (prefix / "include/harfbuzz/hb.h").write_bytes(b"fixture") + (prefix / "lib/harfbuzz.lib").write_bytes(b"import fixture") + (prefix / "bin/harfbuzz.dll").write_bytes(b"runtime fixture") + if complete_debug or debug == "library": + (prefix / "debug/lib").mkdir(parents=True) + (prefix / "debug/lib" / library).write_bytes(b"debug link fixture") + if complete_debug or debug == "runtime": + (prefix / "debug" / runtime_directory).mkdir(parents=True, exist_ok=True) + (prefix / "debug" / runtime_directory / runtime).write_bytes(b"debug runtime fixture") + (root / "CMakeLists.txt").write_text(f'''cmake_minimum_required(VERSION 3.28) +set(CMAKE_SYSTEM_NAME {system}) +project(cheryl LANGUAGES NONE) +set(CMAKE_FIND_LIBRARY_PREFIXES "{'' if windows else 'lib'}") +set(CMAKE_FIND_LIBRARY_SUFFIXES "{'.lib' if windows else '.so;.a'}") +set(VCPKG_INSTALLED_DIR "{(root / 'installed').as_posix()}") +set(VCPKG_TARGET_TRIPLET {triplet}) +set(CHERYL_NATIVE_INPUT OFF) +include("{helper.as_posix()}") +get_target_property(location cheryl_prepared_hwloc IMPORTED_LOCATION) +get_target_property(closure cheryl_prepared_hwloc INTERFACE_LINK_LIBRARIES) +get_target_property(configurations cheryl_prepared_hwloc IMPORTED_CONFIGURATIONS) +if(NOT location STREQUAL "{(prefix / runtime_directory / runtime).as_posix()}" OR closure) + message(FATAL_ERROR "Incorrect shared prepared closure: ${{location}} / ${{closure}}") +endif() +if(NOT configurations STREQUAL "{'RELEASE;DEBUG' if complete_debug else 'RELEASE'}") + message(FATAL_ERROR "Incorrect prepared configurations: ${{configurations}}") +endif() +file(GENERATE OUTPUT "${{CMAKE_BINARY_DIR}}/hwloc.txt" CONTENT "$") +file(GENERATE OUTPUT "${{CMAKE_BINARY_DIR}}/hwloc-link.txt" CONTENT "$") +''', encoding="utf-8") + self.check_generated_locations(root, prefix, debug=complete_debug, + runtime=f"{runtime_directory}/{runtime}", library=f"lib/{library}") + + def test_sdk_shared_export(self): + repository = Path(__file__).resolve().parents[2] + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + (root / "source.cpp").write_text("int fixture() { return 0; }\n", encoding="utf-8") + (root / "CMakeLists.txt").write_text(f'''cmake_minimum_required(VERSION 3.28) +project(sdk_export LANGUAGES CXX) +set(CHERYL_REPOSITORY_ROOT "{repository.as_posix()}") +set(CHERYL_BUILD_ROOT "${{CMAKE_BINARY_DIR}}") +set(CHERYL_BUILD_SHARED_LIBS ON) +set(VERSION_PROJECT_ENGINE 1.0.0) +set(VERSION_PROJECT_CHERYL 1.0.0) +set(TARGET_LIB_ENGINE engine) +set(TARGET_LIB_CHERYL cheryl) +set(TARGET_LIB_ENGINE_S_LOGGING_CONFIG logging) +set(CHERYL_LOG_PROFILE release) +set(CHERYL_LOG_COMPILED_MASK "") +add_library(engine SHARED source.cpp) +add_library(public_native SHARED source.cpp) +add_library(cheryl INTERFACE) +target_link_libraries(cheryl INTERFACE engine) +add_library(logging INTERFACE) +target_compile_definitions(logging INTERFACE CHERYL_LOG_PROFILE=2) +add_library(ctti INTERFACE) +add_library(glm INTERFACE) +add_library(glm::glm-header-only ALIAS glm) +add_library(harfbuzz::harfbuzz SHARED IMPORTED) +if(WIN32) + set_target_properties(harfbuzz::harfbuzz PROPERTIES + IMPORTED_LOCATION "${{CMAKE_SOURCE_DIR}}/harfbuzz.dll" + IMPORTED_IMPLIB "${{CMAKE_SOURCE_DIR}}/harfbuzz.lib") +else() + set_property(TARGET harfbuzz::harfbuzz PROPERTY IMPORTED_LOCATION "${{CMAKE_SOURCE_DIR}}/libharfbuzz.so") +endif() +set_property(GLOBAL PROPERTY CHERYL_SHARED_DEPENDENCIES "harfbuzz::harfbuzz|engine;public_native|engine") +target_link_libraries(engine PUBLIC public_native PRIVATE harfbuzz::harfbuzz) +include("{(repository / 'cmake/CherylSdk.cmake').as_posix()}") +file(GENERATE OUTPUT "${{CMAKE_BINARY_DIR}}/build-links.txt" + CONTENT "$>") +''', encoding="utf-8") + build = root / "build" + result = subprocess.run(["cmake", "-S", str(root), "-B", str(build), "-G", "Ninja", + "-DCMAKE_BUILD_TYPE=Release"], capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + links = (build / "build-links.txt").read_text(encoding="utf-8") + self.assertIn("harfbuzz::harfbuzz", links) + self.assertIn("public_native", links) + public_export = next((build / "CMakeFiles/Export").rglob("CherylTargets.cmake")).read_text(encoding="utf-8") + self.assertIn("Cheryl::Dependency_public_native", public_export) + self.assertNotIn("harfbuzz::harfbuzz", public_export) + prefix = root / "sdk" + package = prefix / "lib/cmake/Cheryl" + package.mkdir(parents=True) + for name in ("CherylConfig.cmake", "CherylConfigVersion.cmake", "CherylNativeTargets.cmake"): + shutil.copy2(build / name, package / name) + for export in (build / "CMakeFiles/Export").rglob("CherylTargets*.cmake"): + shutil.copy2(export, package / export.name) + for name in re.findall(r'(?m)^\s+IMPORTED_(?:LOCATION|IMPLIB)[A-Z_]* "\$\{_IMPORT_PREFIX\}/([^"]+)"', + export.read_text(encoding="utf-8")): + artifact = prefix / name + artifact.parent.mkdir(parents=True, exist_ok=True) + artifact.write_bytes(b"configured export fixture") + (prefix / "include/cheryl").mkdir(parents=True) + (prefix / "include/glm").mkdir() + consumer = root / "consumer" + consumer.mkdir() + (consumer / "source.cpp").write_text("int main() { return 0; }\n", encoding="utf-8") + (consumer / "CMakeLists.txt").write_text('''cmake_minimum_required(VERSION 3.28) +project(sdk_consumer LANGUAGES CXX) +find_package(Cheryl CONFIG REQUIRED) +add_executable(consumer source.cpp) +target_link_libraries(consumer PRIVATE Cheryl::Engine) +if(TARGET harfbuzz::harfbuzz OR TARGET Freetype::Freetype OR TARGET ICU::uc) + message(FATAL_ERROR "Shared Cheryl discovered a private native package") +endif() +''', encoding="utf-8") + result = subprocess.run(["cmake", "-S", str(consumer), "-B", str(root / "consumer-build"), "-G", "Ninja", + f"-DCMAKE_PREFIX_PATH={prefix}", "-DCMAKE_DISABLE_FIND_PACKAGE_harfbuzz=TRUE", + "-DCMAKE_DISABLE_FIND_PACKAGE_Freetype=TRUE", "-DCMAKE_DISABLE_FIND_PACKAGE_ICU=TRUE", + "-DCMAKE_DISABLE_FIND_PACKAGE_PkgConfig=TRUE"], capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + @unittest.skipUnless(sys.platform.startswith("linux"), "Linux native link inputs") + def test_sdk_static_native_links(self): + repository = Path(__file__).resolve().parents[2] + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + native = root / "native.v1+prefix" + native.mkdir() + for name in ("libdw.so", "libelf.so", "libhwloc.so", "libicuuc.so", "libpthread.a"): + (native / name).write_bytes(b"native link fixture") + (root / "source.cpp").write_text("int fixture() { return 0; }\n", encoding="utf-8") + (root / "CMakeLists.txt").write_text(f'''cmake_minimum_required(VERSION 3.28) +project(sdk_static_export LANGUAGES CXX) +include("{(repository / 'cmake/CherylLibraries.cmake').as_posix()}") +set(CHERYL_REPOSITORY_ROOT "{repository.as_posix()}") +set(CHERYL_BUILD_ROOT "${{CMAKE_BINARY_DIR}}") +set(CHERYL_BUILD_SHARED_LIBS OFF) +set(VERSION_PROJECT_ENGINE 1.0.0) +set(VERSION_PROJECT_CHERYL 1.0.0) +set(TARGET_LIB_ENGINE engine) +set(TARGET_LIB_CHERYL cheryl) +set(TARGET_LIB_ENGINE_S_LOGGING_CONFIG logging) +set(TARGET_LIB_ENGINE_S_SIGNAL_HANDLERS signals) +set(CHERYL_LOG_PROFILE release) +set(CHERYL_LOG_COMPILED_MASK "") +set(CHERYL_HWLOC_TARGET supplied_hwloc) +add_library(engine STATIC source.cpp) +add_library(cheryl INTERFACE) +target_link_libraries(cheryl INTERFACE engine) +foreach(target IN ITEMS logging signals ctti glm cheryl_icu backward) + add_library(${{target}} INTERFACE) +endforeach() +target_compile_definitions(logging INTERFACE CHERYL_LOG_PROFILE=2) +add_library(glm::glm-header-only ALIAS glm) +add_library(Threads::Threads INTERFACE IMPORTED) +add_library(Backward::Interface ALIAS backward) +target_link_libraries(backward INTERFACE "{native.as_posix()}/libdw.so" + "$<$:{native.as_posix()}/libelf.so>" + "$<$:{native.as_posix()}/libpthread.a>") +add_library(Cheryl::ICU ALIAS cheryl_icu) +add_library(ICU::uc SHARED IMPORTED) +set_property(TARGET ICU::uc PROPERTY IMPORTED_LOCATION "{native.as_posix()}/libicuuc.so") +target_link_libraries(cheryl_icu INTERFACE ICU::uc) +add_library(supplied_hwloc INTERFACE IMPORTED) +set_property(TARGET supplied_hwloc PROPERTY INTERFACE_LINK_LIBRARIES "{native.as_posix()}/libhwloc.so") +target_link_libraries(engine PRIVATE Backward::Interface Cheryl::ICU supplied_hwloc) +cheryl_shared_dependency(ICU::uc OWNER engine) +cheryl_shared_dependency(supplied_hwloc OWNER engine) +include("{(repository / 'cmake/CherylSdk.cmake').as_posix()}") +''', encoding="utf-8") + build = root / "build" + result = subprocess.run(["cmake", "-S", str(root), "-B", str(build), "-G", "Ninja", + "-DCMAKE_BUILD_TYPE=Release"], capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + exports = "\n".join(path.read_text(encoding="utf-8") + for path in (build / "CMakeFiles/Export").rglob("CherylTargets*.cmake")) + self.assertNotIn(native.as_posix(), exports) + self.assertNotIn("supplied_hwloc", exports) + self.assertNotIn("ICU::uc", exports) + self.assertIn("Threads::Threads", exports) + records = json.loads((build / "cheryl-sdk.json").read_text(encoding="utf-8"))["shared_runtime_dependencies"] + self.assertEqual({entry["artifact"] for entry in records}, {"libdw.so", "libelf.so", "libhwloc.so", "libicuuc.so"}) + for entry in records: + self.assertEqual(entry["deployment"], "sdk") + self.assertIn(entry["sdk_target"], exports) + + def test_sdk_native_headers(self): + helper = Path(__file__).resolve().parents[2] / "cmake/CherylSdkHeaders.cmake" + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + for name, content in { + "provider/include/TGUI/TGUI.hpp": "selected native API\n", + "provider/include/TGUI/Config.hpp": "source configuration\n", + "provider/generated/TGUI/Config.hpp": "generated native configuration\n", + "provider/include/Unrelated/private.h": "not a toolkit header\n", + }.items(): + path = root / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content, encoding="utf-8") + (root / "CMakeLists.txt").write_text(f'''cmake_minimum_required(VERSION 3.28) +project(sdk_headers LANGUAGES NONE) +set(CMAKE_INSTALL_INCLUDEDIR include) +include("{helper.as_posix()}") +add_library(provider INTERFACE IMPORTED) +set_property(TARGET provider PROPERTY INTERFACE_INCLUDE_DIRECTORIES + "$>;$;$") +add_library(toolkit INTERFACE) +target_link_libraries(toolkit INTERFACE provider) +add_library(TGUI::TGUI ALIAS toolkit) +cheryl_sdk_native_headers(TGUI::TGUI TGUI TGUI.hpp Config.hpp) +''', encoding="utf-8") + build = root / "build" + result = subprocess.run(["cmake", "-S", str(root), "-B", str(build), "-G", "Ninja"], + capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + result = subprocess.run(["cmake", "--install", str(build), "--prefix", str(root / "sdk"), + "--component", "CherylSDK"], capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + installed = root / "sdk/include" + self.assertEqual((installed / "TGUI/TGUI.hpp").read_text(encoding="utf-8"), "selected native API\n") + self.assertEqual((installed / "TGUI/Config.hpp").read_text(encoding="utf-8"), "generated native configuration\n") + self.assertFalse((installed / "Unrelated").exists()) + + def test_sdk_packaged_closure(self): + template = Path(__file__).resolve().parents[2] / "cmake/templates/CherylConfig.cmake.in" + for linkage, hwloc in (("static", True), ("static", False), ("shared", True)): + with self.subTest(linkage=linkage, hwloc=hwloc), tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + modules = root / "modules" + modules.mkdir() + (modules / "FindThreads.cmake").write_text('set(Threads_FOUND TRUE)\n', encoding="utf-8") + native_targets = ["Freetype", "HarfBuzz", "ICU"] + (["Hwloc"] if hwloc else []) + native_config = "".join(f"add_library(Cheryl::Native_{name} SHARED IMPORTED)\n" + for name in native_targets) + native_config = native_config.replace("\n", "\\n") + (root / "CMakeLists.txt").write_text(f'''cmake_minimum_required(VERSION 3.28) +project(sdk_fixture LANGUAGES NONE) +list(PREPEND CMAKE_MODULE_PATH "${{CMAKE_SOURCE_DIR}}/modules") +include(CMakePackageConfigHelpers) +set(sdk_linkage {linkage}) +set(sdk_hwloc {'ON' if hwloc else 'OFF'}) +set(sdk_runtime_present ON) +set(sdk_json OFF) +set(sdk_opengl OFF) +set(sdk_native_glfw OFF) +set(ICU_VERSION 78.1) +set(CMAKE_INSTALL_BINDIR bin) +set(CMAKE_INSTALL_LIBDIR lib) +configure_package_config_file("{template.as_posix()}" "${{CMAKE_SOURCE_DIR}}/sdk/lib/cmake/Cheryl/CherylConfig.cmake" + INSTALL_DESTINATION lib/cmake/Cheryl) +file(WRITE "${{CMAKE_SOURCE_DIR}}/sdk/lib/cmake/Cheryl/CherylTargets.cmake" "") +file(WRITE "${{CMAKE_SOURCE_DIR}}/sdk/lib/cmake/Cheryl/CherylNativeTargets.cmake" "{native_config}") +find_package(Cheryl REQUIRED CONFIG PATHS "${{CMAKE_SOURCE_DIR}}/sdk" NO_DEFAULT_PATH) +if(NOT TARGET Cheryl::Native_Freetype OR NOT TARGET Cheryl::Native_HarfBuzz OR NOT TARGET Cheryl::Native_ICU) + message(FATAL_ERROR "The packaged native runtime targets are missing") +endif() +if({'ON' if hwloc else 'OFF'} AND NOT TARGET Cheryl::Native_Hwloc) + message(FATAL_ERROR "Selected packaged hwloc was not loaded") +elseif(NOT {'ON' if hwloc else 'OFF'} AND TARGET Cheryl::Native_Hwloc) + message(FATAL_ERROR "An omitted hwloc provider was discovered") +endif() +if(TARGET Freetype::Freetype OR TARGET ICU::uc OR TARGET harfbuzz::harfbuzz OR TARGET PkgConfig::CHERYL_HWLOC) + message(FATAL_ERROR "The SDK discovered a private host dependency") +endif() +''', encoding="utf-8") + result = subprocess.run(["cmake", "-S", str(root), "-B", str(root / "build"), "-G", "Ninja", + "-DCMAKE_DISABLE_FIND_PACKAGE_Freetype=TRUE", "-DCMAKE_DISABLE_FIND_PACKAGE_ICU=TRUE", + "-DCMAKE_DISABLE_FIND_PACKAGE_harfbuzz=TRUE", "-DCMAKE_DISABLE_FIND_PACKAGE_PkgConfig=TRUE"], + capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + + def check_generated_locations(self, root, prefix, *, debug, runtime, library): + for configuration in ("Release", "Debug", "RelWithDebInfo"): + with self.subTest(configuration=configuration): + build = root / configuration + result = subprocess.run(["cmake", "-S", str(root), "-B", str(build), "-G", "Ninja", + f"-DCMAKE_BUILD_TYPE={configuration}"], capture_output=True, text=True) + self.assertEqual(result.returncode, 0, result.stdout + result.stderr) + directory = prefix / "debug" if debug and configuration == "Debug" else prefix + self.assertEqual(Path((build / "hwloc.txt").read_text(encoding="utf-8")), directory / runtime) + self.assertEqual(Path((build / "hwloc-link.txt").read_text(encoding="utf-8")), directory / library) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/ci/test_github_api.py b/.github/ci/test_github_api.py new file mode 100644 index 00000000..4dab4d4f --- /dev/null +++ b/.github/ci/test_github_api.py @@ -0,0 +1,73 @@ +"""Check workflow API routing and token handling without network access.""" + +import io +from pathlib import Path +import tempfile +import unittest +from unittest.mock import MagicMock +from urllib.error import HTTPError + +from github_api import GitHubApi, GitHubError + + +class ApiTests(unittest.TestCase): + def setUp(self): + self.token = "ghs_123_" + "a" * 250 + "." + "b" * 250 + ".signature" + self.api = GitHubApi("cppcooper/cheryl-engine", self.token) + self.api.opener = MagicMock() + + def respond(self, contents=b'{}'): + self.api.opener.open.return_value.__enter__.return_value.read.return_value = contents + + def test_token(self): + self.respond() + self.api.request("POST", "/releases", {"tag_name": "v4.0.0"}) + request = self.api.opener.open.call_args.args[0] + self.assertEqual(request.get_header("Authorization"), "Bearer " + self.token) + self.assertEqual(request.full_url, "https://api.github.com/repos/cppcooper/cheryl-engine/releases") + self.assertIn(b'v4.0.0', request.data) + + def test_paths(self): + for path in ("https://example.com", "//example.com", "/releases?token=x", "/../outside", "/bad\npath"): + with self.subTest(path=path), self.assertRaises(ValueError): + self.api.request("GET", path) + self.api.opener.open.assert_not_called() + + def test_missing(self): + self.api.opener.open.side_effect = HTTPError("https://api.github.com", 404, "missing", {}, io.BytesIO()) + self.assertIsNone(self.api.request("GET", "/git/ref/tags/v4.0.0", missing_ok=True)) + with self.assertRaises(GitHubError) as failure: + self.api.request("GET", "/releases") + self.assertEqual(failure.exception.status, 404) + self.assertNotIn(self.token, str(failure.exception)) + + def test_no_content(self): + self.respond(b'') + self.assertIsNone(self.api.request("DELETE", "/releases/assets/3")) + + def test_pages(self): + with unittest.mock.patch.object(self.api, "request", side_effect=[list(range(100)), [100]]) as request: + self.assertEqual(self.api.pages("/releases"), list(range(101))) + self.assertEqual(request.call_args_list[1].kwargs, {"per_page": 100, "page": 2}) + + def test_upload(self): + with tempfile.TemporaryDirectory() as temporary: + path = Path(temporary) / "SHA256SUMS" + path.write_bytes(b"checked") + self.respond(b'{"id": 1}') + self.assertEqual(self.api.upload("https://uploads.github.com/repos/cppcooper/cheryl-engine/releases/3/assets{?name,label}", path), {"id": 1}) + request = self.api.opener.open.call_args.args[0] + self.assertEqual(request.get_header("Content-length"), "7") + self.assertTrue(request.full_url.endswith("?name=SHA256SUMS")) + + def test_upload_scope(self): + for url in ("http://uploads.github.com/repos/cppcooper/cheryl-engine/releases/3/assets", + "https://uploads.github.com/repos/another/repo/releases/3/assets", + "https://example.com/repos/cppcooper/cheryl-engine/releases/3/assets"): + with self.subTest(url=url), self.assertRaises(ValueError): + self.api.upload(url, "/unused") + self.api.opener.open.assert_not_called() + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/ci/test_input_controls.py b/.github/ci/test_input_controls.py new file mode 100644 index 00000000..cdb75284 --- /dev/null +++ b/.github/ci/test_input_controls.py @@ -0,0 +1,58 @@ +"""Check control generation rejects unmapped native additions.""" + +import importlib.util +from pathlib import Path +import tempfile +import unittest +from unittest.mock import patch + + +script = Path(__file__).resolve().parents[2] / "tools/input/generate_controls.py" +spec = importlib.util.spec_from_file_location("input_controls", script) +controls = importlib.util.module_from_spec(spec) +spec.loader.exec_module(controls) + + +class ControlGenerationTests(unittest.TestCase): + def setUp(self): + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + root = Path(temporary.name) + self.keys = root / "glfw.h" + self.keys.write_text("#define GLFW_KEY_A 65\n#define GLFW_KEY_LEFT_CONTROL 341\n#define GLFW_KEY_F25 314\n") + self.portable = root / "controls.h" + self.portable.write_text("enum class KeyboardKey { Unknown, A, LeftControl, F25 };\n" + "enum class GamepadControl { Unknown, LeftStickX, A };\n") + (root / "GainputInputDeviceKeyboard.h").write_text("enum Key { KeyA, KeyCtrlL };") + self.pad = root / "GainputInputDevicePad.h" + self.pad.write_text("enum PadButton { PadButtonLeftStickX, PadButtonA, PadButtonMax_ };") + self.paths = patch.multiple(controls, CONTROLS=self.portable, GLFW=self.keys, GAINPUT=root) + self.paths.start() + self.addCleanup(self.paths.stop) + + def test_names(self): + generated = controls.generate() + self.assertIn("GLFW_KEY_LEFT_CONTROL, CE::Input::KeyboardKey::LeftControl, gainput::KeyCtrlL", generated) + self.assertIn("GLFW_KEY_F25, CE::Input::KeyboardKey::F25, gainput::InvalidDeviceButtonId", generated) + self.assertIn("gainput::PadButtonA, CE::Input::GamepadControl::A", generated) + + def test_new_key(self): + with self.keys.open("a") as stream: + stream.write("#define GLFW_KEY_B 66\n") + with self.assertRaisesRegex(ValueError, "GLFW keys need portable identities"): + controls.generate() + + def test_new_pad(self): + self.pad.write_text("enum PadButton { PadButtonLeftStickX, PadButtonA, PadButtonB, PadButtonMax_ };") + with self.assertRaisesRegex(ValueError, "Gainput controls need portable identities"): + controls.generate() + + def test_missing_key(self): + self.portable.write_text("enum class KeyboardKey { Unknown, B };\n" + "enum class GamepadControl { Unknown, LeftStickX, A };\n") + with self.assertRaisesRegex(ValueError, "No unique GLFW mapping"): + controls.generate() + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/ci/test_native_notices.py b/.github/ci/test_native_notices.py new file mode 100644 index 00000000..49d2264a --- /dev/null +++ b/.github/ci/test_native_notices.py @@ -0,0 +1,210 @@ +"""Check native notice selection without querying the host package database.""" + +import importlib.util +import json +from pathlib import Path +import subprocess +import tempfile +import unittest + + +spec = importlib.util.spec_from_file_location("native_notices", Path(__file__).with_name("native_notices.py")) +notices = importlib.util.module_from_spec(spec) +spec.loader.exec_module(notices) + + +class NoticeTests(unittest.TestCase): + def setUp(self): + self.temporary = tempfile.TemporaryDirectory() + self.addCleanup(self.temporary.cleanup) + self.root = Path(self.temporary.name).resolve() + self.system = self.root / "system" + self.destination = self.root / "sdk/share/licenses/native" + self.prefix = self.root / "provider" + self.owners = {} + self.licenses = {} + self.calls = [] + + def file(self, name, contents="notice\n"): + path = self.root / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(contents, encoding="utf-8") + return path + + def library(self, name="libsample.so.1", package="sample", manager="dpkg-query"): + library = self.file("system/usr/lib/" + name, "binary") + if package: + self.owners[manager, str(library)] = package + return library + + def query(self, command, **kwargs): + self.calls.append(command) + if command[:2] == ["pacman", "-Qi"]: + self.assertEqual(kwargs["env"]["LC_ALL"], "C") + licenses = self.licenses.get(command[-1]) + stdout = f"Name : {command[-1]}\nLicenses : {licenses}\nGroups : None\n" if licenses else "" + return subprocess.CompletedProcess(command, 0 if licenses else 1, stdout=stdout) + package = self.owners.get((command[0], command[-1])) + stdout = f"{package}: {command[-1]}\n" if command[0] == "dpkg-query" else f"{package}\n" + return subprocess.CompletedProcess(command, 0 if package else 1, stdout=stdout if package else "") + + def collect(self, libraries, **kwargs): + return notices.collect_notices(libraries, self.destination, system_root=self.system, run=self.query, + query_packages=True, **kwargs) + + def test_debian(self): + library = self.library(package="sample:amd64") + self.file("system/usr/share/doc/sample/copyright", "See /usr/share/common-licenses/LGPL-2.1.\n") + self.file("system/usr/share/common-licenses/LGPL-2.1", "LGPL text\n") + result = self.collect([library]) + self.assertEqual(result["libraries"][0]["package"], "sample:amd64") + self.assertEqual(result["libraries"][0]["notices"], ["sample/copyright", "sample/references/LGPL-2.1"]) + self.assertEqual((self.destination / "sample/references/LGPL-2.1").read_text(), "LGPL text\n") + self.assertNotIn(str(self.root), json.dumps(result)) + + def test_arch(self): + library = self.library(manager="pacman") + self.file("system/usr/share/licenses/sample/LICENSE", "license\n") + self.file("system/usr/share/licenses/sample/extra/NOTICE", "attribution\n") + result = self.collect([library]) + self.assertEqual(result["libraries"][0]["notices"], ["sample/licenses/LICENSE", "sample/licenses/extra/NOTICE"]) + self.assertTrue(any(command[0] == "pacman" for command in self.calls)) + self.assertFalse(any(command[:2] == ["pacman", "-Qi"] for command in self.calls)) + + def test_arch_common(self): + libraries = [self.library("libdw.so.1", package="libelf", manager="pacman"), + self.library("libelf.so.1", package="libelf", manager="pacman"), + self.library("libglib-2.0.so.0", package="glib2", manager="pacman")] + self.licenses = {"libelf": "GPL-2.0-or-later OR LGPL-3.0-or-later", "glib2": "LGPL-2.1-or-later"} + for name in ("GPL-2.0-or-later", "LGPL-3.0-or-later", "LGPL-2.1-or-later", "AGPL-3.0-only"): + self.file(f"system/usr/share/licenses/spdx/{name}.txt", name + " license\n") + result = self.collect(libraries) + records = {record["library"]: record for record in result["libraries"]} + for library in ("libdw.so.1", "libelf.so.1"): + self.assertEqual(records[library]["notices"], + ["libelf/licenses/GPL-2.0-or-later.txt", "libelf/licenses/LGPL-3.0-or-later.txt"]) + self.assertEqual(records[library]["package"], "libelf") + self.assertEqual(records[library]["origin"], "installed-package") + self.assertEqual(records["libglib-2.0.so.0"]["notices"], ["glib2/licenses/LGPL-2.1-or-later.txt"]) + self.assertEqual((self.destination / "glib2/licenses/LGPL-2.1-or-later.txt").read_text(), + "LGPL-2.1-or-later license\n") + self.assertEqual(len(list(self.destination.rglob("*.txt"))), 3) + self.assertNotIn(str(self.root), json.dumps(result)) + + def test_arch_expression(self): + library = self.library(manager="pacman") + self.licenses["sample"] = "(GPL-3.0-or-later WITH GCC-exception-3.1)\n AND LGPL-2.1-or-later" + names = ("GPL-3.0-or-later", "exceptions/GCC-exception-3.1", "LGPL-2.1-or-later") + for name in names: + self.file(f"system/usr/share/licenses/spdx/{name}.txt", name + " text\n") + result = self.collect([library]) + self.assertEqual(result["libraries"][0]["notices"], sorted(f"sample/licenses/{name}.txt" for name in names)) + self.assertEqual((self.destination / "sample/licenses/exceptions/GCC-exception-3.1.txt").read_text(), + "exceptions/GCC-exception-3.1 text\n") + + def test_arch_incomplete(self): + library = self.library(manager="pacman") + self.file("system/usr/share/licenses/spdx/GPL-3.0-or-later.txt") + for expression in ("GPL-3.0-or-later OR LicenseRef-Missing", "GPL-3.0-or-later WITH Missing-exception", + "GPL-3.0-or-later OR ../GPL-3.0-or-later"): + with self.subTest(expression=expression): + self.licenses["sample"] = expression + with self.assertRaisesRegex(ValueError, "No native notices"): + self.collect([library]) + self.assertFalse(self.destination.exists()) + + def test_relative(self): + library = self.library() + self.file("system/usr/share/doc/sample/copyright", "See COPYING\n") + self.file("system/usr/share/doc/sample/COPYING", "See copyright\n") + result = self.collect([library]) + self.assertEqual(result["libraries"][0]["notices"], ["sample/copyright", "sample/references/COPYING"]) + + def test_license_link_text(self): + library = self.library() + self.file("system/usr/share/doc/sample/copyright", "LICENSES/LGPL-2.1-or-later.txt\n") + self.file("system/usr/share/doc/sample/LICENSES/LGPL-2.1-or-later.txt", "LGPL text\n") + result = self.collect([library]) + self.assertEqual(result["libraries"][0]["notices"], + ["sample/copyright", "sample/references/LGPL-2.1-or-later.txt"]) + + def test_override(self): + library = self.library() + self.file("system/usr/share/doc/sample/copyright", "system copy\n") + self.file("provider/share/sample/LICENSE", "provider copy\n") + result = self.collect([library], native_prefix=self.prefix) + self.assertEqual(result["libraries"][0]["origin"], "native-prefix") + self.assertEqual((self.destination / "sample/LICENSE").read_text(), "provider copy\n") + self.assertFalse((self.destination / "sample/copyright").exists()) + + def test_unowned(self): + library = self.library("libicuuc.so.78", package=None) + self.file("provider/share/icu/copyright", "ICU notice\n") + self.file("provider/share/unrelated/LICENSE", "unrelated\n") + result = self.collect([library], native_prefix=self.prefix) + self.assertEqual(result["libraries"][0]["package"], "icu") + self.assertEqual(result["libraries"][0]["notices"], ["icu/copyright"]) + + def test_symlink_owner(self): + library = self.library("libsample.so.1.2") + link = library.with_name("libsample.so") + link.symlink_to(library.name) + self.file("system/usr/share/doc/sample/copyright") + result = self.collect([link]) + self.assertEqual(result["libraries"][0]["library"], "libsample.so") + self.assertEqual(result["libraries"][0]["package"], "sample") + + def test_missing(self): + library = self.library("libdw.so.1", package="libelf", manager="pacman") + self.file("provider/share/unrelated/LICENSE") + with self.assertRaisesRegex(ValueError, r"No native notices for libdw.so.1 \(libelf\).+share/libelf/"): + self.collect([library], native_prefix=self.prefix) + self.assertFalse(self.destination.exists()) + + def test_selection(self): + library = self.library() + self.file("provider/share/sample/README", "not a license\n") + with self.assertRaisesRegex(ValueError, "No native notices"): + self.collect([library], native_prefix=self.prefix) + + def test_owned(self): + libraries = [self.library(name) for name in ("libcheryl-engine.so.5", "libcengine.so", "libcmRmlUi.so", + "libcmNativeGLFW.so", "libglfw.so.3", "libtgui.so.1", "librmlui.so")] + result = self.collect(libraries) + self.assertEqual(result["libraries"], []) + self.assertEqual(result["already_in_sdk"], sorted(library.name for library in libraries)) + self.assertEqual(self.calls, []) + + def test_shared_package(self): + libraries = [self.library(name, package="libelf") for name in ("libdw.so.1", "libelf.so.1")] + self.file("system/usr/share/doc/libelf/copyright") + result = self.collect(libraries) + self.assertEqual(len(result["libraries"]), 2) + self.assertEqual(list(self.destination.rglob("copyright")), [self.destination / "libelf/copyright"]) + + def test_missing_manager(self): + library = self.library(package=None) + self.file("provider/share/sample/LICENSE") + + def unavailable(command, **kwargs): + raise FileNotFoundError(command[0]) + + result = notices.collect_notices([library], self.destination, native_prefix=self.prefix, + system_root=self.system, run=unavailable, query_packages=True) + self.assertEqual(result["libraries"][0]["package"], "sample") + + def test_windows_prefix(self): + library = self.library("hwloc-15.dll", package=None) + self.file("provider/share/hwloc/copyright", "hwloc notice\n") + + def unavailable(command, **kwargs): + self.fail("A Windows native-prefix collection must not query Linux package managers") + + result = notices.collect_notices([library], self.destination, native_prefix=self.prefix, + system_root=self.system, run=unavailable, query_packages=False) + self.assertEqual(result["libraries"][0]["package"], "hwloc") + self.assertEqual(result["libraries"][0]["notices"], ["hwloc/copyright"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/ci/test_release.py b/.github/ci/test_release.py new file mode 100644 index 00000000..fdba5789 --- /dev/null +++ b/.github/ci/test_release.py @@ -0,0 +1,699 @@ +"""Check release acceptance and retry behavior without network or compilation.""" + +from contextlib import redirect_stderr, redirect_stdout +import copy +import hashlib +import importlib.util +import io +import json +from pathlib import Path +import shutil +import subprocess +import tarfile +import tempfile +import unittest +from unittest.mock import patch +import zipfile + + +spec = importlib.util.spec_from_file_location("ci_release", Path(__file__).with_name("release.py")) +release = importlib.util.module_from_spec(spec) +spec.loader.exec_module(release) + +SHA = "a" * 40 +HEAD = "b" * 40 +OTHER = "c" * 40 +TAG = "v4.0.0" +VERSIONS = {variable: "1.0.0" for variable in release.COMPONENTS.values()} +VERSIONS.update({"VERSION_PROJECT_CHERYL": "4.0.0", "VERSION_PROJECT_ENGINE": "4.0.0"}) +VERSIONS.update({"VERSION_PROJECT_MODULE_STARTUP": "4.0.0", "VERSION_PROJECT_MODULE_WORKERS": "4.0.0"}) +ENVIRONMENT = {"GITHUB_ACTIONS": "true", "GITHUB_REPOSITORY": release.REPOSITORY, + "GITHUB_EVENT_NAME": "push", "GITHUB_REF": "refs/heads/main", "GITHUB_SHA": SHA} + + +class Clock: + def __init__(self): + self.value = 0 + + def now(self): + return self.value + + def sleep(self, seconds): + self.value += seconds + + +class FakeApi: + repository = release.REPOSITORY + + def __init__(self): + self.main = SHA + self.main_revisions = [] + self.parents = [{"sha": OTHER}, {"sha": HEAD}] + self.pulls = [{"number": 21, "title": "Release 4.0.0", "state": "closed", + "merged_at": "2026-10-15T12:00:00Z", "merge_commit_sha": SHA, + "head": {"ref": "develop", "sha": HEAD, "repo": {"full_name": self.repository}}, + "base": {"ref": "main", "repo": {"full_name": self.repository}}}] + self.runs = [{"id": 10, "head_sha": SHA, "head_branch": "main", "event": "push", + "head_repository": {"full_name": self.repository}, "status": "completed"}] + self.jobs = {10: [{"name": "Documentation checks", "status": "completed", "conclusion": "success"}]} + self.tag = None + self.annotated_tags = {} + self.release = None + self.assets = {} + self.writes = [] + self.queries = [] + self.uploaded = [] + self.fail_upload = None + self.leave_starter = False + self.release_on_get = None + self.asset_on_get = None + + def pages(self, path, field=None, **query): + self.queries.append((path, field, query)) + if path == f"/commits/{SHA}/pulls": + return copy.deepcopy(self.pulls) + if path == "/actions/workflows/documentation.yml/runs": + return copy.deepcopy(self.runs) + if path.startswith("/actions/runs/") and path.endswith("/jobs"): + return copy.deepcopy(self.jobs[int(path.split("/")[3])]) + if path == "/releases": + return [copy.deepcopy(self.release)] if self.release else [] + if path == "/releases/20/assets": + return copy.deepcopy(list(self.assets.values())) + raise AssertionError(f"Unexpected pages request: {path}") + + def request(self, method, path, body=None, **query): + if method != "GET": + self.writes.append((method, path, copy.deepcopy(body))) + if method == "GET" and path == "/git/ref/heads/main": + revision = self.main_revisions.pop(0) if self.main_revisions else self.main + return {"ref": "refs/heads/main", "object": {"type": "commit", "sha": revision}} + if method == "GET" and path == f"/commits/{SHA}": + return {"sha": SHA, "parents": copy.deepcopy(self.parents)} + if method == "GET" and path == f"/git/ref/tags/{TAG}": + return copy.deepcopy(self.tag) + if method == "GET" and path.startswith("/git/tags/"): + return copy.deepcopy(self.annotated_tags[path.rsplit("/", 1)[1]]) + if method == "GET" and path == "/releases/20": + return copy.deepcopy(self.release_on_get if self.release_on_get is not None else self.release) + if path.startswith("/releases/assets/"): + identifier = int(path.rsplit("/", 1)[1]) + name, asset = next((name, asset) for name, asset in self.assets.items() if asset["id"] == identifier) + if method == "GET": + return copy.deepcopy(self.asset_on_get if self.asset_on_get is not None else asset) + if method == "DELETE": + del self.assets[name] + return None + if method == "POST" and path == "/git/refs": + self.tag = {"ref": body["ref"], "object": {"type": "commit", "sha": body["sha"]}} + return copy.deepcopy(self.tag) + if method == "POST" and path == "/releases": + self.release = {"id": 20, **body, "upload_url": "https://uploads.github.com/repos/cppcooper/cheryl-engine/releases/20/assets{?name,label}"} + return copy.deepcopy(self.release) + if method == "PATCH" and path == "/releases/20": + self.release.update(body) + return copy.deepcopy(self.release) + raise AssertionError(f"Unexpected API request: {method} {path}") + + def upload(self, upload_url, path): + if self.fail_upload == len(self.uploaded) + 1: + self.fail_upload = None + if self.leave_starter: + self.assets[path.name] = {"id": len(self.assets) + 1, "name": path.name, + "state": "starter", "size": 0, "digest": None} + raise RuntimeError("Simulated upload failure") + data = path.read_bytes() + asset = {"id": len(self.assets) + 1, "name": path.name, "state": "uploaded", "size": len(data), + "digest": "sha256:" + hashlib.sha256(data).hexdigest()} + self.assets[path.name] = asset + self.uploaded.append((path.name, data)) + self.writes.append(("UPLOAD", path.name, None)) + return copy.deepcopy(asset) + + +class ReleaseTests(unittest.TestCase): + def setUp(self): + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + self.directory = Path(temporary.name) + self.candidates = self.directory / "candidates" + self.candidates.mkdir() + self.packages = self.directory / "packages" + self.api = FakeApi() + self.clock = Clock() + self.metadata = {} + for variant, fields in release.VARIANTS.items(): + metadata = dict(zip(("system", "linkage", "configuration", "compiler"), fields)) + metadata.update({"version": "4.0.0", "revision": SHA, "architecture": "AMD64", + "submodules": {"extern/example": HEAD}, + "features": {name: "ON" for name in release.FEATURES}, + "component_versions": {name: VERSIONS[release.COMPONENTS[name]] + for name in release.REQUIRED_COMPONENTS}}) + self.metadata[variant] = metadata + self.bundle(variant) + self.assemble() + + def snapshot(self, source, revision, destination): + identity = {"revision": revision, "submodules": {"extern/example": HEAD}} + with release._tar_writer(destination) as archive: + release._tar_bytes(archive, release._archive_root(destination) + "/SOURCE-MANIFEST.json", release._json_bytes(identity)) + release._tar_bytes(archive, release._archive_root(destination) + "/tracked.txt", b"pinned source\n") + return identity + + def assemble(self, *, local_validation=False): + if self.packages.exists(): + shutil.rmtree(self.packages) + with patch.object(release, "source_snapshot", side_effect=self.snapshot): + return release.assemble_release(self.candidates, self.directory, self.packages, SHA, VERSIONS, + local_validation=local_validation) + + def bundle(self, variant, *, inside=None, release_name=False): + directory = self.candidates / variant + directory.mkdir(exist_ok=True) + for path in directory.iterdir(): + path.unlink() + metadata = self.metadata[variant] + platform = metadata["system"].lower() + name = f"cheryl-4.0.0-sdk-{platform}-{metadata['linkage']}-{SHA[:12]}" + if release_name: + name = f"cheryl-SDK-v4.0.0-{platform}" + ("-static" if metadata["linkage"] == "static" else "") + contents = (json.dumps(metadata if inside is None else inside, sort_keys=True) + "\n").encode() + files = {"SDK-MANIFEST.json": contents, "include/TGUI/Config.hpp": platform.encode(), + "lib/cmake/Cheryl/CherylConfig.cmake": b"set(Cheryl_FOUND TRUE)\n", + "lib/cmake/Cheryl/CherylConfigVersion.cmake": b'set(PACKAGE_VERSION "4.0.0")\n'} + if metadata["system"] == "Windows": + path = directory / (name + ".zip") + with zipfile.ZipFile(path, "w") as archive: + for filename, data in files.items(): + archive.writestr(name + "/" + filename, data) + else: + path = directory / (name + ".tar.gz") + with tarfile.open(path, "w:gz") as archive: + for filename, data in files.items(): + member = tarfile.TarInfo(name + "/" + filename) + member.size = len(data) + archive.addfile(member, io.BytesIO(data)) + archive.addfile(release._tar_member(name + "/lib/libtest.so", link="libtest.so.1")) + release._tar_bytes(archive, name + "/lib/libtest.so.1", b"native library") + (directory / "SDK-MANIFEST.json").write_text(json.dumps(metadata), encoding="utf-8") + digest = hashlib.sha256(path.read_bytes()).hexdigest() + (directory / "SHA256SUMS").write_text(f"{digest} {path.name}\n", encoding="utf-8") + return path + + def publish(self): + return release.publish_release(self.api, self.packages, SHA, VERSIONS, + sleep=self.clock.sleep, monotonic=self.clock.now, docs_timeout=1) + + def rejected(self, message): + with self.assertRaisesRegex(RuntimeError, message): + self.publish() + self.assertEqual(self.api.writes, []) + + def candidate_rejected(self, message): + with self.assertRaisesRegex(RuntimeError, message): + self.assemble() + self.assertEqual(self.api.writes, []) + + def update_checksums(self): + (self.packages / "SHA256SUMS").write_text("".join( + f"{release._digest(path)} {path.name}\n" for path in sorted(self.packages.iterdir()) if path.name != "SHA256SUMS"), + encoding="utf-8") + + def test_publish(self): + self.assertEqual(self.publish(), "published") + self.assertEqual(self.api.tag["object"]["sha"], SHA) + self.assertFalse(self.api.release["draft"]) + self.assertEqual(len(self.api.uploaded), 6) + self.assertEqual(self.api.writes[-1], ("PATCH", "/releases/20", {"draft": False, "make_latest": "true"})) + creation = next(body for method, path, body in self.api.writes if path == "/releases") + self.assertTrue(creation["draft"]) + self.assertEqual(creation["target_commitish"], SHA) + self.assertTrue(creation["generate_release_notes"]) + uploaded = dict(self.api.uploaded) + aggregate = json.loads(uploaded["SDK-MANIFESTS.json"]) + self.assertEqual(aggregate["revision"], SHA) + self.assertEqual(len(aggregate["sdks"]), 4) + sums = uploaded["SHA256SUMS"].decode().splitlines() + self.assertEqual(len(sums), 5) + for line in sums: + digest, name = line.split(" ") + self.assertEqual(digest, hashlib.sha256(uploaded[name]).hexdigest()) + docs = next(query for path, field, query in self.api.queries if field == "workflow_runs") + self.assertEqual(docs, {"branch": "main", "event": "push", "head_sha": SHA}) + self.assertTrue(all(query == {"filter": "latest"} for path, field, query in self.api.queries if field == "jobs")) + + def test_not_release(self): + self.api.pulls = [] + shutil.rmtree(self.packages) + self.assertEqual(self.publish(), "not-release") + self.assertEqual(self.api.writes, []) + + def test_ordinary_title(self): + self.api.pulls[0]["title"] = "Updates the build" + self.assertEqual(self.publish(), "not-release") + self.assertEqual(self.api.writes, []) + + def test_version_title(self): + self.api.pulls[0]["title"] = "Release 4.1.0" + self.rejected("PR title") + + def test_fork(self): + self.api.pulls[0]["head"]["repo"]["full_name"] = "someone/cheryl-engine" + self.assertEqual(self.publish(), "not-release") + self.assertEqual(self.api.writes, []) + + def test_wrong_merge(self): + self.api.pulls[0]["merge_commit_sha"] = OTHER + self.assertEqual(self.publish(), "not-release") + self.assertEqual(self.api.writes, []) + + def test_stack_merge(self): + self.api.parents[1]["sha"] = OTHER + self.rejected("ordinary merge") + + def test_squash_merge(self): + self.api.parents = self.api.parents[:1] + self.rejected("ordinary merge") + + def test_stale(self): + self.api.main = OTHER + self.assertEqual(self.publish(), "stale") + self.assertEqual(self.api.writes, []) + + def test_main_advances(self): + self.api.main_revisions = [SHA, OTHER] + self.assertEqual(self.publish(), "stale") + self.assertEqual(self.api.writes, []) + + def test_missing_variant(self): + shutil.rmtree(self.candidates / "candidate-sdk-windows-modules") + self.candidate_rejected("four expected") + + def test_checksum(self): + path = self.bundle("candidate-sdk-linux-modules") + path.write_bytes(path.read_bytes() + b"changed") + self.candidate_rejected("checksum mismatch") + + def test_windows_checksum_lines(self): + checksum = self.candidates / "candidate-sdk-windows-modules/SHA256SUMS" + line = checksum.read_bytes().rstrip(b"\r\n") + for ending in (b"", b"\n", b"\r\n"): + with self.subTest(ending=ending): + checksum.write_bytes(line + ending) + self.assemble() + for ending in (b"\r", b"\r\r\n", b"\n\n", b"\r\nextra", b"\nextra"): + with self.subTest(ending=ending): + checksum.write_bytes(line + ending) + self.candidate_rejected("safe archive basename") + + def test_unsafe_name(self): + (self.candidates / "candidate-sdk-linux-modules/SHA256SUMS").write_text("a" * 64 + " ../outside.tar.gz\n", encoding="utf-8") + self.candidate_rejected("safe archive basename") + + def test_revision(self): + self.metadata["candidate-sdk-linux-modules"]["revision"] = OTHER + self.bundle("candidate-sdk-linux-modules") + self.candidate_rejected("SDK revision or version") + + def test_component(self): + self.metadata["candidate-sdk-linux-modules"]["component_versions"]["opengl"] = "2.0.0" + self.bundle("candidate-sdk-linux-modules") + self.candidate_rejected("component version") + + def test_missing_component(self): + del self.metadata["candidate-sdk-linux-modules"]["component_versions"]["native_glfw"] + self.bundle("candidate-sdk-linux-modules") + self.candidate_rejected("missing required component") + + def test_archive_manifest(self): + inside = copy.deepcopy(self.metadata["candidate-sdk-linux-modules"]) + inside["revision"] = OTHER + self.bundle("candidate-sdk-linux-modules", inside=inside) + self.candidate_rejected("outer manifest disagree") + + def test_four_archives(self): + names = release.release_names("4.0.0") + self.assertEqual({path.name for path in self.packages.iterdir()}, set(names.values()) | release.SIDECARS) + root = release._archive_root(self.packages / names["static"]) + with tarfile.open(self.packages / names["static"], "r:gz") as archive: + self.assertEqual(archive.extractfile(root + "/linux/include/TGUI/Config.hpp").read(), b"linux") + self.assertEqual(archive.extractfile(root + "/windows/include/TGUI/Config.hpp").read(), b"windows") + link = archive.getmember(root + "/linux/lib/libtest.so") + self.assertTrue(link.issym()) + self.assertEqual(link.linkname, "libtest.so.1") + dispatcher = archive.extractfile(root + "/lib/cmake/Cheryl/CherylConfig.cmake").read().decode() + self.assertIn('CMAKE_SYSTEM_NAME STREQUAL "Linux"', dispatcher) + self.assertIn('CMAKE_SYSTEM_NAME STREQUAL "Windows"', dispatcher) + self.assertIn("../../../${_cheryl_platform}/lib/cmake/Cheryl/CherylConfig.cmake", dispatcher) + aggregate = json.loads((self.packages / "SDK-MANIFESTS.json").read_bytes()) + self.assertEqual(aggregate["source"]["archive"], names["source"]) + self.assertEqual(aggregate["sdks"]["candidate-sdk-linux-modules"]["prefix"], "linux") + self.assertEqual(aggregate["sdks"]["candidate-sdk-windows-modules"]["prefix"], "windows") + + def test_deterministic_assembly(self): + before = {path.name: path.read_bytes() for path in self.packages.iterdir()} + self.assemble() + self.assertEqual(before, {path.name: path.read_bytes() for path in self.packages.iterdir()}) + + def test_shared_payload_identity(self): + inputs = [self.bundle(variant, release_name=True) for variant, fields in release.VARIANTS.items() + if fields[1] == "shared"] + self.assemble() + for path in inputs: + self.assertEqual(path.read_bytes(), (self.packages / path.name).read_bytes()) + + def test_local_configuration(self): + variant = "candidate-sdk-windows-modules" + self.metadata[variant]["configuration"] = "Release" + self.metadata[variant]["features"]["hwloc"] = "OFF" + self.bundle(variant) + self.candidate_rejected("accepted CI variant") + self.assemble(local_validation=True) + _, aggregate = release.validate_release(self.packages, SHA, VERSIONS, local_validation=True) + self.assertEqual(aggregate["sdks"][variant]["manifest"], self.metadata[variant]) + self.rejected("Local validation archives cannot be published") + + def test_missing_worker_version(self): + del self.metadata["candidate-sdk-linux-modules"]["component_versions"]["workers"] + self.bundle("candidate-sdk-linux-modules") + self.candidate_rejected("missing required component") + + def test_missing_hwloc(self): + self.metadata["candidate-sdk-linux-modules"]["features"]["hwloc"] = "OFF" + self.bundle("candidate-sdk-linux-modules") + self.candidate_rejected("required release features") + + def test_missing_opengl_startup(self): + self.metadata["candidate-sdk-linux-modules"]["features"]["startup_opengl"] = "OFF" + self.bundle("candidate-sdk-linux-modules") + self.candidate_rejected("required release features") + + def test_source_pins(self): + self.metadata["candidate-sdk-linux-modules"]["submodules"]["extern/example"] = OTHER + self.bundle("candidate-sdk-linux-modules") + self.candidate_rejected("submodule pins differ") + + def test_missing_source_identity(self): + del self.metadata["candidate-sdk-linux-modules"]["submodules"] + self.bundle("candidate-sdk-linux-modules") + self.candidate_rejected("source identity is invalid") + + def test_extra_release_file(self): + (self.packages / "unexpected.zip").write_bytes(b"extra") + self.rejected("exactly four named archives") + + def test_release_checksum(self): + name = release.release_names("4.0.0")["source"] + path = self.packages / name + path.write_bytes(path.read_bytes() + b"changed") + self.rejected("checksum mismatch") + + def test_duplicate_checksum(self): + path = self.packages / "SHA256SUMS" + path.write_bytes(path.read_bytes() + path.read_bytes().splitlines(keepends=True)[0]) + self.rejected("invalid or duplicate") + + def test_release_inner_manifest(self): + path = self.packages / "SDK-MANIFESTS.json" + aggregate = json.loads(path.read_bytes()) + aggregate["sdks"]["candidate-sdk-linux-modules"]["manifest"]["compiler_version"] = "different" + path.write_bytes(release._json_bytes(aggregate)) + self.update_checksums() + self.rejected("outer manifest disagree") + + def test_source_archive_manifest(self): + path = self.packages / release.release_names("4.0.0")["source"] + with release._tar_writer(path) as archive: + release._tar_bytes(archive, release._archive_root(path) + "/SOURCE-MANIFEST.json", + release._json_bytes({"revision": OTHER, "submodules": {"extern/example": HEAD}})) + self.update_checksums() + self.rejected("source archive and its release identity disagree") + + def test_unsafe_archive(self): + path = self.bundle("candidate-sdk-linux-modules") + for filename, link in (("../outside", None), ("lib/escape", "../../../outside"), + ("lib/escape", "/outside")): + with self.subTest(filename=filename, link=link): + with tarfile.open(path, "w:gz") as archive: + archive.addfile(release._tar_member(release._archive_root(path) + "/" + filename, link=link)) + checksum = path.parent / "SHA256SUMS" + checksum.write_text(f"{release._digest(path)} {path.name}\n", encoding="utf-8") + self.candidate_rejected("unsafe|escapes") + + def test_duplicate_archive_member(self): + path = self.bundle("candidate-sdk-linux-modules") + with tarfile.open(path, "w:gz") as archive: + for _ in range(2): + release._tar_bytes(archive, release._archive_root(path) + "/SDK-MANIFEST.json", b"{}") + (path.parent / "SHA256SUMS").write_text(f"{release._digest(path)} {path.name}\n", encoding="utf-8") + self.candidate_rejected("duplicate paths") + + def test_symlink_ancestor(self): + path = self.bundle("candidate-sdk-linux-modules") + root = release._archive_root(path) + with tarfile.open(path, "w:gz") as archive: + archive.addfile(release._tar_member(root + "/lib", link="other")) + release._tar_bytes(archive, root + "/lib/child", b"unsafe") + (path.parent / "SHA256SUMS").write_text(f"{release._digest(path)} {path.name}\n", encoding="utf-8") + self.candidate_rejected("traverses a symlink") + + def test_windows_paths(self): + path = self.bundle("candidate-sdk-windows-modules") + root = release._archive_root(path) + for files in (("include/A.h", "include/a.h"), ("include/NUL.txt",), ("include/path. ",)): + with self.subTest(files=files): + with zipfile.ZipFile(path, "w") as archive: + for name in files: + archive.writestr(root + "/" + name, b"unsafe") + (path.parent / "SHA256SUMS").write_text(f"{release._digest(path)} {path.name}\n", encoding="utf-8") + self.candidate_rejected("unsupported Windows paths") + + def test_no_overwrite(self): + marker = self.packages / "SDK-MANIFESTS.json" + before = marker.read_bytes() + with patch.object(release, "source_snapshot", side_effect=self.snapshot): + with self.assertRaisesRegex(RuntimeError, "empty real output"): + release.assemble_release(self.candidates, self.directory, self.packages, SHA, VERSIONS) + self.assertEqual(marker.read_bytes(), before) + + def test_docs_failure(self): + self.api.jobs[10][0]["conclusion"] = "failure" + self.rejected("Documentation checks did not succeed") + + def test_docs_wrong_sha(self): + self.api.runs[0]["head_sha"] = OTHER + self.rejected("Timed out") + + def test_docs_latest(self): + older = copy.deepcopy(self.api.runs[0]) + older["id"] = 9 + self.api.runs.append(older) + self.api.jobs[9] = copy.deepcopy(self.api.jobs[10]) + self.api.jobs[10][0]["conclusion"] = "failure" + self.rejected("Documentation checks did not succeed") + + def test_docs_stable_ignored(self): + self.api.jobs[10].append({"name": "Publish stable documentation ref", "status": "completed", "conclusion": "failure"}) + self.assertEqual(self.publish(), "published") + + def test_tag_conflict(self): + self.api.tag = {"ref": "refs/tags/" + TAG, "object": {"type": "commit", "sha": OTHER}} + self.rejected("different commit") + + def test_annotated_tag(self): + self.api.tag = {"ref": "refs/tags/" + TAG, "object": {"type": "tag", "sha": OTHER}} + self.api.annotated_tags[OTHER] = {"object": {"type": "commit", "sha": SHA}} + self.assertEqual(self.publish(), "published") + self.assertFalse(any(path == "/git/refs" for method, path, body in self.api.writes)) + + def test_published_retry(self): + self.publish() + self.api.writes.clear() + self.assertEqual(self.publish(), "unchanged") + self.assertEqual(self.api.writes, []) + + def test_asset_conflict(self): + self.publish() + self.api.writes.clear() + self.api.release["draft"] = True + self.api.assets["SDK-MANIFESTS.json"]["digest"] = "sha256:" + "0" * 64 + self.rejected("existing release asset differs") + + def test_published_missing_asset(self): + self.publish() + self.api.writes.clear() + del self.api.assets["SDK-MANIFESTS.json"] + self.rejected("complete checked SDK set") + + def test_resume_upload(self): + self.api.fail_upload = 2 + with self.assertRaisesRegex(RuntimeError, "Simulated upload failure"): + self.publish() + self.assertTrue(self.api.release["draft"]) + first = self.api.uploaded[0][0] + self.assertFalse(any(method == "PATCH" for method, path, body in self.api.writes)) + self.assertEqual(self.publish(), "published") + self.assertEqual(sum(name == first for name, data in self.api.uploaded), 1) + self.assertEqual(len(self.api.uploaded), 6) + + def failed_starter(self): + self.api.fail_upload = 2 + self.api.leave_starter = True + with self.assertRaisesRegex(RuntimeError, "Simulated upload failure"): + self.publish() + self.api.writes.clear() + return next(asset for asset in self.api.assets.values() if asset["state"] == "starter") + + def test_resume_starter(self): + starter = self.failed_starter() + first = self.api.uploaded[0][0] + self.assertEqual(self.publish(), "published") + self.assertIn(("DELETE", f"/releases/assets/{starter['id']}", None), self.api.writes) + self.assertEqual(sum(name == first for name, data in self.api.uploaded), 1) + self.assertEqual(sum(method == "DELETE" for method, path, body in self.api.writes), 1) + self.assertEqual(len(self.api.uploaded), 6) + self.assertTrue(all(asset["state"] == "uploaded" for asset in self.api.assets.values())) + + def test_nonempty_starter(self): + self.failed_starter()["size"] = 1 + self.rejected("existing release asset differs") + + def test_invalid_starter(self): + self.failed_starter()["id"] = 0 + self.rejected("existing release asset differs") + + def test_published_starter(self): + self.failed_starter() + self.api.release["draft"] = False + self.rejected("existing release asset differs") + + def test_starter_completed(self): + starter = self.failed_starter() + self.api.asset_on_get = {**starter, "state": "uploaded", "size": 100} + self.rejected("unfinished release asset changed") + + def test_draft_published(self): + self.failed_starter() + self.api.release_on_get = {**self.api.release, "draft": False} + self.rejected("no longer the matching stable draft") + + def test_starter_stale(self): + self.failed_starter() + self.api.main_revisions = [SHA, SHA, OTHER] + self.assertEqual(self.publish(), "stale") + self.assertEqual(self.api.writes, []) + + def test_starter_tag_changed(self): + self.failed_starter() + with patch.object(release, "_tag_revision", side_effect=[SHA, OTHER]): + self.rejected("tag changed before retrying") + + def test_published_prerelease(self): + self.publish() + self.api.writes.clear() + self.api.release["prerelease"] = True + self.rejected("stable prerelease setting") + + def test_draft_prerelease(self): + self.failed_starter() + self.api.release["prerelease"] = True + self.rejected("stable prerelease setting") + + def test_missing_prerelease(self): + self.failed_starter() + del self.api.release["prerelease"] + self.rejected("stable prerelease setting") + + def test_scope(self): + self.assertEqual(release.publication_revision(ENVIRONMENT), SHA) + for key, value in (("GITHUB_ACTIONS", "false"), ("GITHUB_REPOSITORY", "fork/cheryl-engine"), + ("GITHUB_REF", "refs/heads/develop"), ("GITHUB_EVENT_NAME", "pull_request"), + ("GITHUB_EVENT_NAME", "workflow_dispatch")): + with self.subTest(key=key, value=value): + self.assertIsNone(release.publication_revision({**ENVIRONMENT, key: value})) + with self.assertRaisesRegex(RuntimeError, "differs"): + release.publication_revision(ENVIRONMENT, OTHER) + + def test_disabled_cli(self): + output = io.StringIO() + with patch.dict(release.os.environ, {}, clear=True), redirect_stdout(output): + self.assertEqual(release.main(["--packages", str(self.packages)]), 0) + self.assertIn("disabled", output.getvalue()) + + def test_local_cli_scope(self): + with patch.dict(release.os.environ, {}, clear=True), redirect_stdout(io.StringIO()), redirect_stderr(io.StringIO()): + with self.assertRaises(SystemExit) as raised: + release.main(["--packages", str(self.packages), "--local-validation"]) + self.assertEqual(raised.exception.code, 2) + + +class SourceSnapshotTests(unittest.TestCase): + def setUp(self): + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + self.directory = Path(temporary.name) + self.source = self.directory / "source" + self.source.mkdir() + self.git(self.source, "init", "--quiet") + + def git(self, source, *arguments): + return subprocess.check_output(["git", "-C", str(source), "-c", "user.name=cppcooper", + "-c", "user.email=cppcooper@users.noreply.github.com", *arguments], + stderr=subprocess.PIPE).decode().strip() + + def commit(self, source, filename, contents): + path = source / filename + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(contents.encode("utf-8")) + self.git(source, "add", "--", filename) + self.git(source, "commit", "--quiet", "-m", "Adds fixture") + return self.git(source, "rev-parse", "HEAD") + + def test_pinned_recursive_objects(self): + child = self.source / "extern/child" + nested = child / "nested" + nested.mkdir(parents=True) + self.git(child, "init", "--quiet") + self.git(nested, "init", "--quiet") + nested_sha = self.commit(nested, "nested.txt", "nested pinned\n") + self.git(child, "update-index", "--add", "--cacheinfo", "160000," + nested_sha + ",nested") + child_sha = self.commit(child, "child.txt", "child pinned\n") + self.git(self.source, "update-index", "--add", "--cacheinfo", "160000," + child_sha + ",extern/child") + sha = self.commit(self.source, "tracked.txt", "root pinned\n") + (self.source / "tracked.txt").write_text("uncommitted change\n", encoding="utf-8") + (child / "child.txt").write_text("generated replacement\n", encoding="utf-8") + (self.source / "TRANSIENT-HANDOFF.md").write_text("private handoff\n", encoding="utf-8") + destination = self.directory / "cheryl-src-v4.0.0.tar.gz" + identity = release.source_snapshot(self.source, sha, destination) + self.assertEqual(identity, {"revision": sha, "submodules": {"extern/child": child_sha, + "extern/child/nested": nested_sha}}) + with tarfile.open(destination, "r:gz") as archive: + root = release._archive_root(destination) + self.assertEqual(archive.extractfile(root + "/tracked.txt").read(), b"root pinned\n") + self.assertEqual(archive.extractfile(root + "/extern/child/child.txt").read(), b"child pinned\n") + self.assertEqual(archive.extractfile(root + "/extern/child/nested/nested.txt").read(), b"nested pinned\n") + self.assertNotIn(root + "/TRANSIENT-HANDOFF.md", archive.getnames()) + self.assertFalse(any(".git/" in name for name in archive.getnames())) + first = destination.read_bytes() + release.source_snapshot(self.source, sha, destination) + self.assertEqual(first, destination.read_bytes()) + + def test_wrong_revision(self): + self.commit(self.source, "tracked.txt", "pinned\n") + with self.assertRaisesRegex(RuntimeError, "pinned revision"): + release.source_snapshot(self.source, SHA, self.directory / "source.tar.gz") + + def test_wrong_submodule(self): + child = self.source / "child" + child.mkdir() + self.git(child, "init", "--quiet") + pin = self.commit(child, "tracked.txt", "pinned\n") + self.git(self.source, "update-index", "--add", "--cacheinfo", "160000," + pin + ",child") + sha = self.commit(self.source, "tracked.txt", "root\n") + self.commit(child, "tracked.txt", "different commit\n") + with self.assertRaisesRegex(RuntimeError, "pinned revision"): + release.source_snapshot(self.source, sha, self.directory / "source.tar.gz") + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/ci/test_vcpkg_cache.py b/.github/ci/test_vcpkg_cache.py new file mode 100644 index 00000000..3e4f38b4 --- /dev/null +++ b/.github/ci/test_vcpkg_cache.py @@ -0,0 +1,420 @@ +"""Check global vcpkg package contributions, observed usage and bounded merging.""" + +import hashlib +import importlib.util +import io +import json +import os +from pathlib import Path +import tarfile +import tempfile +import unittest +from unittest.mock import patch + + +spec = importlib.util.spec_from_file_location("vcpkg_cache", Path(__file__).with_name("vcpkg_cache.py")) +cache = importlib.util.module_from_spec(spec) +spec.loader.exec_module(cache) +ABI_A = "ab" + "1" * 62 +ABI_B = "cd" + "2" * 62 +KEY_A = f"archives/ab/{ABI_A}.zip" +KEY_B = f"archives/cd/{ABI_B}.zip" +DOWNLOAD = "downloads/icu4c-78_3-src.tgz" +ENVIRONMENT = {"GITHUB_RUN_ID": "12345", "GITHUB_RUN_ATTEMPT": "2"} + + +def write(directory, name, data): + path = directory / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(data.encode("utf-8") if isinstance(data, str) else data) + return path + + +def record(data, *, day=None): + result = {"sha256": hashlib.sha256(data).hexdigest(), "size": len(data)} + if day is not None: + result["last_used"] = day + return result + + +def status(abi, *, name="icu", state="install ok installed", feature=None): + fields = f"Package: {name}\nVersion: 78.3\nArchitecture: x64-windows\nMulti-Arch: same\n" + if feature: + fields += f"Feature: {feature}\n" + return fields + f"Abi: {abi}\nStatus: {state}\n\n" + + +def snapshot(directory): + return {name: (path.read_bytes(), path.stat().st_mtime_ns) for name, path in cache._files(directory).items()} + + +def contribution(directory, name, files, *, used=(), manifest=None, additions=()): + if manifest is None: + manifest = {"schema": 1, "files": {key: record(value) for key, value in files.items()}, "used": list(used)} + encoded = manifest if isinstance(manifest, bytes) else json.dumps(manifest).encode() + path = directory / name / "contribution.tar" + path.parent.mkdir(parents=True, exist_ok=True) + with tarfile.open(path, "w", format=tarfile.PAX_FORMAT) as archive: + for key, data in [(cache.MANIFEST_NAME, encoded), *files.items()]: + member = tarfile.TarInfo(key) + member.size = len(data) + archive.addfile(member, io.BytesIO(data)) + for member in additions: + archive.addfile(member) + return path + + +class PoolTests(unittest.TestCase): + def setUp(self): + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + self.root = Path(temporary.name).resolve() + self.pool = self.root / "build/cache/vcpkg" + self.installed = self.root / "build/ci/vcpkg_installed" + self.contributions = self.root / "contributions" + self.output = self.root / "build/cache/vcpkg-artifacts/contribution.tar" + + def seed(self, files, *, day=1): + for name, data in files.items(): + write(self.pool, name, data) + index = {"schema": 1, "files": {name: record(data, day=day) for name, data in files.items()}} + write(self.pool, cache.INDEX_NAME, cache._encode(index)) + return index + + def pack(self, text=None): + write(self.installed, "vcpkg/status", status(ABI_A) if text is None else text) + return cache.pack(self.installed, root=self.root) + + def merge(self, *, day=10, limit=cache.DEFAULT_LIMIT): + return cache.merge(self.contributions, root=self.root, now=day * 86400 + 123, limit_bytes=limit) + + def stage_output(self, name="worker"): + target = self.contributions / name / "contribution.tar" + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(self.output.read_bytes()) + + def test_describe(self): + values = cache.describe(environment=ENVIRONMENT, root=self.root) + self.assertEqual(values["cache-prefix"], "vcpkg-v1-Windows-X64-msvc-") + self.assertEqual(values["cache-key"], "vcpkg-v1-Windows-X64-msvc-12345-2") + self.assertEqual(Path(values["cache-path"]), self.pool) + self.assertEqual(Path(values["contribution-path"]), self.output) + other = cache.describe(environment={**ENVIRONMENT, "GITHUB_REF": "refs/pull/42/merge", + "ImageVersion": "different", "DEPENDENCY_PROFILE": "modules"}, root=self.root) + self.assertEqual(other, values) + + def test_run_identity(self): + for name, value in (("GITHUB_RUN_ID", "0"), ("GITHUB_RUN_ID", "../escape"), + ("GITHUB_RUN_ATTEMPT", ""), ("GITHUB_RUN_ATTEMPT", "retry")): + with self.subTest(name=name), self.assertRaises(ValueError): + cache.describe(environment={**ENVIRONMENT, name: value}, root=self.root) + + def test_status(self): + text = (status(ABI_A) + status("", feature="tools") + + status(ABI_B, name="hwloc", state="hold ok installed") + + status("a" * 64, state="deinstall ok not-installed") + + status("", name="uncacheable")) + self.assertEqual(cache.parse_status(text), [KEY_A, KEY_B]) + + def test_status_fields(self): + text = status(ABI_A).replace("Status:", "Description: A description\n with a continuation\nStatus:") + self.assertEqual(cache.parse_status(text), [KEY_A]) + for value in (status("not-an-abi"), status(ABI_A) + " invalid continuation\n", + status(ABI_A).replace("Abi:", f"Abi: {ABI_B}\nAbi:"), "\0"): + with self.subTest(value=value), self.assertRaises(ValueError): + cache.parse_status(value) + + def test_pack_changed(self): + self.seed({KEY_A: b"existing ZIP", DOWNLOAD: b"old download"}) + write(self.pool, KEY_B, b"new ZIP") + write(self.pool, DOWNLOAD, b"new download") + before = snapshot(self.pool) + result = self.pack(status(ABI_A) + status(ABI_B, name="hwloc")) + self.assertEqual(result, {"has-content": "true", "files": "2", "used": "2"}) + with tarfile.open(self.output, "r:") as archive: + self.assertEqual(set(archive.getnames()), {cache.MANIFEST_NAME, KEY_B, DOWNLOAD}) + with archive.extractfile(cache.MANIFEST_NAME) as source: + manifest = json.load(source) + self.assertEqual(manifest["used"], [KEY_A, KEY_B]) + self.assertEqual(set(manifest), {"schema", "files", "used"}) + self.assertEqual(snapshot(self.pool), before) + + def test_pack_usage(self): + self.seed({KEY_A: b"ZIP"}) + before = snapshot(self.pool) + result = self.pack() + self.assertEqual(result, {"has-content": "true", "files": "0", "used": "1"}) + with tarfile.open(self.output, "r:") as archive: + self.assertEqual(archive.getnames(), [cache.MANIFEST_NAME]) + self.assertEqual(snapshot(self.pool), before) + + def test_pack_empty(self): + write(self.output.parent, self.output.name, b"stale contribution") + self.assertEqual(self.pack("")["has-content"], "false") + self.assertFalse(self.output.exists()) + + def test_pack_missing_status(self): + with self.assertRaises(ValueError): + cache.pack(self.installed, root=self.root) + + def test_pack_bound(self): + write(self.pool, DOWNLOAD, b"12345") + write(self.installed, "vcpkg/status", "") + with patch.object(cache, "CONTRIBUTION_LIMIT", 4), self.assertRaises(ValueError): + cache.pack(self.installed, root=self.root) + self.assertFalse(self.output.exists()) + + def test_merge_union(self): + self.seed({KEY_A: b"old ZIP"}) + contribution(self.contributions, "a-worker", {KEY_B: b"new ZIP"}, used=[KEY_B]) + contribution(self.contributions, "z-worker", {DOWNLOAD: b"source archive"}, used=[KEY_A]) + result = self.merge() + self.assertEqual(result["changed"], "true") + self.assertEqual(result["files"], "3") + self.assertEqual((self.pool / KEY_A).read_bytes(), b"old ZIP") + self.assertEqual((self.pool / KEY_B).read_bytes(), b"new ZIP") + self.assertEqual((self.pool / DOWNLOAD).read_bytes(), b"source archive") + index = cache.read_index(self.pool) + self.assertTrue(all(value["last_used"] == 10 for value in index["files"].values())) + actual = sum(path.stat().st_size for path in cache._files(self.pool).values()) + self.assertEqual(int(result["bytes"]), actual) + + def test_zip_identity(self): + self.seed({KEY_A: b"first serialization"}) + contribution(self.contributions, "worker", {KEY_A: b"another serialization"}, used=[KEY_A]) + self.merge() + self.assertEqual((self.pool / KEY_A).read_bytes(), b"first serialization") + self.assertEqual(cache.read_index(self.pool)["files"][KEY_A]["last_used"], 10) + + def test_zip_order(self): + contribution(self.contributions, "z-worker", {KEY_A: b"z serialization"}, used=[KEY_A]) + contribution(self.contributions, "a-worker", {KEY_A: b"a serialization"}, used=[KEY_A]) + self.merge() + self.assertEqual((self.pool / KEY_A).read_bytes(), b"a serialization") + + def test_download_replace(self): + self.seed({DOWNLOAD: b"old"}) + contribution(self.contributions, "worker", {DOWNLOAD: b"new"}) + self.merge() + self.assertEqual((self.pool / DOWNLOAD).read_bytes(), b"new") + + def test_download_revert(self): + self.seed({DOWNLOAD: b"original"}) + contribution(self.contributions, "a-worker", {DOWNLOAD: b"other"}) + contribution(self.contributions, "z-worker", {DOWNLOAD: b"original"}) + self.merge() + self.assertEqual((self.pool / DOWNLOAD).read_bytes(), b"original") + + def test_usage_checkpoint(self): + self.seed({KEY_A: b"ZIP"}, day=10) + contribution(self.contributions, "worker", {}, used=[KEY_A]) + before = snapshot(self.pool) + self.assertEqual(self.merge()["changed"], "false") + self.assertEqual(snapshot(self.pool), before) + self.assertEqual(self.merge(day=11)["changed"], "true") + self.assertEqual(cache.read_index(self.pool)["files"][KEY_A]["last_used"], 11) + + def test_unknown_usage(self): + self.seed({KEY_A: b"ZIP"}, day=10) + contribution(self.contributions, "worker", {}, used=[KEY_B]) + self.assertEqual(self.merge()["changed"], "false") + self.assertEqual(set(cache.inventory(self.pool)), {KEY_A}) + + def test_roundtrip(self): + write(self.pool, KEY_A, b"new ZIP") + write(self.pool, DOWNLOAD, b"download") + self.pack() + self.stage_output() + publisher = self.root / "publisher" + result = cache.merge(self.contributions, root=publisher, now=10 * 86400) + self.assertEqual(result["changed"], "true") + self.assertEqual(cache.inventory(publisher / "build/cache/vcpkg"), cache.inventory(self.pool)) + + def test_lru(self): + self.seed({KEY_A: b"a" * 1024, KEY_B: b"b" * 1024}) + contribution(self.contributions, "worker", {}, used=[KEY_A]) + result = self.merge(limit=1700) + self.assertEqual(result["removed"], "1") + self.assertLessEqual(int(result["bytes"]), 1700) + self.assertEqual(set(cache.inventory(self.pool)), {KEY_A}) + + def test_lru_clock(self): + index = self.seed({KEY_A: b"a" * 1024, KEY_B: b"b" * 1024}) + index["files"][KEY_A]["last_used"] = 2 + write(self.pool, cache.INDEX_NAME, cache._encode(index)) + os.utime(self.pool / KEY_B, (2_000_000_000, 2_000_000_000)) + os.utime(self.pool / KEY_A, (100, 100)) + self.merge(limit=1700) + self.assertEqual(set(cache.inventory(self.pool)), {KEY_A}) + + def test_trim_oversized(self): + self.seed({KEY_A: b"a" * 100, KEY_B: b"b" * 2000}) + contribution(self.contributions, "worker", {}, used=[KEY_B]) + self.merge(limit=600) + self.assertEqual(set(cache.inventory(self.pool)), {KEY_A}) + + def test_preserve_last(self): + self.seed({KEY_A: b"a" * 1024}) + before = snapshot(self.pool) + with self.assertRaises(ValueError): + self.merge(limit=100) + self.assertEqual(snapshot(self.pool), before) + + def test_index_integrity(self): + self.seed({KEY_A: b"ZIP"}) + write(self.pool, KEY_A, b"changed") + before = snapshot(self.pool) + with self.assertRaises(ValueError): + self.merge() + self.assertEqual(snapshot(self.pool), before) + + def test_index_schema(self): + for value in ({"schema": 2, "files": {}}, {"schema": True, "files": {}}, + {"schema": 1, "files": {}, "token": "secret"}, + {"schema": 1, "files": {KEY_A: record(b"ZIP", day=-1)}}): + with self.subTest(value=value): + write(self.pool, cache.INDEX_NAME, json.dumps(value).encode()) + with self.assertRaises(ValueError): + cache.read_index(self.pool) + + def test_native_paths(self): + paths = (f"archives/ff/{ABI_A}.zip", f"archives/ab/{'1' * 40}.zip", + "downloads/../credentials", "downloads/.git-credentials", "downloads/.ssh/id_rsa", + "downloads/CON.txt", "downloads/a\\b", "downloads/file.", "other/file") + for name in paths: + with self.subTest(name=name), self.assertRaises(ValueError): + cache._payload_path(name) + + def test_inventory_paths(self): + # Native filesystems can normalize separators and trailing dots before + # inventory sees them. Keep raw-spelling checks above and in TAR inputs. + paths = (f"archives/ff/{ABI_A}.zip", f"archives/ab/{'1' * 40}.zip", + "downloads/.git-credentials", "downloads/.ssh/id_rsa", "other/file") + for index, name in enumerate(paths): + with self.subTest(name=name): + directory = self.root / str(index) + write(directory, name, b"unexpected") + with self.assertRaises(ValueError): + cache.inventory(directory) + + def test_contribution_paths(self): + self.seed({KEY_A: b"ZIP"}) + before = snapshot(self.pool) + for name in ("../outside", "/absolute", "downloads/.netrc", f"archives/ff/{ABI_B}.zip", + "downloads/../credentials", "downloads/a\\b", "downloads/file.", "downloads/CON.txt"): + with self.subTest(name=name): + contribution(self.contributions, "unsafe", {name: b"bad"}) + with self.assertRaises(ValueError): + self.merge() + self.assertEqual(snapshot(self.pool), before) + + def test_contribution_links(self): + for kind in (tarfile.SYMTYPE, tarfile.LNKTYPE): + with self.subTest(kind=kind): + member = tarfile.TarInfo(DOWNLOAD) + member.type, member.linkname = kind, "../outside" + contribution(self.contributions, "unsafe", {}, additions=[member]) + with self.assertRaises(ValueError): + self.merge() + self.assertFalse((self.root / "outside").exists()) + + def test_case_collision(self): + contribution(self.contributions, "a-worker", {"downloads/Tool/a.txt": b"a"}) + contribution(self.contributions, "z-worker", {"downloads/tool/b.txt": b"b"}) + with self.assertRaises(ValueError): + self.merge() + self.assertFalse((self.pool / cache.INDEX_NAME).exists()) + + def test_file_directory(self): + contribution(self.contributions, "a-worker", {"downloads/tool": b"file"}) + contribution(self.contributions, "z-worker", {"downloads/tool/a.txt": b"nested"}) + with self.assertRaises(ValueError): + self.merge() + self.assertFalse((self.pool / "downloads/tool").exists()) + + def test_digest(self): + self.seed({KEY_A: b"ZIP"}) + before = snapshot(self.pool) + manifest = {"schema": 1, "files": {KEY_B: record(b"different")}, "used": []} + contribution(self.contributions, "unsafe", {KEY_B: b"corrupted"}, manifest=manifest) + with self.assertRaises(ValueError): + self.merge() + self.assertEqual(snapshot(self.pool), before) + + def test_manifest_schema(self): + for manifest in ({"schema": 2, "files": {}, "used": []}, + {"schema": 1, "files": {}, "used": [KEY_A, KEY_A]}, + {"schema": 1, "files": {}, "used": [DOWNLOAD]}, + {"schema": 1, "files": {}, "used": [], "credentials": "secret"}, + b'{"schema":1,"schema":1,"files":{},"used":[]}'): + with self.subTest(manifest=manifest): + contribution(self.contributions, "unsafe", {}, manifest=manifest) + with self.assertRaises(ValueError): + self.merge() + + def test_payload_match(self): + contribution(self.contributions, "unsafe", {KEY_A: b"unlisted"}, + manifest={"schema": 1, "files": {}, "used": []}) + with self.assertRaises(ValueError): + self.merge() + + def test_duplicate_member(self): + member = tarfile.TarInfo(cache.MANIFEST_NAME) + contribution(self.contributions, "unsafe", {}, additions=[member]) + with self.assertRaises(ValueError): + self.merge() + + def test_contribution_bound(self): + contribution(self.contributions, "unsafe", {DOWNLOAD: b"12345"}) + with patch.object(cache, "CONTRIBUTION_LIMIT", 4), self.assertRaises(ValueError): + self.merge() + + def test_contribution_count(self): + for number in range(33): + contribution(self.contributions, f"worker-{number}", {}) + with self.assertRaises(ValueError): + self.merge() + self.assertFalse((self.pool / cache.INDEX_NAME).exists()) + + def test_aggregate_size(self): + for number in range(4): + contribution(self.contributions, f"worker-{number}", {}) + with patch.object(cache, "AGGREGATE_LIMIT", 39_000), self.assertRaises(ValueError): + self.merge() + self.assertFalse((self.pool / cache.INDEX_NAME).exists()) + + def test_extended_header(self): + path = self.contributions / "unsafe/contribution.tar" + path.parent.mkdir(parents=True) + for kind in (tarfile.XHDTYPE, tarfile.XGLTYPE, tarfile.GNUTYPE_LONGNAME, tarfile.GNUTYPE_LONGLINK): + with self.subTest(kind=kind): + member = tarfile.TarInfo("././@ExtendedHeader") + member.type, member.size = kind, cache.METADATA_LIMIT + 1 + path.write_bytes(member.tobuf(format=tarfile.USTAR_FORMAT) + b"\0" * 1024) + with self.assertRaisesRegex(ValueError, "extended header"): + self.merge() + self.assertFalse((self.pool / cache.INDEX_NAME).exists()) + + def test_validate_all(self): + self.seed({KEY_A: b"ZIP"}) + before = snapshot(self.pool) + contribution(self.contributions, "a-valid", {KEY_B: b"new"}) + contribution(self.contributions, "z-invalid", {"../outside": b"bad"}) + with self.assertRaises(ValueError): + self.merge() + self.assertEqual(snapshot(self.pool), before) + + def test_pool_link(self): + outside = write(self.root, "outside", b"secret") + self.pool.mkdir(parents=True) + try: + (self.pool / "downloads").symlink_to(outside.parent, target_is_directory=True) + except (NotImplementedError, OSError) as error: + self.skipTest(f"Symbolic-link fixture unavailable: {error}") + with self.assertRaises(ValueError): + cache.inventory(self.pool) + self.assertEqual(outside.read_bytes(), b"secret") + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/ci/test_vcpkg_publication.py b/.github/ci/test_vcpkg_publication.py new file mode 100644 index 00000000..13071320 --- /dev/null +++ b/.github/ci/test_vcpkg_publication.py @@ -0,0 +1,264 @@ +"""Check shared dependency publication provenance and snapshot retirement.""" + +import copy +import importlib.util +from pathlib import Path +import unittest + + +spec = importlib.util.spec_from_file_location("vcpkg_publication", Path(__file__).with_name("vcpkg_publication.py")) +publication = importlib.util.module_from_spec(spec) +spec.loader.exec_module(publication) + +REPOSITORY = publication.REPOSITORY +SHA = "a" * 40 +EVENT = {"repository": {"full_name": REPOSITORY, "default_branch": "main"}, + "workflow_run": {"id": 10, "run_attempt": 2, "head_sha": SHA}} +ENVIRONMENT = {"GITHUB_REPOSITORY": REPOSITORY, "GITHUB_EVENT_NAME": "workflow_run", + "GITHUB_REF": "refs/heads/main", "GITHUB_RUN_ID": "100", "GITHUB_RUN_ATTEMPT": "1"} + + +def artifact(identifier=1, name="windows-modules", attempt=2): + return {"id": identifier, "name": f"vcpkg-cache-X64-{name}-{attempt}", + "expired": False, "size_in_bytes": 100} + + +def job(name="windows-modules", conclusion="success"): + return {"name": name, "conclusion": "failure", + "steps": [{"name": "Prepare dependencies", "conclusion": conclusion}, + {"name": "Build and test", "conclusion": "failure"}]} + + +def cache(identifier, run, *, version="archive-v1", ref="refs/heads/main", created="2026-10-16T12:00:00Z"): + return {"id": identifier, "key": f"{publication.PREFIX}{run}-1", "ref": ref, + "version": version, "created_at": created} + + +class FakeApi: + repository = REPOSITORY + + def __init__(self): + self.run = {"id": 10, "run_attempt": 2, "workflow_id": 20, "path": ".github/workflows/ci.yml", + "head_repository": {"full_name": REPOSITORY}, "head_sha": SHA, "status": "completed", + "event": "pull_request", "pull_requests": [{"number": 21}]} + self.workflow = {"id": 20} + self.pull = {"head": {"repo": {"full_name": REPOSITORY}}} + self.artifacts = [artifact()] + self.jobs = {1: [job()], 2: [job()]} + self.caches = [cache(1, 90), cache(2, 100)] + self.requests = [] + self.deleted = [] + + def request(self, method, path, body=None, **query): + self.requests.append((method, path, query)) + if method == "GET" and path == "/actions/runs/10/attempts/2": + return copy.deepcopy(self.run) + if method == "GET" and path == "/actions/workflows/ci.yml": + return copy.deepcopy(self.workflow) + if method == "GET" and path == "/pulls/21": + return copy.deepcopy(self.pull) + if method == "DELETE" and path.startswith("/actions/caches/"): + self.deleted.append(int(path.rsplit("/", 1)[1])) + return None + raise AssertionError(f"Unexpected API request: {method} {path}") + + def pages(self, path, field=None, **query): + self.requests.append(("PAGES", path, query)) + if path == "/actions/runs/10/artifacts" and field == "artifacts": + return copy.deepcopy(self.artifacts) + if path in {"/actions/runs/10/attempts/1/jobs", "/actions/runs/10/attempts/2/jobs"} and field == "jobs": + return copy.deepcopy(self.jobs[int(path.split("/")[-2])]) + if path == "/actions/caches" and field == "actions_caches": + self.assert_cache_query(query) + return copy.deepcopy(self.caches) + raise AssertionError(f"Unexpected pages request: {path}") + + def assert_cache_query(self, query): + if query != {"ref": "refs/heads/main", "key": publication.PREFIX}: + raise AssertionError(f"Unexpected cache query: {query}") + + +class PublicationTests(unittest.TestCase): + def setUp(self): + self.api = FakeApi() + self.event = copy.deepcopy(EVENT) + self.environment = dict(ENVIRONMENT) + + def admit(self): + return publication.admit(self.api, self.event, self.environment) + + def retire(self): + return publication.retire(self.api, self.event, self.environment) + + def test_prepared(self): + result = self.admit() + self.assertEqual(result["allowed"], "true") + self.assertEqual(result["artifact-ids"], "1") + self.assertEqual(result["source-run-id"], "10") + self.assertEqual(self.api.deleted, []) + + def test_push(self): + for event in ("push", "workflow_dispatch"): + with self.subTest(event=event): + self.api.run["event"] = event + self.assertEqual(self.admit()["allowed"], "true") + + def test_default_branch(self): + for name, value in (("GITHUB_REF", "refs/heads/develop"), ("GITHUB_EVENT_NAME", "pull_request"), + ("GITHUB_REPOSITORY", "other/repo")): + with self.subTest(name=name): + self.environment = {**ENVIRONMENT, name: value} + self.assertEqual(self.admit()["allowed"], "false") + self.assertEqual(self.api.requests, []) + + def test_repository(self): + self.event["repository"]["full_name"] = "other/repo" + self.assertEqual(self.admit()["allowed"], "false") + self.assertEqual(self.api.requests, []) + + def test_forks(self): + self.api.run["head_repository"]["full_name"] = "fork/cheryl-engine" + self.assertEqual(self.admit()["allowed"], "false") + self.api.run["head_repository"]["full_name"] = REPOSITORY + self.api.pull["head"]["repo"]["full_name"] = "fork/cheryl-engine" + self.assertEqual(self.admit()["allowed"], "false") + + def test_missing_pr(self): + self.api.run["pull_requests"] = [] + self.assertEqual(self.admit()["allowed"], "false") + + def test_other_event(self): + self.api.run["event"] = "pull_request_target" + self.assertEqual(self.admit()["allowed"], "false") + + def test_run_identity(self): + for field, value in (("id", 11), ("run_attempt", 1), ("workflow_id", 21), ("workflow_id", None), + ("head_sha", "b" * 40), ("head_sha", None), ("status", "in_progress"), + ("path", ".github/workflows/other.yml")): + with self.subTest(field=field, value=value): + saved = self.api.run[field] + self.api.run[field] = value + with self.assertRaises(ValueError): + self.admit() + self.api.run[field] = saved + + def test_missing_workflow(self): + self.api.workflow.clear() + with self.assertRaises(ValueError): + self.admit() + + def test_trigger_id(self): + for value in (None, True, 0, "10"): + with self.subTest(value=value): + self.event["workflow_run"]["id"] = value + with self.assertRaises(ValueError): + self.admit() + self.assertEqual(self.api.requests, []) + + def test_reruns(self): + self.api.artifacts = [artifact(3, attempt=2), artifact(1, attempt=1)] + result = self.admit() + self.assertEqual(result["artifact-ids"], "3") + paths = {path for _, path, _ in self.api.requests} + self.assertIn("/actions/runs/10/attempts/1/jobs", paths) + self.assertIn("/actions/runs/10/attempts/2/jobs", paths) + + def test_rerun_fallback(self): + self.api.artifacts = [artifact(3, attempt=2), artifact(1, attempt=1)] + self.api.jobs[2] = [job(conclusion="failure")] + self.assertEqual(self.admit()["artifact-ids"], "1") + + def test_total_download(self): + self.api.artifacts = [{**artifact(n, name=f"windows-{n}"), "size_in_bytes": publication.ARTIFACT_LIMIT} + for n in range(1, 5)] + self.api.jobs[2] = [job(f"windows-{n}") for n in range(1, 5)] + with self.assertRaisesRegex(ValueError, "total download"): + self.admit() + + def test_unsupported_artifacts(self): + for replacement in ({"expired": True}, {"expired": None}, {"name": "vcpkg-cache-X64-windows-modules-3"}, + {"name": "vcpkg-cache-ARM64-windows-modules-2"}, {"name": "other-artifact"}): + with self.subTest(replacement=replacement): + self.api.artifacts = [{**artifact(), **replacement}] + self.assertEqual(self.admit()["allowed"], "false") + + def test_artifact_limits(self): + for replacement in ({"size_in_bytes": 0}, {"size_in_bytes": publication.ARTIFACT_LIMIT + 1}, + {"size_in_bytes": True}, {"id": -1}): + with self.subTest(replacement=replacement): + self.api.artifacts = [{**artifact(), **replacement}] + with self.assertRaises(ValueError): + self.admit() + + def test_duplicate_artifacts(self): + self.api.artifacts *= 2 + with self.assertRaisesRegex(ValueError, "unique"): + self.admit() + + def test_many_artifacts(self): + self.api.artifacts = [artifact(n) for n in range(1, publication.CONTRIBUTION_LIMIT + 2)] + with self.assertRaisesRegex(ValueError, "Too many"): + self.admit() + + def test_preparation_failed(self): + for conclusion in ("failure", "cancelled", "skipped", None): + with self.subTest(conclusion=conclusion): + self.api.jobs[2] = [job(conclusion=conclusion)] + self.assertEqual(self.admit()["allowed"], "false") + + def test_wrong_job(self): + self.api.jobs[2] = [job("other-job")] + self.assertEqual(self.admit()["allowed"], "false") + + def test_ambiguous_job(self): + self.api.jobs[2] *= 2 + self.assertEqual(self.admit()["allowed"], "false") + + def test_retire(self): + self.assertEqual(self.retire(), {"removed": "1"}) + self.assertEqual(self.api.deleted, [1]) + + def test_save_first(self): + self.api.caches.pop() + with self.assertRaisesRegex(ValueError, "saved before"): + self.retire() + self.assertEqual(self.api.deleted, []) + + def test_retire_scopes(self): + self.api.caches.extend([cache(3, 95, ref="refs/heads/develop"), cache(4, 95, version="other-path"), + cache(5, 101, created="2026-10-16T13:00:00Z"), + {**cache(6, 95), "key": "other-cache-95-1"}]) + self.retire() + self.assertEqual(self.api.deleted, [1]) + + def test_retire_branch(self): + self.environment["GITHUB_REF"] = "refs/heads/develop" + with self.assertRaises(ValueError): + self.retire() + self.assertEqual(self.api.requests, []) + + def test_bad_snapshot(self): + for value in ("", "0", "-1", "1\nextra=bad"): + with self.subTest(value=value): + self.environment["GITHUB_RUN_ID"] = value + with self.assertRaises(ValueError): + self.retire() + self.assertEqual(self.api.deleted, []) + + def test_retire_metadata(self): + for replacement in ({"version": ""}, {"created_at": None}, {"created_at": "2026-10-16"}, {"id": 0}): + with self.subTest(replacement=replacement): + self.api.caches = [cache(1, 90), {**cache(2, 100), **replacement}] + with self.assertRaises(ValueError): + self.retire() + self.assertEqual(self.api.deleted, []) + + def test_validate_before_delete(self): + self.api.caches.insert(1, {**cache(3, 95), "created_at": "bad"}) + with self.assertRaises(ValueError): + self.retire() + self.assertEqual(self.api.deleted, []) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/ci/test_verify_sdk.py b/.github/ci/test_verify_sdk.py new file mode 100644 index 00000000..94c17b1f --- /dev/null +++ b/.github/ci/test_verify_sdk.py @@ -0,0 +1,132 @@ +"""Check relocation validation without loading or compiling native libraries.""" + +import importlib.util +from pathlib import Path +import subprocess +import tempfile +import unittest +from unittest.mock import patch + + +spec = importlib.util.spec_from_file_location("sdk_package", Path(__file__).with_name("package.py")) +package = importlib.util.module_from_spec(spec) +spec.loader.exec_module(package) +spec = importlib.util.spec_from_file_location("sdk_verify", Path(__file__).with_name("verify_sdk.py")) +verify_sdk = importlib.util.module_from_spec(spec) +with patch.dict("sys.modules", {"package": package}): + spec.loader.exec_module(verify_sdk) + + +class RelocationTests(unittest.TestCase): + def setUp(self): + temporary = tempfile.TemporaryDirectory() + self.addCleanup(temporary.cleanup) + self.root = Path(temporary.name).resolve() + self.sdk = self.root / "relocated sdk" + self.library = self.sdk / "lib/libfreetype.so.6" + self.library.parent.mkdir(parents=True) + self.library.write_bytes(b"native runtime fixture") + + def result(self, command, output): + return subprocess.CompletedProcess(command, 0, stdout=output, stderr="") + + def test_runpath(self): + for path in (None, "/producer/lib", "$ORIGIN:/producer/lib"): + dynamic = "" if path is None else f"(RUNPATH) Library runpath: [{path}]\n" + with self.subTest(path=path), patch.object(verify_sdk.subprocess, "run", + side_effect=lambda command, **kwargs: self.result(command, dynamic)) as execute: + with self.assertRaisesRegex(ValueError, "relative runtime search path"): + verify_sdk.check_relocated_runtimes(self.sdk, {"system": "Linux"}) + self.assertEqual(execute.call_args.args[0][0], "readelf") + + def test_external_runtime(self): + outside = self.root / "host libraries/libpng16.so.16" + outside.parent.mkdir() + outside.write_bytes(b"host runtime fixture") + + def execute(command, **kwargs): + output = ("(RUNPATH) Library runpath: [$ORIGIN]\n(NEEDED) Shared library: [libpng16.so.16]\n" + if command[0] == "readelf" + else f"libpng16.so.16 => {outside} (0x123456)\n") + return self.result(command, output) + + with patch.object(verify_sdk.subprocess, "run", side_effect=execute), \ + self.assertRaisesRegex(ValueError, "loads libpng16.so.16 outside its package"): + verify_sdk.check_relocated_runtimes(self.sdk, {"system": "Linux"}) + + def test_relocated_runtime(self): + dependency = self.library.with_name("libpng16.so.16") + dependency.write_bytes(b"packaged runtime fixture") + commands = [] + + def execute(command, **kwargs): + commands.append(command) + output = "(RUNPATH) Library runpath: [$ORIGIN]\n" + if command[0] == "ldd": + output = "libc.so.6 => /system/libc.so.6 (0x123456)\n" + if command[1] == self.library: + output += f"libpng16.so.16 => {dependency} (0x234567)\n" + return self.result(command, output) + + with patch.object(verify_sdk.subprocess, "run", side_effect=execute): + verify_sdk.check_relocated_runtimes(self.sdk, {"system": "Linux"}) + self.assertEqual({Path(command[1]) for command in commands if command[0] == "ldd"}, {self.library, dependency}) + self.assertEqual(len(commands), 4) + + def test_system_closure(self): + host = self.root / "host libraries" + for interface, dependency in (("libudev.so.1", "libcap.so.2"), ("libXdmcp.so.6", "libbsd.so.0")): + def execute(command, **kwargs): + output = (f"(RUNPATH) Library runpath: [$ORIGIN]\n(NEEDED) Shared library: [{interface}]\n" + if command[0] == "readelf" else + f"{interface} => {host / interface} (0x123456)\n" + f"{dependency} => {host / dependency} (0x234567)\n") + return self.result(command, output) + + with self.subTest(interface=interface), patch.object(verify_sdk.subprocess, "run", side_effect=execute): + verify_sdk.check_relocated_runtimes(self.sdk, {"system": "Linux"}) + + def test_transitive_external(self): + dependency = self.library.with_name("libpng16.so.16") + dependency.write_bytes(b"packaged image runtime") + transitive = self.library.with_name("libz.so.1") + transitive.write_bytes(b"packaged compression runtime") + outside = self.root / "host libraries/libz.so.1" + imports = {self.library: dependency.name, dependency: transitive.name, transitive: "libc.so.6"} + + def execute(command, **kwargs): + library = Path(command[-1]) + if command[0] == "readelf": + output = f"(RUNPATH) Library runpath: [$ORIGIN]\n(NEEDED) Shared library: [{imports[library]}]\n" + elif library == self.library: + output = (f"{dependency.name} => {dependency} (0x123456)\n" + f"{transitive.name} => {outside} (0x234567)\n") + elif library == dependency: + output = f"{transitive.name} => {transitive} (0x123456)\n" + else: + output = "libc.so.6 => /system/libc.so.6 (0x123456)\n" + return self.result(command, output) + + with patch.object(verify_sdk.subprocess, "run", side_effect=execute), \ + self.assertRaisesRegex(ValueError, "loads libz.so.1 outside its package"): + verify_sdk.check_relocated_runtimes(self.sdk, {"system": "Linux"}) + + def test_missing_runtime(self): + def execute(command, **kwargs): + output = ("(RUNPATH) Library runpath: [$ORIGIN]\n(NEEDED) Shared library: [libpng16.so.16]\n" + if command[0] == "readelf" + else "libpng16.so.16 => not found\n") + return self.result(command, output) + + with patch.object(verify_sdk.subprocess, "run", side_effect=execute), \ + self.assertRaisesRegex(ValueError, "cannot load libpng16.so.16"): + verify_sdk.check_relocated_runtimes(self.sdk, {"system": "Linux"}) + + def test_windows(self): + with patch.object(verify_sdk.subprocess, "run") as execute: + verify_sdk.check_relocated_runtimes(self.sdk, {"system": "Windows"}) + execute.assert_not_called() + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/ci/test_versions.py b/.github/ci/test_versions.py new file mode 100644 index 00000000..d9670be5 --- /dev/null +++ b/.github/ci/test_versions.py @@ -0,0 +1,396 @@ +"""Check version policies and reviewed PR changes using fake Git and API state.""" + +import base64 +from contextlib import redirect_stdout +import hashlib +import importlib.util +import io +import json +from pathlib import Path +import unittest +from unittest.mock import patch + + +spec = importlib.util.spec_from_file_location("versions", Path(__file__).with_name("versions.py")) +versions = importlib.util.module_from_spec(spec) +spec.loader.exec_module(versions) + +A, B, C, MAIN, STABLE, BOT, GENERATED = (character * 40 for character in "1234abe") +TEXT = """# Independent component versions. +set(VERSION_PROJECT_CHERYL 4.0.0) +set(VERSION_PROJECT_ENGINE 4.0.0) +set(VERSION_PROJECT_MODULE_NATIVE_GLFW 1.0.0) +set(VERSION_PROJECT_MODULE_DEBUG_TERMINAL_LINUX 1.0.0) +set(VERSION_PROJECT_MODULE_OPENGL 1.0.0) +set(VERSION_PROJECT_MODULE_UI_TGUI 1.0.0) +set(VERSION_PROJECT_MODULE_UI_RMLUI 1.0.0) +set(VERSION_PROJECT_MODULE_AUDIO_MINIAUDIO 1.0.0) +set(VERSION_PROJECT_APP_DEMO 1.0.0) +set(UNRELATED value) +""" + + +def state(source=A, values=None, kind="maintenance"): + return {"format": 1, "kind": kind, "source_revision": source, + "versions": values or versions.read_versions(TEXT), "stable_tag": "v4.0.0", "stable_revision": STABLE} + + +class FakeGit: + def __init__(self): + self.refs = {"HEAD": B, "refs/remotes/origin/develop": B, + "refs/remotes/origin/main": STABLE, "refs/tags/v4.0.0": STABLE} + self.parents_by_revision = {A: [MAIN], B: [A], STABLE: [MAIN, A], MAIN: []} + self.files = {revision: {versions.VERSION_FILE: TEXT, "source.cpp": revision} for revision in (A, B, MAIN, STABLE)} + self.paths = {B: {"projects/engine/src/example.cpp"}} + + def resolve(self, ref): + return self.refs[ref] + + def text(self, revision, path, *, optional=False): + if optional: + return self.files[revision].get(path) + return self.files[revision][path] + + def parents(self, revision): + return self.parents_by_revision[revision] + + def ancestor(self, before, after): + pending, seen = [after], set() + while pending: + current = pending.pop() + if current == before: + return True + if current not in seen: + seen.add(current) + pending.extend(self.parents_by_revision.get(current, [])) + return False + + def integrations(self, before, after): + commits = [] + while after != before: + commits.append(after) + after = self.parents(after)[0] + return commits[::-1] + + def changed(self, before, after): + return self.paths[after] + + +class FakeApi: + def __init__(self, git): + self.git = git + self.releases = [{"tag_name": "v4.0.0", "draft": False, "prerelease": False, "published_at": "2026-10-20"}] + self.refs, self.pulls, self.calls, self.commits, self.tree_files, self.commit_trees = {}, [], [], [], {}, {} + + def pages(self, path, field=None, **query): + self.calls.append(("GET", path, None, query)) + if path == "/releases": + return self.releases + if path == "/pulls": + return [pull for pull in self.pulls if pull["base"]["ref"] == query["base"] + and pull["head"]["ref"] == query["head"].split(":", 1)[1]] + raise AssertionError(path) + + def request(self, method, path, body=None, **query): + self.calls.append((method, path, body, query)) + if method == "GET" and path.startswith("/git/ref/heads/"): + revision = self.refs.get(path.removeprefix("/git/ref/heads/")) + return {"object": {"sha": revision}} if revision else None + if method == "GET" and path.startswith("/contents/"): + text = self.git.files[query["ref"]].get(path.removeprefix("/contents/")) + if text is None and query.get("missing_ok"): + return None + return {"encoding": "base64", "content": base64.b64encode(text.encode()).decode()} + if method == "GET" and path.startswith("/git/commits/"): + revision = path.rsplit("/", 1)[1] + return {"tree": {"sha": self.commit_trees.get(revision, revision)}} + if method == "GET" and path.startswith("/git/trees/"): + revision = path.rsplit("/", 1)[1] + files = self.tree_files.get(revision, self.git.files.get(revision)) + return {"truncated": False, "tree": [ + {"path": name, "mode": "100644", "type": "blob", "sha": hashlib.sha1(text.encode()).hexdigest()} + for name, text in files.items()]} + if method == "POST" and path == "/git/trees": + files = dict(self.git.files[body["base_tree"]]) + files.update({entry["path"]: entry["content"] for entry in body["tree"]}) + self.tree_files[C] = files + return {"sha": C} + if method == "POST" and path == "/git/commits": + self.commits.append(body) + self.commit_trees[GENERATED] = body["tree"] + self.git.files[GENERATED] = self.tree_files[body["tree"]] + return {"sha": GENERATED} + if method == "POST" and path == "/git/refs": + self.refs[body["ref"].removeprefix("refs/heads/")] = body["sha"] + return body + if method == "PATCH" and path.startswith("/git/refs/heads/"): + self.refs[path.removeprefix("/git/refs/heads/")] = body["sha"] + return body + if method == "POST" and path == "/pulls": + pull = {**body, "number": len(self.pulls) + 1, + "head": {"ref": body["head"], "repo": {"full_name": versions.REPOSITORY}}, + "base": {"ref": body["base"]}} + self.pulls.append(pull) + return pull + if method == "PATCH" and path.startswith("/pulls/"): + pull = next(pull for pull in self.pulls if pull["number"] == int(path.rsplit("/", 1)[1])) + pull.update(body) + return pull + raise AssertionError((method, path)) + + +class PolicyTests(unittest.TestCase): + def test_versions(self): + text = TEXT.replace("4.0.0)", '"4.0.0") # preserve comment', 1).replace("\n", "\r\n") + values = versions.read_versions(text) + self.assertEqual(values[versions.ENGINE], "4.0.0") + changed = versions.replace_versions(text, {versions.PROJECT: "4.1.0"}) + self.assertEqual(changed, text.replace('"4.0.0"', '"4.1.0"', 1)) + self.assertEqual(versions.read_versions(changed)[versions.ENGINE], "4.0.0") + + def test_invalid(self): + for value in ("1.0", "1.0.0-alpha", "01.0.0", "-1.0.0", None): + with self.subTest(value=value), self.assertRaises(ValueError): + versions.version_tuple(value) + with self.assertRaises(ValueError): + versions.read_versions(TEXT + "set(VERSION_PROJECT_ENGINE 5.0.0)\n") + with self.assertRaises(ValueError): + versions.replace_versions(TEXT, {"VERSION_PROJECT_UNKNOWN": "1.0.0"}) + + def test_aliases(self): + text = ("set(VERSION_PROJECT_ENGINE 5.0.0)\n" + "set(VERSION_PROJECT_MODULE_STARTUP ${VERSION_PROJECT_ENGINE})\n" + 'set(VERSION_PROJECT_MODULE_WORKERS "${VERSION_PROJECT_MODULE_STARTUP}") # preserve alias\n') + values = versions.read_versions(text) + self.assertEqual(values, {"VERSION_PROJECT_ENGINE": "5.0.0", "VERSION_PROJECT_MODULE_STARTUP": "5.0.0", + "VERSION_PROJECT_MODULE_WORKERS": "5.0.0"}) + changed = versions.replace_versions(text, {versions.ENGINE: "5.0.1"}) + self.assertEqual(changed, text.replace("5.0.0", "5.0.1")) + self.assertTrue(all(value == "5.0.1" for value in versions.read_versions(changed).values())) + + def test_invalid_aliases(self): + for value in ("${VERSION_PROJECT_UNKNOWN}", "${VERSION_PROJECT_MODULE_STARTUP}", "${ENV}", + "$ENV{VERSION_PROJECT_ENGINE}", "${VERSION_PROJECT_ENGINE}.0", "${${VERSION_PROJECT_ENGINE}}"): + with self.subTest(value=value), self.assertRaises(ValueError): + versions.read_versions("set(VERSION_PROJECT_ENGINE 5.0.0)\n" + f"set(VERSION_PROJECT_MODULE_STARTUP {value})\n") + with self.assertRaisesRegex(ValueError, "previously declared"): + versions.read_versions("set(VERSION_PROJECT_MODULE_STARTUP ${VERSION_PROJECT_ENGINE})\n" + "set(VERSION_PROJECT_ENGINE 5.0.0)\n") + + def test_repository_versions(self): + text = (versions.ROOT / versions.VERSION_FILE).read_text(encoding="utf-8") + values = versions.read_versions(text) + self.assertIn(versions.PROJECT, values) + self.assertIn(versions.ENGINE, values) + self.assertEqual(values["VERSION_PROJECT_MODULE_STARTUP"], values[versions.ENGINE]) + self.assertEqual(values["VERSION_PROJECT_MODULE_WORKERS"], values[versions.ENGINE]) + self.assertEqual(versions.replace_versions(text, {}), text) + + def test_owners(self): + expected = {versions.ENGINE, "VERSION_PROJECT_MODULE_NATIVE_GLFW", "VERSION_PROJECT_MODULE_UI_RMLUI"} + self.assertEqual(versions.affected_components({"cmake/CherylOptions.cmake", "extern/glfw", "extern/rmlui"}), expected) + self.assertEqual(versions.affected_components({"projects/engine/tests/unit/src/test.cpp", "docs/site/runtime/logging.md", + "docs/site/modules/rmlui.md", ".github/workflows/ci.yml"}), set()) + self.assertEqual(versions.affected_components({"projects/apps/first-game/src/first-game.cpp"}), set()) + + def test_release_target(self): + for current, stable, expected in (("4.0.9", "4.0.0", "4.1.0"), ("4.3.2", "4.0.0", "4.3.0"), + ("5.0.2", "4.0.0", "5.0.0")): + with self.subTest(current=current): + self.assertEqual(versions.release_version(current, stable), expected) + with self.assertRaises(ValueError): + versions.release_version("3.0.0", "4.0.0") + + +class AutomationTests(unittest.TestCase): + def setUp(self): + self.git = FakeGit() + self.api = FakeApi(self.git) + self.automation = versions.Automation(self.api, self.git) + + def pending(self, source=A, values=None, kind="maintenance", branch="ci/version-update"): + value = state(source, values, kind) + self.git.files[BOT] = {**self.git.files[source], versions.STATE_FILE: json.dumps(value), + versions.VERSION_FILE: versions.replace_versions(self.git.text(source, versions.VERSION_FILE), value["versions"])} + self.api.refs[branch] = BOT + return value + + def test_frozen(self): + for releases in ([], [{**self.api.releases[0], "draft": True}], [{**self.api.releases[0], "prerelease": True}]): + with self.subTest(releases=releases), redirect_stdout(io.StringIO()): + self.api.releases = releases + self.assertIsNone(self.automation.maintain()) + self.assertFalse(self.api.commits) + self.assertFalse(self.api.pulls) + + def test_bootstrap(self): + initial = self.automation.initial_state(self.automation.stable_release(), B) + self.assertEqual(initial["source_revision"], A) + self.assertNotEqual(initial["source_revision"], STABLE) + + def test_maintenance(self): + pull = self.automation.maintain() + values = versions.read_versions(self.git.text(GENERATED, versions.VERSION_FILE)) + self.assertEqual(values[versions.PROJECT], "4.0.1") + self.assertEqual(values[versions.ENGINE], "4.0.1") + self.assertEqual(values["VERSION_PROJECT_MODULE_NATIVE_GLFW"], "1.0.0") + self.assertEqual(pull["head"]["ref"], "ci/version-update") + self.assertEqual(pull["base"]["ref"], "develop") + self.assertIn("Approve", pull["body"]) + self.assertEqual(self.api.commits[0]["parents"], [B]) + self.assertFalse(any("dispatch" in path for _, path, _, _ in self.api.calls)) + + def test_repeat(self): + self.automation.maintain() + self.automation.maintain() + self.assertEqual(len(self.api.commits), 1) + self.assertEqual(len(self.api.pulls), 1) + + def test_accumulate(self): + values = versions.read_versions(TEXT) + values[versions.PROJECT] = values[versions.ENGINE] = "4.0.1" + self.pending(B, values) + self.git.refs["HEAD"] = self.git.refs["refs/remotes/origin/develop"] = C + self.git.parents_by_revision[C] = [B] + self.git.files[C] = {**self.git.files[B], "source.cpp": C} + self.git.paths[C] = {"projects/modules/platform/native-glfw/src/window.cpp"} + self.automation.maintain() + actual = versions.read_versions(self.git.text(GENERATED, versions.VERSION_FILE)) + self.assertEqual(actual[versions.PROJECT], "4.0.2") + self.assertEqual(actual[versions.ENGINE], "4.0.1") + self.assertEqual(actual["VERSION_PROJECT_MODULE_NATIVE_GLFW"], "1.0.1") + self.assertEqual(self.api.commits[0]["parents"], [BOT, C]) + update = next(body for method, path, body, _ in self.api.calls if method == "PATCH" and path.startswith("/git/refs/")) + self.assertFalse(update["force"]) + + def test_manual(self): + self.git.files[B][versions.VERSION_FILE] = versions.replace_versions(TEXT, {versions.PROJECT: "5.0.0", versions.ENGINE: "7.0.0"}) + self.git.paths[B].add(versions.VERSION_FILE) + planned = self.automation.planned_versions(state(), B) + self.assertEqual(planned[versions.PROJECT], "5.0.0") + self.assertEqual(planned[versions.ENGINE], "7.0.0") + + def test_version_only(self): + self.git.paths[B] = set(versions.OWNED_FILES) + self.git.files[B][versions.VERSION_FILE] = versions.replace_versions(TEXT, {versions.PROJECT: "4.0.1"}) + self.assertEqual(self.automation.planned_versions(state(), B)[versions.PROJECT], "4.0.1") + self.assertEqual(self.automation.planned_versions(state(), B)[versions.ENGINE], "4.0.0") + + def test_old_generated_merge(self): + older = versions.read_versions(TEXT) + older[versions.PROJECT] = older[versions.ENGINE] = "4.0.1" + newer = {**older, versions.PROJECT: "4.0.2", versions.ENGINE: "4.0.2"} + self.git.parents_by_revision[C] = [B] + self.git.files[C] = {**self.git.files[B], versions.VERSION_FILE: versions.replace_versions(TEXT, older), + versions.STATE_FILE: json.dumps(state(A, older))} + self.git.paths[C] = set(versions.OWNED_FILES) + self.assertEqual(self.automation.planned_versions(state(B, newer), C), newer) + + def test_docs(self): + self.git.paths[B] = {"docs/site/development/building.md"} + planned = self.automation.planned_versions(state(), B) + self.assertEqual(planned[versions.PROJECT], "4.0.1") + self.assertEqual(planned[versions.ENGINE], "4.0.0") + + def test_protected_bot(self): + self.api.refs["ci/version-update"] = BOT + self.git.files[BOT] = dict(self.git.files[A]) + with self.assertRaisesRegex(ValueError, "no owned"): + self.automation.maintain() + self.pending() + self.git.files[BOT]["unrelated.cpp"] = "user work" + with self.assertRaisesRegex(ValueError, "outside generated"): + self.automation.maintain() + self.assertFalse(self.api.commits) + + def test_rollback(self): + saved = state(source="f" * 40) + self.git.files[B][versions.STATE_FILE] = json.dumps(saved) + with self.assertRaisesRegex(ValueError, "rollback"): + self.automation.maintain() + self.assertFalse(self.api.commits) + + def test_stale_pending(self): + pending = {"state": self.pending(A)} + saved = state(source=B) + saved["versions"][versions.PROJECT] = "4.1.0" + self.git.files[B][versions.STATE_FILE] = json.dumps(saved) + actual = self.automation.current_state(self.automation.stable_release(), B, pending) + self.assertEqual(actual["source_revision"], B) + self.assertEqual(actual["versions"][versions.PROJECT], "4.1.0") + + def test_release_bootstrap(self): + self.api.releases = [] + existing = self.api.request("POST", "/pulls", {"head": "develop", "base": "main", "title": "Release 4.0.0", + "body": "User release notes", "draft": True}) + result = self.automation.prepare_release() + self.assertEqual(result["release"]["number"], existing["number"]) + self.assertEqual(result["release"]["body"], "User release notes") + self.assertTrue(result["release"]["draft"]) + self.assertFalse(self.api.commits) + + def test_prepare_release(self): + self.git.files[B][versions.VERSION_FILE] = versions.replace_versions(TEXT, {versions.PROJECT: "4.0.9", versions.ENGINE: "4.0.7"}) + result = self.automation.prepare_release() + actual = versions.read_versions(self.git.text(GENERATED, versions.VERSION_FILE)) + self.assertEqual(actual[versions.PROJECT], "4.1.0") + self.assertEqual(actual[versions.ENGINE], "4.0.7") + self.assertEqual(result["preparation"]["base"]["ref"], "develop") + self.assertEqual(result["release"]["title"], "Release 4.1.0") + self.assertTrue(result["release"]["draft"]) + + def test_pending_release(self): + planned = versions.read_versions(TEXT) + planned[versions.PROJECT] = planned[versions.ENGINE] = "4.0.1" + self.pending(B, planned) + with self.assertRaisesRegex(ValueError, "Merge the development version-update PR"): + self.automation.prepare_release() + self.assertFalse(self.api.commits) + self.assertFalse(self.api.pulls) + + def test_tag_history(self): + self.git.refs["refs/remotes/origin/main"] = MAIN + with self.assertRaisesRegex(ValueError, "not part of main"): + self.automation.maintain() + self.assertFalse(self.api.commits) + + def test_pr_permissions(self): + with patch.object(self.api, "request", side_effect=RuntimeError("HTTP 403")): + with self.assertRaisesRegex(RuntimeError, "Allow GitHub Actions to create and approve pull requests"): + self.automation.ensure_pull("ci/version-update", "develop", "Updates versions", versions.MARKER) + + +class ReleasePolicyTests(unittest.TestCase): + def setUp(self): + self.git = FakeGit() + self.pull = {"title": "Release 4.0.0", "base": {"ref": "main"}, + "head": {"ref": "develop", "sha": B, "repo": {"full_name": versions.REPOSITORY}}} + + def test_release(self): + self.assertEqual(versions.check_release({"pull_request": self.pull}, self.git), "4.0.0") + + def test_development(self): + self.pull["base"]["ref"] = "develop" + self.pull["title"] = "Ordinary development" + self.assertIsNone(versions.check_release({"pull_request": self.pull}, self.git)) + + def test_title(self): + self.pull["title"] = "Release 4.1.0" + with self.assertRaisesRegex(ValueError, "title"): + versions.check_release({"pull_request": self.pull}, self.git) + + def test_branch(self): + self.pull["head"]["repo"]["full_name"] = "someone/cheryl-engine" + with self.assertRaisesRegex(ValueError, "repository's develop"): + versions.check_release({"pull_request": self.pull}, self.git) + + def test_patch(self): + self.git.files[B][versions.VERSION_FILE] = versions.replace_versions(TEXT, {versions.PROJECT: "4.0.1"}) + self.pull["title"] = "Release 4.0.1" + with self.assertRaisesRegex(ValueError, "zero patch"): + versions.check_release({"pull_request": self.pull}, self.git) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/ci/vcpkg.json b/.github/ci/vcpkg.json index 8f4dfae5..6e63b12a 100644 --- a/.github/ci/vcpkg.json +++ b/.github/ci/vcpkg.json @@ -3,12 +3,31 @@ "version": "4.0.0", "builtin-baseline": "e456309491fd875487bf02b8a0dca76c1b1b00cc", "dependencies": [ - { "name": "freetype", "default-features": false }, - { "name": "harfbuzz", "default-features": false, "features": ["freetype"] }, - "icu" + { + "name": "freetype", + "default-features": false + }, + { + "name": "harfbuzz", + "default-features": false + }, + { + "name": "icu", + "platform": "!windows" + } ], "features": { - "hwloc": { "description": "Exercise the optional native topology provider", "dependencies": ["hwloc"] }, - "native": { "description": "Build the Windows native input adapter", "dependencies": ["hidapi"] } + "hwloc": { + "description": "Exercise the optional native topology provider", + "dependencies": [ + "hwloc" + ] + }, + "native": { + "description": "Build the Windows native input adapter", + "dependencies": [ + "hidapi" + ] + } } } diff --git a/.github/ci/vcpkg_cache.py b/.github/ci/vcpkg_cache.py new file mode 100644 index 00000000..416212dd --- /dev/null +++ b/.github/ci/vcpkg_cache.py @@ -0,0 +1,469 @@ +"""Pack Windows vcpkg additions and maintain one bounded shared package pool.""" + +import argparse +import hashlib +import io +import json +import math +import os +from pathlib import Path +import re +import shutil +import stat +import tarfile +import tempfile +import time + + +ROOT = Path(__file__).resolve().parents[2] +CACHE_PREFIX = "vcpkg-v1-Windows-X64-msvc-" +GIB = 1024 ** 3 +DEFAULT_LIMIT = 2 * GIB +CONTRIBUTION_LIMIT = 2 * GIB +METADATA_LIMIT = 16 * 1024 ** 2 +FILE_LIMIT = 200_000 +TAR_LIMIT = CONTRIBUTION_LIMIT + METADATA_LIMIT + FILE_LIMIT * 1024 + 10240 +CONTRIBUTION_COUNT = 32 +AGGREGATE_LIMIT = 3 * GIB +INDEX_NAME = "pool-index.json" +MANIFEST_NAME = "contribution.json" +ABI = re.compile(r"[0-9a-f]{64}") +# Pinned vcpkg-tool 2026-09-26: binarycaching.cpp files_archive_subpath(). +ARCHIVE = re.compile(r"archives/([0-9a-f]{2})/([0-9a-f]{64})\.zip") +SEGMENT = re.compile(r"[A-Za-z0-9_.+@=(){}\[\],~-]+") +RESERVED = re.compile(r"(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?", re.IGNORECASE) +CREDENTIAL_NAMES = {".git", ".ssh", ".aws", ".azure", ".config", ".netrc", "_netrc", ".npmrc", + ".pypirc", ".git-credentials", "credentials", "credentials.json", "nuget.config", + "id_rsa", "id_ed25519", "hosts.yml", "access_token", "token", "tokens.json"} + + +class _BoundedTarInfo(tarfile.TarInfo): + def _check_extension(self): + if not 0 <= self.size <= METADATA_LIMIT: + raise ValueError("vcpkg tar extended header exceeds its metadata limit") + + def _proc_pax(self, archive): + self._check_extension() + return super()._proc_pax(archive) + + def _proc_gnulong(self, archive): + self._check_extension() + return super()._proc_gnulong(archive) + + +def describe(*, environment=None, root=ROOT): + environment = os.environ if environment is None else environment + identity = [] + for name in ("GITHUB_RUN_ID", "GITHUB_RUN_ATTEMPT"): + value = environment.get(name) + if not isinstance(value, str) or not re.fullmatch(r"[1-9][0-9]*", value): + raise ValueError(f"{name} must be a positive integer") + identity.append(value) + directory = Path(root).absolute() / "build/cache" + return {"cache-path": str(directory / "vcpkg"), "cache-prefix": CACHE_PREFIX, + "cache-key": CACHE_PREFIX + "-".join(identity), + "contribution-path": str(directory / "vcpkg-artifacts/contribution.tar")} + + +def _pool(root): + return Path(root).absolute() / "build/cache/vcpkg" + + +def _no_links(path): + path = Path(path).absolute() + for ancestor in (path, *path.parents): + if ancestor.is_symlink() or (hasattr(ancestor, "is_junction") and ancestor.is_junction()): + raise ValueError(f"vcpkg cache paths must not contain links: {ancestor}") + + +def _path(name): + if not isinstance(name, str) or not name or len(name) > 1024: + raise ValueError("Invalid vcpkg cache relative path") + parts = name.split("/") + for part in parts: + if (part in {"", ".", ".."} or not SEGMENT.fullmatch(part) or part.endswith(".") + or RESERVED.fullmatch(part) or part.casefold() in CREDENTIAL_NAMES): + raise ValueError(f"Invalid vcpkg cache relative path: {name!r}") + return parts + + +def _payload_path(name): + parts = _path(name) + if parts[0] == "archives": + match = ARCHIVE.fullmatch(name) + if not match or match[1] != match[2][:2]: + raise ValueError(f"Invalid native vcpkg binary archive path: {name!r}") + elif parts[0] != "downloads" or len(parts) < 2: + raise ValueError(f"Unexpected vcpkg cache payload path: {name!r}") + return name + + +def _register(name, names): + parts = _path(name) + for length in range(1, len(parts) + 1): + prefix = "/".join(parts[:length]) + folded = prefix.casefold() + if folded in names and names[folded] != prefix: + raise ValueError(f"vcpkg cache has a case-insensitive path collision: {prefix!r}") + names[folded] = prefix + + +def _files(directory): + directory = Path(directory).absolute() + _no_links(directory) + if not directory.exists(): + return {} + if not directory.is_dir(): + raise ValueError(f"Expected a vcpkg cache directory: {directory}") + files = {} + names = {} + for parent, directories, entries in os.walk(directory, followlinks=False): + directories.sort() + for name in sorted(directories + entries): + path = Path(parent) / name + _no_links(path) + relative = path.relative_to(directory).as_posix() + _register(relative, names) + mode = path.lstat().st_mode + if stat.S_ISREG(mode): + files[relative] = path + if len(files) > FILE_LIMIT: + raise ValueError("vcpkg cache exceeds the file-count limit") + elif not stat.S_ISDIR(mode): + raise ValueError(f"vcpkg cache contains a non-regular file: {relative!r}") + return files + + +def _digest(path): + value = hashlib.sha256() + with Path(path).open("rb") as source: + for block in iter(lambda: source.read(1024 * 1024), b""): + value.update(block) + return value.hexdigest() + + +def _encode(value): + return (json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n").encode("utf-8") + + +def _pairs(items): + result = {} + for name, value in items: + if name in result: + raise ValueError(f"Duplicate vcpkg metadata key: {name!r}") + result[name] = value + return result + + +def _decode(data): + if len(data) > METADATA_LIMIT: + raise ValueError("vcpkg metadata exceeds its size limit") + try: + return json.loads(data.decode("utf-8"), object_pairs_hook=_pairs) + except (UnicodeDecodeError, json.JSONDecodeError) as error: + raise ValueError("Invalid vcpkg metadata JSON") from error + + +def _records(value, *, indexed): + if not isinstance(value, dict) or len(value) > FILE_LIMIT: + raise ValueError("Invalid vcpkg file metadata") + names = {} + for name, record in value.items(): + _payload_path(name) + _register(name, names) + fields = {"sha256", "size", "last_used"} if indexed else {"sha256", "size"} + if (not isinstance(record, dict) or set(record) != fields + or not isinstance(record["sha256"], str) or not ABI.fullmatch(record["sha256"]) + or type(record["size"]) is not int or not 0 <= record["size"] <= CONTRIBUTION_LIMIT): + raise ValueError(f"Invalid vcpkg file metadata: {name!r}") + if indexed and (type(record["last_used"]) is not int or not 0 <= record["last_used"] <= 1_000_000_000): + raise ValueError(f"Invalid vcpkg last-use checkpoint: {name!r}") + for name in value: + parts = name.split("/") + if any("/".join(parts[:length]) in value for length in range(1, len(parts))): + raise ValueError(f"vcpkg file is also used as a directory: {name!r}") + return value + + +def inventory(directory): + result = {} + for name, path in _files(directory).items(): + if name == INDEX_NAME: + continue + _payload_path(name) + size = path.stat().st_size + if size > CONTRIBUTION_LIMIT: + raise ValueError(f"vcpkg cache file exceeds the contribution limit: {name!r}") + result[name] = {"sha256": _digest(path), "size": size} + return result + + +def read_index(directory): + path = Path(directory) / INDEX_NAME + _no_links(path) + if not path.exists(): + return {"schema": 1, "files": {}} + if not path.is_file() or path.stat().st_size > METADATA_LIMIT: + raise ValueError("Invalid vcpkg pool index") + value = _decode(path.read_bytes()) + if not isinstance(value, dict) or set(value) != {"schema", "files"} or type(value["schema"]) is not int or value["schema"] != 1: + raise ValueError("Unsupported vcpkg pool index schema") + _records(value["files"], indexed=True) + return value + + +def parse_status(text): + """Read core-package ABI identities from vcpkg's Debian-style status file.""" + if not isinstance(text, str) or "\0" in text or len(text.encode("utf-8")) > METADATA_LIMIT: + raise ValueError("Invalid vcpkg installed status") + paragraphs = [] + fields = {} + previous = None + for line in text.splitlines() + [""]: + if not line: + if fields: + paragraphs.append(fields) + fields, previous = {}, None + elif line[0].isspace(): + if previous is None: + raise ValueError("Invalid vcpkg status continuation") + fields[previous] += "\n" + line + else: + name, separator, value = line.partition(":") + if not separator or not re.fullmatch(r"[A-Za-z][A-Za-z0-9-]*", name) or name in fields: + raise ValueError("Invalid vcpkg status field") + fields[name], previous = value.strip(), name + used = set() + for fields in paragraphs: + if fields.get("Status") not in {"install ok installed", "hold ok installed"} or fields.get("Feature"): + continue + abi = fields.get("Abi", "") + if abi: + if not ABI.fullmatch(abi): + raise ValueError("Invalid installed vcpkg package ABI") + used.add(f"archives/{abi[:2]}/{abi}.zip") + return sorted(used) + + +def used_archives(installed): + path = Path(installed).absolute() / "vcpkg/status" + _no_links(path) + if not path.is_file() or path.stat().st_size > METADATA_LIMIT: + raise ValueError("Expected a bounded installed vcpkg status file") + return parse_status(path.read_text(encoding="utf-8")) + + +def pack(installed, *, root=ROOT): + pool = _pool(root) + previous = read_index(pool)["files"] + current = inventory(pool) + changed = {name: record for name, record in current.items() + if any(previous.get(name, {}).get(field) != record[field] for field in ("size", "sha256"))} + if sum(record["size"] for record in changed.values()) > CONTRIBUTION_LIMIT: + raise ValueError("vcpkg contribution exceeds its payload limit") + used = used_archives(installed) + manifest = {"schema": 1, "files": changed, "used": used} + encoded = _encode(manifest) + if len(encoded) > METADATA_LIMIT: + raise ValueError("vcpkg contribution metadata exceeds its size limit") + output = Path(root).absolute() / "build/cache/vcpkg-artifacts/contribution.tar" + _no_links(output) + output.parent.mkdir(parents=True, exist_ok=True) + output.unlink(missing_ok=True) + if changed or used: + with tarfile.open(output, "w", format=tarfile.PAX_FORMAT) as archive: + member = tarfile.TarInfo(MANIFEST_NAME) + member.size, member.mode = len(encoded), 0o644 + archive.addfile(member, io.BytesIO(encoded)) + for name, record in sorted(changed.items()): + member = tarfile.TarInfo(name) + member.size, member.mode = record["size"], 0o644 + with (pool / name).open("rb") as source: + archive.addfile(member, source) + return {"has-content": str(bool(changed or used)).lower(), "files": str(len(changed)), "used": str(len(used))} + + +def _manifest(value): + if (not isinstance(value, dict) or set(value) != {"schema", "files", "used"} + or type(value["schema"]) is not int or value["schema"] != 1): + raise ValueError("Unsupported vcpkg contribution schema") + _records(value["files"], indexed=False) + used = value["used"] + if not isinstance(used, list) or len(used) > FILE_LIMIT: + raise ValueError("Invalid vcpkg contribution usage metadata") + seen = set() + for name in used: + _payload_path(name) + if not ARCHIVE.fullmatch(name) or name in seen: + raise ValueError("Invalid or duplicate vcpkg contribution archive usage") + seen.add(name) + return value + + +def _contribution(path, destination): + if path.stat().st_size > TAR_LIMIT: + raise ValueError("vcpkg contribution tar exceeds its size limit") + with tarfile.open(path, "r:", tarinfo=_BoundedTarInfo) as archive: + members, names = {}, {} + size = 0 + for member in archive: + _register(member.name, names) + if (not member.isfile() or member.name in members or member.size < 0 + or not math.isfinite(member.mtime) or member.mtime < 0): + raise ValueError(f"Invalid vcpkg contribution member: {member.name!r}") + if member.name == MANIFEST_NAME: + if member.size > METADATA_LIMIT: + raise ValueError("vcpkg contribution metadata exceeds its size limit") + else: + _payload_path(member.name) + size += member.size + if len(members) >= FILE_LIMIT + 1 or size > CONTRIBUTION_LIMIT: + raise ValueError("vcpkg contribution exceeds its payload or file-count limit") + members[member.name] = member + if MANIFEST_NAME not in members: + raise ValueError("vcpkg contribution is missing its manifest") + with archive.extractfile(members[MANIFEST_NAME]) as source: + manifest = _manifest(_decode(source.read())) + if set(members) - {MANIFEST_NAME} != set(manifest["files"]): + raise ValueError("vcpkg contribution files do not match its manifest") + for name, record in sorted(manifest["files"].items()): + member = members[name] + if member.size != record["size"]: + raise ValueError(f"vcpkg contribution size mismatch: {name!r}") + target = destination / name + target.parent.mkdir(parents=True, exist_ok=True) + with archive.extractfile(member) as source, target.open("wb") as output: + shutil.copyfileobj(source, output) + if _digest(target) != record["sha256"]: + raise ValueError(f"vcpkg contribution digest mismatch: {name!r}") + return manifest + + +def trim(index, *, limit_bytes=DEFAULT_LIMIT, used=frozenset()): + """Return an index within the byte cap, preferring the observed working set.""" + if type(limit_bytes) is not int or limit_bytes <= 0: + raise ValueError("The vcpkg pool byte limit must be positive") + files = {name: dict(record) for name, record in index["files"].items()} + result = {"schema": 1, "files": files} + oversized = {name for name, record in files.items() + if record["size"] + len(_encode({"schema": 1, "files": {name: record}})) > limit_bytes} + if files and len(oversized) == len(files): + raise ValueError("The vcpkg pool limit cannot retain even one entry") + for name in oversized: + del files[name] + total = sum(record["size"] for record in files.values()) + candidates = sorted(files, key=lambda name: (name in used, files[name]["last_used"], name)) + for name in candidates: + if total + len(_encode(result)) <= limit_bytes: + break + if len(files) == 1: + raise ValueError("The vcpkg pool limit cannot retain even one remaining entry") + total -= files.pop(name)["size"] + if total + len(_encode(result)) > limit_bytes: + raise ValueError("The vcpkg pool metadata exceeds the byte limit") + return result + + +def merge(contributions, *, root=ROOT, limit_bytes=DEFAULT_LIMIT, now=None): + pool = _pool(root) + original = read_index(pool) + current = inventory(pool) + indexed = original["files"] + if (pool / INDEX_NAME).exists() and (set(current) != set(indexed) or any( + any(record[field] != indexed[name][field] for field in ("sha256", "size")) + for name, record in current.items())): + raise ValueError("The restored vcpkg pool does not match its index") + timestamp = time.time() if now is None else now + if type(timestamp) not in {int, float} or not math.isfinite(timestamp) or timestamp < 0: + raise ValueError("The vcpkg publisher time must be finite and nonnegative") + day = int(timestamp // 86400) + result = {"schema": 1, "files": {name: {**record, "last_used": indexed.get(name, {}).get("last_used", 0)} + for name, record in current.items()}} + archives = _files(contributions) + if len(archives) > CONTRIBUTION_COUNT or sum(path.stat().st_size for path in archives.values()) > AGGREGATE_LIMIT: + raise ValueError("vcpkg contributions exceed their aggregate count or size limit") + if any(path.name != "contribution.tar" for path in archives.values()): + raise ValueError("Expected only contribution.tar vcpkg artifacts") + used = set() + sources = {} + names = {} + for name in current: + _register(name, names) + # Validate every artifact, including hashes, before changing the restored pool. + with tempfile.TemporaryDirectory(prefix="cheryl-vcpkg-contributions-") as temporary: + for number, path in enumerate(sorted(archives.values())): + staging = Path(temporary) / str(number) + manifest = _contribution(path, staging) + used.update(manifest["used"]) + for name, record in sorted(manifest["files"].items()): + _register(name, names) + if name.startswith("archives/") and name in result["files"]: + # ABI identity is stable even when independent ZIP serializations differ. + result["files"][name]["last_used"] = day + continue + result["files"][name] = {**record, "last_used": day} + if current.get(name) != record: + sources[name] = staging / name + else: + sources.pop(name, None) + for name in used: + if name in result["files"]: + result["files"][name]["last_used"] = day + _records(result["files"], indexed=True) + retained_before_trim = len(result["files"]) + result = trim(result, limit_bytes=limit_bytes, used=used) + encoded = _encode(result) + if len(encoded) > METADATA_LIMIT: + raise ValueError("vcpkg pool index exceeds its size limit") + pool.mkdir(parents=True, exist_ok=True) + for name, source in sorted(sources.items()): + if name in result["files"]: + target = pool / name + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(source, target) + removed = set(current) - set(result["files"]) + for name in sorted(removed): + (pool / name).unlink() + changed = original != result or not (pool / INDEX_NAME).exists() + if changed: + temporary_index = pool / "pool-index.tmp" + temporary_index.write_bytes(encoded) + temporary_index.replace(pool / INDEX_NAME) + return {"changed": str(changed).lower(), "files": str(len(result["files"])), + "removed": str(retained_before_trim - len(result["files"])), + "bytes": str(sum(record["size"] for record in result["files"].values()) + len(encoded))} + + +def _emit(values, variable): + if any("\r" in str(value) or "\n" in str(value) for value in values.values()): + raise ValueError("vcpkg cache outputs must be single-line") + with Path(os.environ[variable]).open("a", encoding="utf-8") as output: + for name, value in values.items(): + output.write(f"{name}={value}\n") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + commands = parser.add_subparsers(dest="command", required=True) + commands.add_parser("describe") + package = commands.add_parser("pack") + package.add_argument("--installed", type=Path, required=True) + publication = commands.add_parser("merge") + publication.add_argument("--contributions", type=Path, required=True) + publication.add_argument("--limit-gib", type=float, default=2) + args = parser.parse_args() + if args.command == "describe": + result = describe() + _emit({"CHERYL_CI_VCPKG_CACHE_DIR": result["cache-path"]}, "GITHUB_ENV") + elif args.command == "pack": + result = pack(args.installed) + else: + if not math.isfinite(args.limit_gib) or args.limit_gib <= 0: + parser.error("--limit-gib must be finite and positive") + result = merge(args.contributions, limit_bytes=int(args.limit_gib * GIB)) + _emit(result, "GITHUB_OUTPUT") + print("vcpkg cache " + args.command + ": " + json.dumps(result, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/.github/ci/vcpkg_publication.py b/.github/ci/vcpkg_publication.py new file mode 100644 index 00000000..5d0f9419 --- /dev/null +++ b/.github/ci/vcpkg_publication.py @@ -0,0 +1,156 @@ +"""Admit trusted vcpkg contributions and retire superseded global snapshots.""" + +import argparse +from datetime import datetime +import json +import os +from pathlib import Path +import re + +from github_api import GitHubApi +from vcpkg_cache import TAR_LIMIT + + +REPOSITORY = "cppcooper/cheryl-engine" +ARTIFACT = re.compile(r"vcpkg-cache-X64-([A-Za-z0-9_.-]+)-([1-9][0-9]*)") +PREFIX = "vcpkg-v1-Windows-X64-msvc-" +SNAPSHOT = re.compile(re.escape(PREFIX) + r"[1-9][0-9]*-[1-9][0-9]*") +CONTRIBUTION_LIMIT = 32 +ARTIFACT_LIMIT = TAR_LIMIT + 1024 ** 2 +DOWNLOAD_LIMIT = 3 * 1024 ** 3 + + +def positive(value): + if type(value) is not int or value <= 0: + raise ValueError("Expected a positive GitHub ID or run attempt") + return value + + +def publication_branch(api, event, environment): + repository = event.get("repository") or {} + branch = repository.get("default_branch") + if (api.repository != REPOSITORY or repository.get("full_name") != REPOSITORY + or environment.get("GITHUB_REPOSITORY") != REPOSITORY + or environment.get("GITHUB_EVENT_NAME") != "workflow_run" + or not isinstance(branch, str) or environment.get("GITHUB_REF") != "refs/heads/" + branch): + return None + return branch + + +def admit(api, event, environment): + if publication_branch(api, event, environment) is None: + return {"allowed": "false", "reason": "Publication requires the default-branch workflow"} + trigger = event.get("workflow_run") or {} + identifier, attempt = positive(trigger.get("id")), positive(trigger.get("run_attempt")) + run = api.request("GET", f"/actions/runs/{identifier}/attempts/{attempt}") + workflow = api.request("GET", "/actions/workflows/ci.yml") + if (run.get("id") != identifier or run.get("run_attempt") != attempt + or positive(run.get("workflow_id")) != positive(workflow.get("id")) + or not isinstance(run.get("head_sha"), str) or not re.fullmatch(r"[0-9a-f]{40}", run["head_sha"]) + or run["head_sha"] != trigger.get("head_sha") + or run.get("path", "").split("@", 1)[0] != ".github/workflows/ci.yml" + or run.get("status") != "completed"): + raise ValueError("The completed source run does not match canonical CI") + if (run.get("head_repository") or {}).get("full_name") != REPOSITORY: + return {"allowed": "false", "reason": "Fork runs cannot publish shared dependency binaries"} + if run.get("event") == "pull_request": + pulls = run.get("pull_requests") or [] + if not pulls: + return {"allowed": "false", "reason": "Cannot establish the source PR repository"} + for entry in pulls: + pull = api.request("GET", f"/pulls/{positive(entry.get('number'))}") + if ((pull.get("head") or {}).get("repo") or {}).get("full_name") != REPOSITORY: + return {"allowed": "false", "reason": "Fork PRs cannot publish shared dependency binaries"} + elif run.get("event") not in {"push", "workflow_dispatch"}: + return {"allowed": "false", "reason": "Unsupported source workflow event"} + + artifacts = [] + for artifact in api.pages(f"/actions/runs/{identifier}/artifacts", field="artifacts"): + name = artifact.get("name", "") + match = ARTIFACT.fullmatch(name) if isinstance(name, str) else None + if not match or artifact.get("expired") is not False or int(match[2]) > attempt: + continue + positive(artifact.get("id")) + size = artifact.get("size_in_bytes") + if type(size) is not int or not 0 < size <= ARTIFACT_LIMIT: + raise ValueError("A dependency contribution exceeds its artifact size limit") + artifacts.append((artifact, match[1], int(match[2]))) + if len(artifacts) > CONTRIBUTION_LIMIT: + raise ValueError("Too many dependency contributions in one CI run") + if len({artifact["id"] for artifact, *_ in artifacts}) != len(artifacts): + raise ValueError("Dependency contribution IDs must be unique") + jobs = {} + accepted = {} + for artifact, name, source_attempt in artifacts: + if source_attempt not in jobs: + jobs[source_attempt] = api.pages(f"/actions/runs/{identifier}/attempts/{source_attempt}/jobs", field="jobs") + prepared = [job for job in jobs[source_attempt] if job.get("name") == name + and any(step.get("name") == "Prepare dependencies" and step.get("conclusion") == "success" + for step in job.get("steps", []))] + if len(prepared) == 1: + candidate = (source_attempt, artifact["id"], artifact["size_in_bytes"]) + if name not in accepted or candidate[:2] > accepted[name][:2]: + accepted[name] = candidate + if sum(size for _, _, size in accepted.values()) > DOWNLOAD_LIMIT: + raise ValueError("Prepared dependency artifacts exceed the total download limit") + return {"allowed": str(bool(accepted)).lower(), "source-run-id": str(identifier), + "artifact-ids": ",".join(map(str, sorted(identifier for _, identifier, _ in accepted.values()))), + "reason": "Prepared dependency contributions" if accepted else "No successful dependency preparations"} + + +def timestamp(value): + if not isinstance(value, str): + raise ValueError("Cache creation timestamps must be ISO 8601 strings") + result = datetime.fromisoformat(value.replace("Z", "+00:00")) + if result.tzinfo is None: + raise ValueError("Cache creation timestamps must include a timezone") + return result + + +def retire(api, event, environment): + branch = publication_branch(api, event, environment) + if branch is None: + raise ValueError("Snapshot retirement requires default-branch publication") + current = PREFIX + environment["GITHUB_RUN_ID"] + "-" + environment["GITHUB_RUN_ATTEMPT"] + if not SNAPSHOT.fullmatch(current): + raise ValueError("Invalid current shared-cache snapshot key") + caches = api.pages("/actions/caches", field="actions_caches", ref="refs/heads/" + branch, key=PREFIX) + matching = [cache for cache in caches if cache.get("key") == current + and cache.get("ref") == "refs/heads/" + branch] + if len(matching) != 1: + raise ValueError("The new shared snapshot must be saved before retiring old snapshots") + saved = matching[0] + positive(saved.get("id")) + if not isinstance(saved.get("version"), str) or not saved["version"]: + raise ValueError("The saved snapshot has no cache version") + created = timestamp(saved.get("created_at")) + removed = [] + for cache in caches: + if (cache.get("ref") == saved["ref"] and cache.get("version") == saved["version"] + and SNAPSHOT.fullmatch(cache.get("key", "")) and cache["key"] != current + and timestamp(cache.get("created_at")) <= created): + removed.append(positive(cache.get("id"))) + for identifier in removed: + api.request("DELETE", f"/actions/caches/{identifier}", missing_ok=True) + return {"removed": str(len(removed))} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("operation", choices=("admit", "retire")) + args = parser.parse_args() + api = GitHubApi(os.environ.get("GITHUB_REPOSITORY"), os.environ.get("GITHUB_TOKEN")) + event = json.loads(Path(os.environ["GITHUB_EVENT_PATH"]).read_text(encoding="utf-8")) + if args.operation == "admit": + result = admit(api, event, os.environ) + else: + result = retire(api, event, os.environ) + print(json.dumps(result, sort_keys=True)) + if os.environ.get("GITHUB_OUTPUT"): + with Path(os.environ["GITHUB_OUTPUT"]).open("a", encoding="utf-8") as output: + for name, value in result.items(): + output.write(f"{name}={value}\n") + + +if __name__ == "__main__": + main() diff --git a/.github/ci/verify_sdk.py b/.github/ci/verify_sdk.py index 5edc02ea..c761de22 100644 --- a/.github/ci/verify_sdk.py +++ b/.github/ci/verify_sdk.py @@ -5,11 +5,43 @@ import json import os from pathlib import Path +import re +import shutil import subprocess import tarfile import zipfile -from package import check_contents +from package import check_contents, check_runtime_imports, system_runtime + + +def check_relocated_runtimes(sdk, metadata): + if metadata["system"] != "Linux": + return + files = {path for path in (sdk / "lib").glob("*.so*") if path.is_file()} + libraries = {path.resolve() for path in files} + required = {path.name for path in files} + for library in sorted(libraries): + dynamic = subprocess.run(["readelf", "-d", library], check=True, capture_output=True, text=True).stdout + paths = re.findall(r"\((?:RUNPATH|RPATH)\).*?\[(.*?)\]", dynamic) + if paths != ["$ORIGIN"]: + raise ValueError(f"SDK library requires a relative runtime search path: {library.name}: {paths}") + required.update(re.findall(r"\(NEEDED\).*?\[([^]]+)\]", dynamic)) + # ldd also lists the private dependencies of allowed host interfaces. Those + # remain host-owned, while every packaged runtime and direct SDK import must + # resolve within the SDK unless it is itself an allowed system interface. + for library in sorted(libraries): + loaded = subprocess.run(["ldd", library], check=True, capture_output=True, text=True).stdout + for line in loaded.splitlines(): + match = re.match(r"\s*(\S+)\s+=>\s+(.+)", line) + if not match: + continue + name, location = match.groups() + location = re.sub(r"\s+\(0x[0-9a-fA-F]+\)\s*$", "", location).strip() + if location == "not found": + raise ValueError(f"Relocated SDK cannot load {name} required by {library.name}") + if (name in required and not system_runtime(name, "Linux") + and not Path(location).resolve().is_relative_to(sdk.resolve())): + raise ValueError(f"Relocated SDK loads {name} outside its package: {location}") def main(): @@ -19,52 +51,79 @@ def main(): parser.add_argument("--build-dir", type=Path, required=True) parser.add_argument("--producer-build", type=Path, required=True) parser.add_argument("--jobs", type=int, default=os.cpu_count() or 1) + parser.add_argument("--phase", choices=("all", "build", "test"), default="all") args = parser.parse_args() source = args.source.resolve() packages = args.packages.resolve() build = args.build_dir.resolve() - digest, name = (packages / "SHA256SUMS").read_text(encoding="utf-8").strip().split(" ") - archive_path = packages / name - if archive_path.parent != packages: - raise ValueError("Invalid archive filename") - with archive_path.open("rb") as contents: - if hashlib.file_digest(contents, "sha256").hexdigest() != digest: - raise ValueError("SDK checksum mismatch") + hermetic_build = build.parent / (build.name + "-hermetic") + hermetic_source = build.parent / (build.name + "-standalone-source") extracted = build.parent / (build.name + "-relocated-sdk") - extracted.mkdir(parents=True) - if archive_path.suffix == ".zip": - with zipfile.ZipFile(archive_path) as archive: - for name in archive.namelist(): - if not (extracted / name).resolve().is_relative_to(extracted.resolve()): - raise ValueError("Invalid SDK archive path") - archive.extractall(extracted) - else: - with tarfile.open(archive_path) as archive: - archive.extractall(extracted, filter="data") + if args.phase != "test": + digest, name = (packages / "SHA256SUMS").read_text(encoding="utf-8").strip().split(" ") + archive_path = packages / name + if archive_path.parent != packages: + raise ValueError("Invalid archive filename") + with archive_path.open("rb") as contents: + if hashlib.file_digest(contents, "sha256").hexdigest() != digest: + raise ValueError("SDK checksum mismatch") + extracted.mkdir(parents=True) + if archive_path.suffix == ".zip": + with zipfile.ZipFile(archive_path) as archive: + for name in archive.namelist(): + if not (extracted / name).resolve().is_relative_to(extracted.resolve()): + raise ValueError("Invalid SDK archive path") + archive.extractall(extracted) + else: + with tarfile.open(archive_path) as archive: + archive.extractall(extracted, filter="data") roots = list(extracted.iterdir()) if len(roots) != 1: raise ValueError("Expected one SDK root") sdk = roots[0] - check_contents(sdk) metadata = json.loads((sdk / "SDK-MANIFEST.json").read_text(encoding="utf-8")) - # Source and producer build paths must never be required by the import files. - for config in sdk.rglob("*.cmake"): - contents = config.read_text(encoding="utf-8") - if any(path.resolve().as_posix() in contents for path in (source, args.producer_build)): - raise ValueError(f"SDK leaks a producer path: {config}") + if args.phase != "test": + check_contents(sdk, metadata) + check_runtime_imports(sdk, metadata) + check_relocated_runtimes(sdk, metadata) + # Source and producer build paths must never be required by the import files. + for config in sdk.rglob("*.cmake"): + contents = config.read_text(encoding="utf-8") + if any(path.resolve().as_posix() in contents for path in (source, args.producer_build)): + raise ValueError(f"SDK leaks a producer path: {config}") environment = os.environ.copy() if metadata["system"] == "Windows": - environment["PATH"] = str(sdk / "dependencies/bin") + os.pathsep + environment["PATH"] - commands = [ - ["cmake", "-S", source / ".github/ci/sdk-consumer", "-B", build, "-G", "Ninja", - f"-DCMAKE_PREFIX_PATH={sdk}", f"-DCHERYL_REPOSITORY_ROOT={source.as_posix()}", - f"-DCMAKE_BUILD_TYPE={metadata['configuration']}"], - ["cmake", "--build", build, "--parallel", str(args.jobs)], - ["ctest", "--test-dir", build, "--parallel", str(args.jobs), "--no-tests=error", "--timeout", "120", - "--output-on-failure", "--output-junit", build / "junit.xml"]] - if metadata["system"] == "Windows" and environment.get("CMAKE_CXX_COMPILER_LAUNCHER") == "ccache": - commands[0] += ["-DCMAKE_POLICY_DEFAULT_CMP0141=NEW", - "-DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=$<$:Embedded>"] + environment["PATH"] = os.pathsep.join((str(sdk / "bin"), environment.get("PATH", ""))) + commands = [] + if args.phase != "test": + configure = ["cmake", "-S", source / ".github/ci/sdk-consumer", "-B", build, "-G", "Ninja", + f"-DCMAKE_PREFIX_PATH={sdk}", f"-DCHERYL_REPOSITORY_ROOT={source.as_posix()}", + f"-DCMAKE_BUILD_TYPE={metadata['configuration']}"] + if metadata["system"] == "Windows" and environment.get("CMAKE_CXX_COMPILER_LAUNCHER") == "ccache": + configure += ["-DCMAKE_POLICY_DEFAULT_CMP0141=NEW", + "-DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=$<$:Embedded>"] + # Copy a complete tiny project outside the repository. It has no source + # header paths, test fixtures, toolchain file, or producer target access. + shutil.copytree(source / ".github/ci/sdk-consumer/standalone", hermetic_source) + hermetic_configure = ["cmake", "-S", hermetic_source, "-B", hermetic_build, "-G", "Ninja", + f"-DCMAKE_PREFIX_PATH={sdk}", f"-DCMAKE_BUILD_TYPE={metadata['configuration']}", + "-DCMAKE_FIND_USE_PACKAGE_REGISTRY=OFF", "-DCMAKE_FIND_USE_SYSTEM_PACKAGE_REGISTRY=OFF"] + # Native implementations come from the SDK for either Engine linkage. + for dependency in ("Freetype", "harfbuzz", "ICU", "hwloc", "hidapi"): + hermetic_configure.append(f"-DCMAKE_DISABLE_FIND_PACKAGE_{dependency}=TRUE") + if metadata.get("linkage") == "shared": + for dependency in ("PkgConfig", "OpenGL", "X11"): + hermetic_configure.append(f"-DCMAKE_DISABLE_FIND_PACKAGE_{dependency}=TRUE") + if metadata["system"] == "Windows": + hermetic_configure.append("-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded$<$:Debug>DLL") + commands.extend([configure, hermetic_configure, + ["cmake", "--build", build, "--parallel", str(args.jobs)], + ["cmake", "--build", hermetic_build, "--parallel", str(args.jobs)]]) + if args.phase != "build": + commands.append(["ctest", "--test-dir", build, "--parallel", str(args.jobs), "--no-tests=error", "--timeout", "120", + "--output-on-failure", "--output-junit", build / "junit.xml"]) + commands.append(["ctest", "--test-dir", hermetic_build, "--parallel", str(args.jobs), "--no-tests=error", "--timeout", "120", + "--output-on-failure", "--output-junit", hermetic_build / "junit.xml"]) for command in commands: print("+ " + subprocess.list2cmdline(list(map(str, command))), flush=True) subprocess.run(command, env=environment, check=True) diff --git a/.github/ci/versions.py b/.github/ci/versions.py new file mode 100644 index 00000000..5af5a826 --- /dev/null +++ b/.github/ci/versions.py @@ -0,0 +1,421 @@ +"""Prepare reviewed component version changes without writing protected branches.""" + +import argparse +import base64 +import json +import os +from pathlib import Path +import re +import subprocess + + +ROOT = Path(__file__).resolve().parents[2] +REPOSITORY = "cppcooper/cheryl-engine" +VERSION_FILE = "cmake/CherylVersions.cmake" +STATE_FILE = ".github/ci/version-state.json" +OWNED_FILES = {VERSION_FILE, STATE_FILE} +PROJECT = "VERSION_PROJECT_CHERYL" +ENGINE = "VERSION_PROJECT_ENGINE" +MARKER = "" +SEMVER = re.compile(r"(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)") +SHA = re.compile(r"[0-9a-f]{40}") +VERSION_SETTING = re.compile( + r'(?m)^(?P[ \t]*set\([ \t]*(?PVERSION_PROJECT_[A-Z0-9_]+)[ \t]+)' + r'(?P"?)(?P[^"\s)]+)(?P=quote)(?P[ \t]*\)[^\r\n]*)(?P\r?)$' +) +VERSION_ALIAS = re.compile(r"\$\{(VERSION_PROJECT_[A-Z0-9_]+)\}") +COMPONENT_PATHS = { + "projects/engine/": ENGINE, + "projects/modules/platform/native-glfw/": "VERSION_PROJECT_MODULE_NATIVE_GLFW", + "projects/modules/platform/debug-terminal-linux/": "VERSION_PROJECT_MODULE_DEBUG_TERMINAL_LINUX", + "projects/modules/graphics/opengl/": "VERSION_PROJECT_MODULE_OPENGL", + "projects/modules/ui/tgui/": "VERSION_PROJECT_MODULE_UI_TGUI", + "projects/modules/ui/rmlui/": "VERSION_PROJECT_MODULE_UI_RMLUI", + "projects/modules/audio/miniaudio/": "VERSION_PROJECT_MODULE_AUDIO_MINIAUDIO", + "projects/apps/demo/": "VERSION_PROJECT_APP_DEMO", +} +EXTERNAL_OWNERS = { + "backward-cpp": ENGINE, "cli11": ENGINE, "ctti": ENGINE, "glm": ENGINE, + "json": ENGINE, "spdlog": ENGINE, "stb": ENGINE, + "glfw": "VERSION_PROJECT_MODULE_NATIVE_GLFW", "gainput": "VERSION_PROJECT_MODULE_NATIVE_GLFW", + "glad": "VERSION_PROJECT_MODULE_OPENGL", "tgui": "VERSION_PROJECT_MODULE_UI_TGUI", + "rmlui": "VERSION_PROJECT_MODULE_UI_RMLUI", "miniaudio": "VERSION_PROJECT_MODULE_AUDIO_MINIAUDIO", +} + + +def version_tuple(value): + match = SEMVER.fullmatch(value) if isinstance(value, str) else None + if not match: + raise ValueError(f"Expected a three-part semantic version, got {value!r}") + return tuple(map(int, match.groups())) + + +def read_versions(text): + values = {} + for match in VERSION_SETTING.finditer(text): + name, value = match.group("name", "version") + if name in values: + raise ValueError(f"Duplicate version setting: {name}") + alias = VERSION_ALIAS.fullmatch(value) + if alias: + referenced = alias[1] + if referenced not in values: + raise ValueError(f"Version alias must reference a previously declared version: {name}") + value = values[referenced] + version_tuple(value) + values[name] = value + return values + + +def replace_versions(text, values): + existing = read_versions(text) + for name, value in values.items(): + if name not in existing: + raise ValueError(f"Unknown version setting: {name}") + version_tuple(value) + + def replace(match): + value = values.get(match.group("name"), match.group("version")) + return (match.group("prefix") + match.group("quote") + value + match.group("quote") + + match.group("suffix") + match.group("cr")) + + return VERSION_SETTING.sub(replace, text) + + +def patch_version(value): + major, minor, patch = version_tuple(value) + return f"{major}.{minor}.{patch + 1}" + + +def release_version(current, stable): + major, minor, _ = version_tuple(current) + stable_major, stable_minor, _ = version_tuple(stable) + if (major, minor) < (stable_major, stable_minor): + raise ValueError("The project version is behind the published stable release") + if (major, minor) > (stable_major, stable_minor): + return f"{major}.{minor}.0" + return f"{major}.{minor + 1}.0" + + +def affected_components(paths): + affected = set() + for path in paths: + if path in OWNED_FILES or path.endswith((".md", ".rst")) or "/tests/" in path or "/docs/" in path: + continue + for prefix, component in COMPONENT_PATHS.items(): + if path.startswith(prefix): + affected.add(component) + if path.startswith("cmake/"): + affected.add(ENGINE) + if path == "assets" or path.startswith("assets/"): + affected.add(ENGINE) + if path.startswith("extern/"): + owner = EXTERNAL_OWNERS.get(path.split("/", 2)[1]) + if owner: + affected.add(owner) + return affected + + +def commit_id(value): + if not isinstance(value, str) or not SHA.fullmatch(value): + raise ValueError("Expected a full Git commit ID") + return value + + +def validate_state(value): + if not isinstance(value, dict) or value.get("format") != 1 or value.get("kind") not in {"maintenance", "release"}: + raise ValueError("Invalid version-maintenance state") + commit_id(value.get("source_revision")) + commit_id(value.get("stable_revision")) + if not isinstance(value.get("stable_tag"), str) or not value["stable_tag"].startswith("v"): + raise ValueError("Invalid stable tag in version-maintenance state") + version_tuple(value["stable_tag"][1:]) + values = value.get("versions") + if not isinstance(values, dict) or PROJECT not in values or ENGINE not in values: + raise ValueError("Version-maintenance state is missing component versions") + for name, version in values.items(): + if not re.fullmatch(r"VERSION_PROJECT_[A-Z0-9_]+", name): + raise ValueError("Invalid component name in version-maintenance state") + version_tuple(version) + return value + + +class Git: + def __init__(self, source=ROOT): + self.source = Path(source) + + def output(self, *arguments): + return subprocess.check_output(["git", *arguments], cwd=self.source, text=True) + + def resolve(self, ref): + return commit_id(self.output("rev-parse", "--verify", ref + "^{commit}").strip()) + + def text(self, revision, path, *, optional=False): + commit_id(revision) + try: + return self.output("show", f"{revision}:{path}") + except subprocess.CalledProcessError: + if optional: + return None + raise + + def ancestor(self, before, after): + result = subprocess.run(["git", "merge-base", "--is-ancestor", commit_id(before), commit_id(after)], + cwd=self.source, capture_output=True, text=True) + if result.returncode not in (0, 1): + raise RuntimeError("Cannot establish version-maintenance Git ancestry") + return result.returncode == 0 + + def parents(self, revision): + return self.output("rev-list", "--parents", "-n", "1", commit_id(revision)).split()[1:] + + def integrations(self, before, after): + return self.output("rev-list", "--first-parent", "--reverse", f"{commit_id(before)}..{commit_id(after)}").split() + + def changed(self, before, after): + return set(filter(None, self.output("diff", "--no-renames", "--name-only", "-z", + commit_id(before), commit_id(after)).split("\0"))) + + +class Automation: + def __init__(self, api, git): + self.api = api + self.git = git + + def stable_release(self): + releases = [] + for release in self.api.pages("/releases"): + tag = release.get("tag_name", "") + if (not release.get("draft") and not release.get("prerelease") and release.get("published_at") + and isinstance(tag, str) and tag.startswith("v") and SEMVER.fullmatch(tag[1:]) + and version_tuple(tag[1:]) >= (4, 0, 0)): + releases.append(release) + if not releases: + return None + release = max(releases, key=lambda entry: version_tuple(entry["tag_name"][1:])) + revision = self.git.resolve("refs/tags/" + release["tag_name"]) + if not self.git.ancestor(revision, self.git.resolve("refs/remotes/origin/main")): + raise ValueError("The published stable tag is not part of main; review release history before versioning") + return {"tag": release["tag_name"], "revision": revision} + + def contents(self, revision, path, *, optional=False): + result = self.api.request("GET", "/contents/" + path, ref=commit_id(revision), missing_ok=optional) + if result is None and optional: + return None + if not isinstance(result, dict) or result.get("encoding") != "base64": + raise ValueError(f"Expected a repository file: {path}") + return base64.b64decode(result["content"]).decode("utf-8") + + def tree(self, revision): + commit = self.api.request("GET", "/git/commits/" + commit_id(revision)) + tree = self.api.request("GET", "/git/trees/" + commit["tree"]["sha"], recursive=1) + if tree.get("truncated"): + raise ValueError("Cannot validate a truncated version branch tree") + return {entry["path"]: (entry["mode"], entry["type"], entry["sha"]) + for entry in tree["tree"] if entry["path"] not in OWNED_FILES and entry["type"] != "tree"} + + def pending(self, branch, head, kind): + ref = self.api.request("GET", "/git/ref/heads/" + branch, missing_ok=True) + if ref is None: + return None + revision = commit_id(ref["object"]["sha"]) + raw_state = self.contents(revision, STATE_FILE, optional=True) + if raw_state is None: + raise ValueError(f"Refusing to overwrite {branch}: it has no owned version-maintenance state") + state = validate_state(json.loads(raw_state)) + if state["kind"] != kind or not self.git.ancestor(state["source_revision"], head): + raise ValueError(f"Refusing to overwrite {branch}: its version state does not match develop history") + source = state["source_revision"] + source_text = self.git.text(source, VERSION_FILE) + if (self.contents(revision, VERSION_FILE) != replace_versions(source_text, state["versions"]) + or self.tree(revision) != self.tree(source)): + raise ValueError(f"Refusing to overwrite {branch}: it contains changes outside generated version state") + return {"revision": revision, "state": state} + + def initial_state(self, stable, head): + revision = stable["revision"] + if not self.git.ancestor(revision, head): + parents = self.git.parents(revision) + if len(parents) < 2 or not self.git.ancestor(parents[1], head): + raise ValueError("Cannot identify the develop revision promoted by the stable release; review branch ancestry") + revision = parents[1] + return {"format": 1, "kind": "maintenance", "source_revision": revision, + "versions": read_versions(self.git.text(revision, VERSION_FILE)), + "stable_tag": stable["tag"], "stable_revision": stable["revision"]} + + def current_state(self, stable, head, pending): + raw = self.git.text(head, STATE_FILE, optional=True) + state = validate_state(json.loads(raw)) if raw else self.initial_state(stable, head) + if not self.git.ancestor(state["source_revision"], head): + raise ValueError("The saved version watermark is outside develop history; review the rollback before continuing") + if pending: + candidate = pending["state"] + if state["source_revision"] != candidate["source_revision"] and self.git.ancestor( + state["source_revision"], candidate["source_revision"]): + state = candidate + elif not self.git.ancestor(candidate["source_revision"], state["source_revision"]): + raise ValueError("Pending and saved version watermarks diverge; review the version PR") + if state["stable_tag"] == stable["tag"] and state["stable_revision"] != stable["revision"]: + raise ValueError("The published stable tag moved after version maintenance began") + return state + + def planned_versions(self, state, head): + values = dict(state["versions"]) + for revision in self.git.integrations(state["source_revision"], head): + parents = self.git.parents(revision) + if not parents: + raise ValueError("An integration commit has no parent") + before = read_versions(self.git.text(parents[0], VERSION_FILE)) + after = read_versions(self.git.text(revision, VERSION_FILE)) + paths = self.git.changed(parents[0], revision) + if not paths - OWNED_FILES and STATE_FILE in paths: + raw = self.git.text(revision, STATE_FILE, optional=True) + generated = validate_state(json.loads(raw)) if raw else None + if generated and generated["versions"] == after: + # A delayed generated PR must not roll back already accumulated bumps. + if generated["kind"] == "release" and version_tuple(after[PROJECT]) > version_tuple(values[PROJECT]): + values[PROJECT] = after[PROJECT] + continue + manual = {name: value for name, value in after.items() if before.get(name) != value} + values.update(manual) + if not paths - OWNED_FILES: + continue + for name in {PROJECT, *affected_components(paths)} - manual.keys(): + if name not in values: + raise ValueError(f"Missing component version: {name}") + values[name] = patch_version(values[name]) + return values + + def ensure_pull(self, branch, base, title, body, *, draft=False): + pulls = self.api.pages("/pulls", state="open", base=base, head=REPOSITORY.split("/")[0] + ":" + branch) + matches = [pull for pull in pulls if pull["head"]["ref"] == branch + and pull["head"].get("repo", {}).get("full_name") == REPOSITORY and pull["base"]["ref"] == base] + if len(matches) > 1: + raise ValueError(f"More than one open version PR for {branch}") + if matches: + pull = matches[0] + # Keep user release notes and the existing draft state. + update = {"title": title} + if branch.startswith("ci/"): + update["body"] = body + return self.api.request("PATCH", f"/pulls/{pull['number']}", update) + try: + return self.api.request("POST", "/pulls", {"head": branch, "base": base, "title": title, + "body": body, "draft": draft}) + except RuntimeError as error: + raise RuntimeError("Could not create the version PR. Check Settings > Actions > General > Workflow permissions > " + "Allow GitHub Actions to create and approve pull requests, and grant this workflow " + "contents/pull-requests write permissions.") from error + + def propose(self, branch, kind, head, stable, values, pending, title): + source_text = self.git.text(head, VERSION_FILE) + if values == read_versions(source_text): + return None + state = {"format": 1, "kind": kind, "source_revision": head, "versions": values, + "stable_tag": stable["tag"], "stable_revision": stable["revision"]} + files = {VERSION_FILE: replace_versions(source_text, values), STATE_FILE: json.dumps(state, indent=2, sort_keys=True) + "\n"} + if pending: + current = self.api.request("GET", "/git/ref/heads/" + branch) + if current["object"]["sha"] != pending["revision"]: + raise ValueError("The version branch advanced during planning; rerun maintenance") + if all(self.contents(pending["revision"], path) == text for path, text in files.items()): + return self.ensure_pull(branch, "develop", title, MARKER + "\n\nGenerated version changes for review. " + "Approve the GitHub Actions workflows on this PR so its required checks can run.") + source_commit = self.api.request("GET", "/git/commits/" + head) + tree = self.api.request("POST", "/git/trees", {"base_tree": source_commit["tree"]["sha"], "tree": [ + {"path": path, "mode": "100644", "type": "blob", "content": text} for path, text in files.items()]}) + parents = [pending["revision"], head] if pending and pending["revision"] != head else [head] + identity = {"name": "cppcooper", "email": "cppcooper@users.noreply.github.com"} + commit = self.api.request("POST", "/git/commits", {"message": "Updates reviewed component versions", "tree": tree["sha"], + "parents": parents, "author": identity, "committer": identity}) + if pending: + self.api.request("PATCH", "/git/refs/heads/" + branch, {"sha": commit["sha"], "force": False}) + else: + self.api.request("POST", "/git/refs", {"ref": "refs/heads/" + branch, "sha": commit["sha"]}) + return self.ensure_pull(branch, "develop", title, MARKER + "\n\nGenerated version changes for review. " + "Approve the GitHub Actions workflows on this PR so its required checks can run.") + + def maintain(self): + stable = self.stable_release() + if stable is None: + print("Versions remain frozen until the first published stable 4.0.0 release on main.") + return None + head = self.git.resolve("HEAD") + pending = self.pending("ci/version-update", head, "maintenance") + state = self.current_state(stable, head, pending) + values = self.planned_versions(state, head) + return self.propose("ci/version-update", "maintenance", head, stable, values, pending, + "Updates development component versions") + + def prepare_release(self): + head = self.git.resolve("HEAD") + values = read_versions(self.git.text(head, VERSION_FILE)) + stable = self.stable_release() + preparation = None + if stable is None: + target = "4.0.0" + if values.get(PROJECT) != target: + raise ValueError("The first release is frozen at 4.0.0; review the CMake project version") + else: + maintenance = self.pending("ci/version-update", head, "maintenance") + current = self.current_state(stable, head, maintenance) + if self.planned_versions(current, head) != values: + raise ValueError("Merge the development version-update PR before preparing the release") + target = release_version(values[PROJECT], stable["tag"][1:]) + pending = self.pending("ci/release-version", head, "release") + values[PROJECT] = target + preparation = self.propose("ci/release-version", "release", head, stable, values, pending, + "Prepares Release " + target + " versions") + release = self.ensure_pull("develop", "main", "Release " + target, + "Prepare the release from develop after its version changes and required checks pass.", draft=True) + return {"preparation": preparation, "release": release} + + +def check_release(event, git): + pull = event.get("pull_request") if isinstance(event, dict) else None + if not isinstance(pull, dict): + raise ValueError("Release policy requires a pull request event") + if pull.get("base", {}).get("ref") != "main": + return None + head = pull.get("head", {}) + if head.get("ref") != "develop" or head.get("repo", {}).get("full_name") != REPOSITORY: + raise ValueError("Stable release PRs must use this repository's develop branch") + value = read_versions(git.text(commit_id(head.get("sha")), VERSION_FILE)).get(PROJECT) + if value is None or version_tuple(value)[2] != 0 or pull.get("title") != "Release " + value: + raise ValueError("Release PR title must match the CMake project version with a zero patch: Release X.Y.0") + return value + + +def main(arguments=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--source", type=Path, default=ROOT) + commands = parser.add_subparsers(dest="command", required=True) + commands.add_parser("maintain") + commands.add_parser("prepare-release") + check = commands.add_parser("check-release") + check.add_argument("--event", type=Path, default=os.environ.get("GITHUB_EVENT_PATH")) + args = parser.parse_args(arguments) + git = Git(args.source) + if args.command == "check-release": + if not args.event: + parser.error("check-release requires --event or GITHUB_EVENT_PATH") + result = check_release(json.loads(Path(args.event).read_text(encoding="utf-8")), git) + else: + events = {"push", "workflow_dispatch"} if args.command == "maintain" else {"workflow_dispatch"} + if (os.environ.get("GITHUB_REPOSITORY") != REPOSITORY or os.environ.get("GITHUB_REF") != "refs/heads/develop" + or os.environ.get("GITHUB_EVENT_NAME") not in events): + raise ValueError("Version changes require the canonical develop push or manual release-preparation workflow") + if git.resolve("HEAD") != git.resolve("refs/remotes/origin/develop"): + raise ValueError("The checked source is no longer the current develop revision; rerun maintenance") + from github_api import GitHubApi + automation = Automation(GitHubApi(REPOSITORY, os.environ.get("GITHUB_TOKEN")), git) + result = automation.maintain() if args.command == "maintain" else automation.prepare_release() + print(json.dumps(result, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except (KeyError, OSError, RuntimeError, ValueError, subprocess.CalledProcessError) as error: + raise SystemExit(f"Version maintenance failed: {error}") from error diff --git a/.github/ci/windows-hwloc.cmake b/.github/ci/windows-hwloc.cmake index 01e69699..a014638f 100644 --- a/.github/ci/windows-hwloc.cmake +++ b/.github/ci/windows-hwloc.cmake @@ -1,18 +1,2 @@ -# vcpkg's hwloc port provides headers/libraries, not a CMake config package. -# Supply the explicit SDK target already supported by Cheryl's provider contract. -if(PROJECT_NAME STREQUAL "cheryl" AND CHERYL_BUILD_WORKER_TOPOLOGY_HWLOC) - find_path(cheryl_ci_hwloc_include NAMES hwloc.h REQUIRED) - find_library(cheryl_ci_hwloc_library NAMES hwloc libhwloc REQUIRED) - add_library(cheryl_ci_hwloc UNKNOWN IMPORTED GLOBAL) - set_target_properties(cheryl_ci_hwloc PROPERTIES - IMPORTED_LOCATION "${cheryl_ci_hwloc_library}" - INTERFACE_INCLUDE_DIRECTORIES "${cheryl_ci_hwloc_include}") - set(CHERYL_HWLOC_TARGET cheryl_ci_hwloc) -endif() -if(PROJECT_NAME STREQUAL "cheryl" AND CHERYL_BUILD_NATIVE_GLFW) - # Gainput forces HID on for Windows and requests hidapi through FetchContent. - # Declare it first so that request reuses the pinned vcpkg SDK dependency. - include(FetchContent) - FetchContent_Declare(hidapi FIND_PACKAGE_ARGS CONFIG REQUIRED) - FetchContent_MakeAvailable(hidapi) -endif() +# Compatibility entry point for existing CI configurations. +include("${CMAKE_CURRENT_LIST_DIR}/../../tools/dependencies/prepared.cmake") diff --git a/.github/workflows/cache-maintenance.yml b/.github/workflows/cache-maintenance.yml new file mode 100644 index 00000000..f22ec63a --- /dev/null +++ b/.github/workflows/cache-maintenance.yml @@ -0,0 +1,58 @@ +name: Maintain Actions caches + +on: + schedule: + - cron: '23 4 * * *' + workflow_run: + workflows: ['Build and test', 'Documentation'] + types: [completed] + workflow_dispatch: + inputs: + dry_run: + description: 'Preview deletions without changing caches' + type: boolean + default: true + closed_pr: + description: 'Optional closed PR number; leave empty for repository cleanup' + type: string + default: '' + +permissions: + contents: read + +concurrency: + group: actions-cache-maintenance + cancel-in-progress: false + +jobs: + cleanup: + if: github.repository == 'cppcooper/cheryl-engine' + runs-on: ubuntu-24.04 + timeout-minutes: 15 + permissions: + contents: read + actions: write + pull-requests: read + env: + PYTHONDONTWRITEBYTECODE: '1' + steps: + # Elevated events use trusted code, never the triggering PR or workflow's head. + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: refs/heads/${{ github.event.repository.default_branch }} + persist-credentials: false + - name: Maintain cache budget + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + APPLY_CLEANUP: ${{ github.event_name != 'workflow_dispatch' || !inputs.dry_run }} + CLOSED_PR: ${{ inputs.closed_pr || '' }} + run: | + args=(--budget-gib 8) + if [[ "$APPLY_CLEANUP" == 'true' ]]; then + args+=(--apply) + fi + if [[ -n "$CLOSED_PR" ]]; then + args+=(--closed-pr "$CLOSED_PR") + fi + python3 .github/ci/cache_cleanup.py "${args[@]}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f81d8081..1b617b35 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,6 @@ permissions: env: CMAKE_C_COMPILER_LAUNCHER: ccache CMAKE_CXX_COMPILER_LAUNCHER: ccache - CCACHE_MAXSIZE: 1G CCACHE_COMPILERCHECK: content concurrency: @@ -21,20 +20,45 @@ concurrency: cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: + windows-dependencies: + name: windows-dependencies + runs-on: windows-2025 + timeout-minutes: 45 + outputs: + artifact: vcpkg-installed-Windows-X64-${{ github.run_id }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Prepare dependencies + uses: ./.github/actions/prepare-dependencies + with: + profile: modules + cache-job: windows-dependencies + windows-vcpkg: build + - name: Identify prepared Windows installation + run: python .github/ci/dependencies.py windows-stamp --profile modules + - name: Share prepared Windows installation + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: vcpkg-installed-Windows-X64-${{ github.run_id }} + path: build/ci/vcpkg_installed/ + include-hidden-files: true + if-no-files-found: error + overwrite: true + retention-days: 1 + headless: name: ${{ matrix.name }} runs-on: ${{ matrix.os }} timeout-minutes: 60 - env: - CCACHE_DIR: ${{ github.workspace }}/build/ccache strategy: fail-fast: false matrix: include: - - {name: linux-gcc-hwloc, os: ubuntu-24.04, type: Release, hwloc: 'ON', cc: gcc-14, cxx: g++-14} - - {name: linux-gcc-neutral, os: ubuntu-24.04, type: Release, hwloc: 'OFF', cc: gcc-14, cxx: g++-14} - - {name: linux-clang-debug, os: ubuntu-24.04, type: Debug, hwloc: 'ON', cc: clang-18, cxx: clang++-18} - - {name: windows-msvc-hwloc, os: windows-2025, type: RelWithDebInfo, hwloc: 'ON', cc: cl, cxx: cl} + - {name: linux-gcc-hwloc, os: ubuntu-24.04, cache_family: linux-gcc14, type: Release, hwloc: 'ON', cc: gcc-14, cxx: g++-14} + - {name: linux-gcc-neutral, os: ubuntu-24.04, cache_family: linux-gcc14, type: Release, hwloc: 'OFF', cc: gcc-14, cxx: g++-14} + - {name: linux-clang-debug, os: ubuntu-24.04, cache_family: linux-clang18, type: Debug, hwloc: 'ON', cc: clang-18, cxx: clang++-18} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -44,20 +68,31 @@ jobs: uses: ./.github/actions/prepare-dependencies with: profile: headless + cache-job: ${{ matrix.name }} - name: Restore compiler cache id: ccache - uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + uses: ./.github/actions/restore-compiler-cache with: - path: ${{ env.CCACHE_DIR }} - key: ccache-v1-${{ matrix.os }}-${{ runner.arch }}-${{ matrix.name }}-${{ matrix.type }}-${{ github.run_id }}-${{ github.run_attempt }} - restore-keys: | - ccache-v1-${{ matrix.os }}-${{ runner.arch }}-${{ matrix.name }}-${{ matrix.type }}- + prefix: ccache-v3-${{ runner.os }}-${{ runner.arch }}-${{ matrix.cache_family }} - name: Reset compiler cache statistics id: ccache-ready run: ccache --zero-stats - name: Check CI tools - run: python .github/ci/test_ci.py - - name: Build and test Linux + shell: bash + run: | + python .github/ci/test_ci.py + python .github/ci/test_cache_cleanup.py + python .github/ci/test_compiler_cache.py + python .github/ci/test_dependencies.py + python .github/ci/test_components.py + python .github/ci/test_native_notices.py + python .github/ci/test_verify_sdk.py + python .github/ci/test_vcpkg_cache.py + python .github/ci/test_vcpkg_publication.py + python .github/ci/test_github_api.py + python .github/ci/test_versions.py + python .github/ci/test_release.py + - name: Build Linux if: runner.os == 'Linux' env: CC: ${{ matrix.cc }} @@ -65,39 +100,34 @@ jobs: BUILD_TYPE: ${{ matrix.type }} HWLOC: ${{ matrix.hwloc }} CHERYL_STB_ALLOCATION_CFF: /usr/share/fonts/opentype/urw-base35/NimbusSans-Regular.otf - run: python .github/ci/run.py --build-dir build/ci --profile headless --build-type "$BUILD_TYPE" --hwloc "$HWLOC" - - name: Build and test Windows - if: runner.os == 'Windows' - shell: pwsh + run: python .github/ci/run.py --phase build --build-dir build/ci --profile headless --build-type "$BUILD_TYPE" --hwloc "$HWLOC" + - name: Test Linux + if: runner.os == 'Linux' env: BUILD_TYPE: ${{ matrix.type }} HWLOC: ${{ matrix.hwloc }} - run: | - $vswhere = "${env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer/vswhere.exe" - $vs = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath - if (!$vs) { throw 'MSVC x64 tools are unavailable' } - & "$vs/Common7/Tools/Launch-VsDevShell.ps1" -Arch amd64 -HostArch amd64 -SkipAutomaticLocation - python .github/ci/run.py --build-dir build/ci --profile headless --build-type $env:BUILD_TYPE --hwloc $env:HWLOC --vcpkg-root $env:CHERYL_CI_VCPKG_ROOT - if ($LASTEXITCODE) { exit $LASTEXITCODE } - - name: Check dependency cache updates - if: ${{ !cancelled() && runner.os == 'Windows' && steps.dependencies.outcome == 'success' }} - id: dependency-cache-state - run: python .github/ci/dependencies.py cache-state - - name: Save dependency packages and downloads - if: ${{ !cancelled() && steps.dependency-cache-state.outputs.changed == 'true' }} - uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: ${{ env.CHERYL_CI_DEPENDENCY_CACHE_DIR }} - key: ${{ env.CHERYL_CI_DEPENDENCY_CACHE_KEY }} + CHERYL_STB_ALLOCATION_CFF: /usr/share/fonts/opentype/urw-base35/NimbusSans-Regular.otf + run: python .github/ci/run.py --phase test --build-dir build/ci --profile headless --build-type "$BUILD_TYPE" --hwloc "$HWLOC" - name: Report compiler cache statistics if: ${{ !cancelled() && steps.ccache-ready.outcome == 'success' }} run: ccache --show-stats --verbose - - name: Save compiler cache - if: ${{ !cancelled() && steps.ccache-ready.outcome == 'success' && steps.ccache.outputs.cache-hit != 'true' }} - uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + - name: Pack compiler cache additions + if: ${{ !cancelled() && steps.ccache-ready.outcome == 'success' }} + id: compiler-cache-state + shell: bash + env: + CCACHE_DELTA_ARCHIVE: ${{ steps.ccache.outputs.delta-archive }} + run: python .github/ci/compiler_cache.py pack --output "$CCACHE_DELTA_ARCHIVE" + - name: Retain compiler cache additions + if: ${{ !cancelled() && steps.compiler-cache-state.outputs.has-content == 'true' }} + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: - path: ${{ env.CCACHE_DIR }} - key: ${{ steps.ccache.outputs.cache-primary-key }} + name: compiler-cache-${{ runner.os }}-${{ runner.arch }}-${{ matrix.cache_family }}-${{ matrix.name }} + path: ${{ steps.ccache.outputs.delta-archive }} + if-no-files-found: error + compression-level: 0 + overwrite: true + retention-days: 1 - name: Retain results if: always() uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 @@ -114,14 +144,12 @@ jobs: name: ${{ matrix.name }} runs-on: ${{ matrix.os }} timeout-minutes: 90 - env: - CCACHE_DIR: ${{ github.workspace }}/build/ccache strategy: fail-fast: false matrix: include: - - {name: linux-modules, os: ubuntu-24.04, type: Release} - - {name: windows-modules, os: windows-2025, type: RelWithDebInfo} + - {name: linux-modules, os: ubuntu-24.04, cache_family: linux-gcc14, type: Release, linkage: static} + - {name: linux-modules-shared, os: ubuntu-24.04, cache_family: linux-gcc14, type: Release, linkage: shared} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -131,62 +159,201 @@ jobs: uses: ./.github/actions/prepare-dependencies with: profile: modules + cache-job: ${{ matrix.name }} - name: Restore compiler cache id: ccache - uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + uses: ./.github/actions/restore-compiler-cache with: - path: ${{ env.CCACHE_DIR }} - key: ccache-v1-${{ matrix.os }}-${{ runner.arch }}-${{ matrix.name }}-${{ matrix.type }}-${{ github.run_id }}-${{ github.run_attempt }} - restore-keys: | - ccache-v1-${{ matrix.os }}-${{ runner.arch }}-${{ matrix.name }}-${{ matrix.type }}- + prefix: ccache-v3-${{ runner.os }}-${{ runner.arch }}-${{ matrix.cache_family }} - name: Reset compiler cache statistics id: ccache-ready run: ccache --zero-stats - - name: Build and test Linux modules + - name: Build Linux modules and SDK consumers if: runner.os == 'Linux' env: CC: gcc-14 CXX: g++-14 + BUILD_TYPE: ${{ matrix.type }} + LINKAGE: ${{ matrix.linkage }} CHERYL_STB_ALLOCATION_CFF: /usr/share/fonts/opentype/urw-base35/NimbusSans-Regular.otf run: | - python .github/ci/run.py --build-dir build/ci --profile modules --hwloc ON --sdk + python .github/ci/run.py --phase build --build-dir build/ci --profile modules --build-type "$BUILD_TYPE" --linkage "$LINKAGE" --hwloc ON --sdk python .github/ci/package.py --build-dir build/ci --output build/packages - python .github/ci/verify_sdk.py --packages build/packages --build-dir build/sdk-consumer --producer-build build/ci - - name: Build and test Windows modules - if: runner.os == 'Windows' + python .github/ci/verify_sdk.py --phase build --packages build/packages --build-dir build/sdk-consumer --producer-build build/ci + - name: Test Linux modules and SDK consumers + if: runner.os == 'Linux' + env: + BUILD_TYPE: ${{ matrix.type }} + LINKAGE: ${{ matrix.linkage }} + CHERYL_STB_ALLOCATION_CFF: /usr/share/fonts/opentype/urw-base35/NimbusSans-Regular.otf + run: | + python .github/ci/run.py --phase test --build-dir build/ci --profile modules --build-type "$BUILD_TYPE" --linkage "$LINKAGE" --hwloc ON --sdk + python .github/ci/verify_sdk.py --phase test --packages build/packages --build-dir build/sdk-consumer --producer-build build/ci + - name: Report compiler cache statistics + if: ${{ !cancelled() && steps.ccache-ready.outcome == 'success' }} + run: ccache --show-stats --verbose + - name: Pack compiler cache additions + if: ${{ !cancelled() && steps.ccache-ready.outcome == 'success' }} + id: compiler-cache-state + shell: bash + env: + CCACHE_DELTA_ARCHIVE: ${{ steps.ccache.outputs.delta-archive }} + run: python .github/ci/compiler_cache.py pack --output "$CCACHE_DELTA_ARCHIVE" + - name: Retain compiler cache additions + if: ${{ !cancelled() && steps.compiler-cache-state.outputs.has-content == 'true' }} + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: compiler-cache-${{ runner.os }}-${{ runner.arch }}-${{ matrix.cache_family }}-${{ matrix.name }} + path: ${{ steps.ccache.outputs.delta-archive }} + if-no-files-found: error + compression-level: 0 + overwrite: true + retention-days: 1 + - name: Retain checked SDK candidate + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: candidate-sdk-${{ matrix.name }} + path: build/packages/ + if-no-files-found: error + retention-days: 3 + - name: Retain results + if: always() + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: results-${{ matrix.name }} + path: | + build/ci/junit.xml + build/ci/Testing/Temporary/ + build/ci/CMakeFiles/CMakeConfigureLog.yaml + build/sdk-consumer/junit.xml + build/sdk-consumer/Testing/Temporary/ + build/sdk-consumer/CMakeFiles/CMakeConfigureLog.yaml + build/sdk-consumer-hermetic/junit.xml + build/ci/cheryl-dependency-ownership.json + build/ci/link-maps/ + if-no-files-found: warn + retention-days: 14 + + windows: + name: ${{ matrix.name }} + needs: [windows-dependencies] + runs-on: windows-2025 + timeout-minutes: 90 + strategy: + fail-fast: false + matrix: + include: + - {name: windows-msvc-hwloc, profile: headless, cache_family: windows-msvc, type: RelWithDebInfo, hwloc: 'ON', linkage: static} + - {name: windows-modules, profile: modules, cache_family: windows-msvc, type: RelWithDebInfo, hwloc: 'ON', linkage: static} + - {name: windows-modules-shared, profile: modules, cache_family: windows-msvc, type: RelWithDebInfo, hwloc: 'ON', linkage: shared} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Prepare dependencies + id: dependencies + uses: ./.github/actions/prepare-dependencies + with: + profile: ${{ matrix.profile }} + windows-vcpkg: consume + prepared-artifact: ${{ needs.windows-dependencies.outputs.artifact }} + - name: Restore compiler cache + id: ccache + uses: ./.github/actions/restore-compiler-cache + with: + prefix: ccache-v3-${{ runner.os }}-${{ runner.arch }}-${{ matrix.cache_family }} + - name: Reset compiler cache statistics + id: ccache-ready + run: ccache --zero-stats + - name: Check CI tools + if: matrix.profile == 'headless' + shell: bash + run: | + python .github/ci/test_ci.py + python .github/ci/test_cache_cleanup.py + python .github/ci/test_compiler_cache.py + python .github/ci/test_dependencies.py + python .github/ci/test_components.py + python .github/ci/test_native_notices.py + python .github/ci/test_verify_sdk.py + python .github/ci/test_vcpkg_cache.py + python .github/ci/test_vcpkg_publication.py + python .github/ci/test_github_api.py + python .github/ci/test_versions.py + python .github/ci/test_release.py + - name: Build Windows + if: matrix.profile == 'headless' + shell: pwsh + env: + BUILD_TYPE: ${{ matrix.type }} + HWLOC: ${{ matrix.hwloc }} + run: | + $vswhere = "${env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer/vswhere.exe" + $vs = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath + if (!$vs) { throw 'MSVC x64 tools are unavailable' } + & "$vs/Common7/Tools/Launch-VsDevShell.ps1" -Arch amd64 -HostArch amd64 -SkipAutomaticLocation + python .github/ci/run.py --phase build --build-dir build/ci --profile headless --build-type $env:BUILD_TYPE --hwloc $env:HWLOC --vcpkg-root $env:CHERYL_CI_VCPKG_ROOT --vcpkg-installed-dir $env:CHERYL_CI_VCPKG_INSTALLED_DIR + if ($LASTEXITCODE) { exit $LASTEXITCODE } + - name: Build Windows modules and SDK consumers + if: matrix.profile == 'modules' shell: pwsh + env: + BUILD_TYPE: ${{ matrix.type }} + LINKAGE: ${{ matrix.linkage }} run: | $vswhere = "${env:ProgramFiles(x86)}/Microsoft Visual Studio/Installer/vswhere.exe" $vs = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath if (!$vs) { throw 'MSVC x64 tools are unavailable' } & "$vs/Common7/Tools/Launch-VsDevShell.ps1" -Arch amd64 -HostArch amd64 -SkipAutomaticLocation - python .github/ci/run.py --build-dir build/ci --profile modules --build-type RelWithDebInfo --hwloc ON --sdk --vcpkg-root $env:CHERYL_CI_VCPKG_ROOT + python .github/ci/run.py --phase build --build-dir build/ci --profile modules --build-type $env:BUILD_TYPE --linkage $env:LINKAGE --hwloc ON --sdk --vcpkg-root $env:CHERYL_CI_VCPKG_ROOT --vcpkg-installed-dir $env:CHERYL_CI_VCPKG_INSTALLED_DIR if ($LASTEXITCODE) { exit $LASTEXITCODE } python .github/ci/package.py --build-dir build/ci --output build/packages if ($LASTEXITCODE) { exit $LASTEXITCODE } - python .github/ci/verify_sdk.py --packages build/packages --build-dir build/sdk-consumer --producer-build build/ci + python .github/ci/verify_sdk.py --phase build --packages build/packages --build-dir build/sdk-consumer --producer-build build/ci + if ($LASTEXITCODE) { exit $LASTEXITCODE } + - name: Test Windows + if: matrix.profile == 'headless' + shell: pwsh + env: + BUILD_TYPE: ${{ matrix.type }} + HWLOC: ${{ matrix.hwloc }} + run: | + python .github/ci/run.py --phase test --build-dir build/ci --profile headless --build-type $env:BUILD_TYPE --hwloc $env:HWLOC + if ($LASTEXITCODE) { exit $LASTEXITCODE } + - name: Test Windows modules and SDK consumers + if: matrix.profile == 'modules' + shell: pwsh + env: + BUILD_TYPE: ${{ matrix.type }} + LINKAGE: ${{ matrix.linkage }} + run: | + python .github/ci/run.py --phase test --build-dir build/ci --profile modules --build-type $env:BUILD_TYPE --linkage $env:LINKAGE --hwloc ON --sdk + if ($LASTEXITCODE) { exit $LASTEXITCODE } + python .github/ci/verify_sdk.py --phase test --packages build/packages --build-dir build/sdk-consumer --producer-build build/ci if ($LASTEXITCODE) { exit $LASTEXITCODE } - - name: Check dependency cache updates - if: ${{ !cancelled() && runner.os == 'Windows' && steps.dependencies.outcome == 'success' }} - id: dependency-cache-state - run: python .github/ci/dependencies.py cache-state - - name: Save dependency packages and downloads - if: ${{ !cancelled() && steps.dependency-cache-state.outputs.changed == 'true' }} - uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: ${{ env.CHERYL_CI_DEPENDENCY_CACHE_DIR }} - key: ${{ env.CHERYL_CI_DEPENDENCY_CACHE_KEY }} - name: Report compiler cache statistics if: ${{ !cancelled() && steps.ccache-ready.outcome == 'success' }} run: ccache --show-stats --verbose - - name: Save compiler cache - if: ${{ !cancelled() && steps.ccache-ready.outcome == 'success' && steps.ccache.outputs.cache-hit != 'true' }} - uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + - name: Pack compiler cache additions + if: ${{ !cancelled() && steps.ccache-ready.outcome == 'success' }} + id: compiler-cache-state + shell: bash + env: + CCACHE_DELTA_ARCHIVE: ${{ steps.ccache.outputs.delta-archive }} + run: python .github/ci/compiler_cache.py pack --output "$CCACHE_DELTA_ARCHIVE" + - name: Retain compiler cache additions + if: ${{ !cancelled() && steps.compiler-cache-state.outputs.has-content == 'true' }} + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: - path: ${{ env.CCACHE_DIR }} - key: ${{ steps.ccache.outputs.cache-primary-key }} + name: compiler-cache-${{ runner.os }}-${{ runner.arch }}-${{ matrix.cache_family }}-${{ matrix.name }} + path: ${{ steps.ccache.outputs.delta-archive }} + if-no-files-found: error + compression-level: 0 + overwrite: true + retention-days: 1 - name: Retain checked SDK candidate - if: github.event_name == 'push' && github.ref == 'refs/heads/main' + if: matrix.profile == 'modules' && github.event_name == 'push' && github.ref == 'refs/heads/main' uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: candidate-sdk-${{ matrix.name }} @@ -205,32 +372,139 @@ jobs: build/sdk-consumer/junit.xml build/sdk-consumer/Testing/Temporary/ build/sdk-consumer/CMakeFiles/CMakeConfigureLog.yaml + build/sdk-consumer-hermetic/junit.xml + build/ci/cheryl-dependency-ownership.json + build/ci/link-maps/ if-no-files-found: warn retention-days: 14 + compiler-cache: + name: Publish compiler cache (${{ matrix.cache_family }}) + if: ${{ !cancelled() }} + needs: [headless, modules, windows] + runs-on: ${{ matrix.os }} + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-24.04 + cache_family: linux-gcc14 + legacy_1: linux-gcc-hwloc-Release + legacy_2: linux-gcc-neutral-Release + legacy_3: linux-modules-Release + legacy_4: linux-modules-shared-Release + - os: ubuntu-24.04 + cache_family: linux-clang18 + legacy_1: linux-clang-debug-Debug + legacy_2: '' + legacy_3: '' + legacy_4: '' + - os: windows-2025 + cache_family: windows-msvc + legacy_1: windows-msvc-hwloc-RelWithDebInfo + legacy_2: windows-modules-RelWithDebInfo + legacy_3: windows-modules-shared-RelWithDebInfo + legacy_4: '' + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Restore compiler cache layers + id: ccache + uses: ./.github/actions/restore-compiler-cache + with: + prefix: ccache-v3-${{ runner.os }}-${{ runner.arch }}-${{ matrix.cache_family }} + attach: 'false' + - name: Import previous compiler cache (1) + if: steps.ccache.outputs.current-hit != 'true' && matrix.legacy_1 != '' + uses: ./.github/actions/migrate-compiler-cache + with: + legacy-prefix: ccache-v2-${{ matrix.os }}-${{ runner.arch }}-${{ matrix.legacy_1 }} + - name: Import previous compiler cache (2) + if: steps.ccache.outputs.current-hit != 'true' && matrix.legacy_2 != '' + uses: ./.github/actions/migrate-compiler-cache + with: + legacy-prefix: ccache-v2-${{ matrix.os }}-${{ runner.arch }}-${{ matrix.legacy_2 }} + - name: Import previous compiler cache (3) + if: steps.ccache.outputs.current-hit != 'true' && matrix.legacy_3 != '' + uses: ./.github/actions/migrate-compiler-cache + with: + legacy-prefix: ccache-v2-${{ matrix.os }}-${{ runner.arch }}-${{ matrix.legacy_3 }} + - name: Import previous compiler cache (4) + if: steps.ccache.outputs.current-hit != 'true' && matrix.legacy_4 != '' + uses: ./.github/actions/migrate-compiler-cache + with: + legacy-prefix: ccache-v2-${{ matrix.os }}-${{ runner.arch }}-${{ matrix.legacy_4 }} + - name: Collect compiler cache additions + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + pattern: compiler-cache-${{ runner.os }}-${{ runner.arch }}-${{ matrix.cache_family }}-* + path: build/cache/compiler-deltas + - name: Merge compiler cache additions + id: compiler-cache-state + shell: bash + run: python .github/ci/compiler_cache.py publish --deltas build/cache/compiler-deltas + - name: Save compiler cache family + if: steps.compiler-cache-state.outputs.changed == 'true' + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.ccache.outputs.current-path }} + key: ${{ steps.ccache.outputs.current-key }} + sdk: - name: Collect accepted development SDKs + name: Assemble accepted SDK and source packages if: github.event_name == 'push' && github.ref == 'refs/heads/main' - needs: [headless, modules] + needs: [headless, modules, windows] runs-on: ubuntu-24.04 timeout-minutes: 10 steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.sha }} + submodules: recursive + persist-credentials: false - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: pattern: candidate-sdk-* path: packages - - name: Check collected SDK checksums - run: | - for bundle in packages/candidate-sdk-*; do - (cd "$bundle" && sha256sum --check SHA256SUMS) - done + - name: Assemble the four checked packages + run: python .github/ci/release.py --assemble-only --packages packages --source . --output build/release-packages --revision "$GITHUB_SHA" - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: cheryl-development-sdks-${{ github.sha }} path: | - packages/**/*.tar.gz - packages/**/*.zip - packages/**/SHA256SUMS - packages/**/SDK-MANIFEST.json + build/release-packages/*.tar.gz + build/release-packages/*.zip + build/release-packages/SHA256SUMS + build/release-packages/SDK-MANIFESTS.json if-no-files-found: error retention-days: 90 + + release: + name: Publish accepted release + if: github.event_name == 'push' && github.repository == 'cppcooper/cheryl-engine' && github.ref == 'refs/heads/main' + needs: [sdk] + runs-on: ubuntu-24.04 + timeout-minutes: 20 + permissions: + contents: write + actions: read + pull-requests: read + concurrency: + group: release-${{ github.repository }} + cancel-in-progress: false + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.sha }} + persist-credentials: false + - name: Download accepted SDK packages + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: cheryl-development-sdks-${{ github.sha }} + path: build/release-packages + - name: Tag and publish the checked release + env: + GITHUB_TOKEN: ${{ github.token }} + run: python .github/ci/release.py --packages build/release-packages --revision "$GITHUB_SHA" diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index a0832f78..c5422352 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -21,7 +21,6 @@ jobs: group: docs-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true env: - CHERYL_DOCS_CACHE_DIR: ${{ runner.temp }}/cheryl-engine/docs-tools PYTHONDONTWRITEBYTECODE: '1' defaults: run: @@ -35,27 +34,42 @@ jobs: uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: '3.13' - - name: Check documentation tooling + - name: Select documentation cache directory run: | - python .github/scripts/test_sync_docs_stable.py - python -m unittest discover -s docs/generation/tests -p 'test_*.py' + printf 'CHERYL_DOCS_CACHE_DIR=%s/cheryl-engine/docs-tools\n' "$RUNNER_TEMP" >> "$GITHUB_ENV" - name: Identify documentation tool cache id: tool-cache - run: python -c 'import sys; sys.path.insert(0, "docs/generation"); from bootstrap import environment_identity; print("identity=" + environment_identity())' >> "$GITHUB_OUTPUT" + run: python -c 'import sys; sys.path.insert(0, "docs/site/generation"); from bootstrap import environment_identity; print("identity=" + environment_identity())' >> "$GITHUB_OUTPUT" + - name: Describe shared Actions environment + id: environment + run: python .github/ci/dependencies.py environment-prefix --profile docs - name: Restore documentation tools - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + id: tools + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ env.CHERYL_DOCS_CACHE_DIR }} + key: ${{ steps.environment.outputs.environment-prefix }}-tools-${{ steps.tool-cache.outputs.identity }}-${{ hashFiles('docs/site/generation/requirements.txt', 'docs/site/generation/native-tools.json', 'docs/site/generation/bootstrap.py') }} + - name: Prepare pinned documentation tools + run: python docs/site/generation/bootstrap.py setup --force-pinned + - name: Check documentation tooling + run: | + python .github/scripts/test_sync_docs_stable.py + python docs/site/generation/bootstrap.py exec --force-pinned -- python -m unittest discover -s docs/site/generation/tests -p 'test_*.py' + - name: Save shared documentation tools + if: steps.environment.outputs.environment-cache-writer == 'true' && steps.tools.outputs.cache-hit != 'true' + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ${{ env.CHERYL_DOCS_CACHE_DIR }} - key: docs-tools-v1-${{ runner.os }}-${{ runner.arch }}-${{ steps.tool-cache.outputs.identity }}-${{ hashFiles('docs/generation/requirements.txt', 'docs/generation/native-tools.json', 'docs/generation/bootstrap.py') }} + key: ${{ steps.tools.outputs.cache-primary-key }} - name: Generate and check documentation run: | - mkdir -p docs/generation/generated - python docs/generation/bootstrap.py exec --force-pinned -- python docs/generation/build.py 2>&1 | tee docs/generation/generated/generation.log + mkdir -p docs/site/generation/generated + python docs/site/generation/bootstrap.py exec --force-pinned -- python docs/site/generation/build.py --clean 2>&1 | tee docs/site/generation/generated/generation.log - name: Retain checked HTML uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: documentation-html-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }} - path: docs/generation/generated/html/ + path: docs/site/generation/generated/html/ if-no-files-found: error retention-days: 30 - name: Retain documentation diagnostics @@ -64,10 +78,10 @@ jobs: with: name: documentation-diagnostics-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }} path: | - docs/generation/generated/*.log - docs/generation/generated/Doxyfile - docs/generation/generated/xml/ - docs/generation/generated/html/revision.json + docs/site/generation/generated/*.log + docs/site/generation/generated/Doxyfile + docs/site/generation/generated/xml/ + docs/site/generation/generated/html/revision.json if-no-files-found: warn retention-days: 30 diff --git a/.github/workflows/release-policy.yml b/.github/workflows/release-policy.yml new file mode 100644 index 00000000..457c50a2 --- /dev/null +++ b/.github/workflows/release-policy.yml @@ -0,0 +1,22 @@ +name: Release policy + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened, edited, ready_for_review] + +permissions: + contents: read + +jobs: + release-policy: + name: Check release version and promotion branch + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + - name: Check release PR + run: python .github/ci/versions.py check-release diff --git a/.github/workflows/vcpkg-cache.yml b/.github/workflows/vcpkg-cache.yml new file mode 100644 index 00000000..6f8e63ad --- /dev/null +++ b/.github/workflows/vcpkg-cache.yml @@ -0,0 +1,75 @@ +name: Publish shared Windows dependencies + +on: + workflow_run: + workflows: ['Build and test'] + types: [completed] + +permissions: + contents: read + +concurrency: + group: shared-windows-dependencies-${{ github.repository }} + cancel-in-progress: false + queue: max + +jobs: + publish: + if: github.repository == 'cppcooper/cheryl-engine' + runs-on: windows-2025 + timeout-minutes: 20 + cache-mode: write + permissions: + contents: read + actions: write + pull-requests: read + env: + PYTHONDONTWRITEBYTECODE: '1' + steps: + # This elevated workflow always uses trusted default-branch code. + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: refs/heads/${{ github.event.repository.default_branch }} + persist-credentials: false + - name: Admit prepared dependency contributions + id: admit + env: + GITHUB_TOKEN: ${{ github.token }} + run: python .github/ci/vcpkg_publication.py admit + - name: Describe shared dependency pool + if: steps.admit.outputs.allowed == 'true' + id: pool + run: python .github/ci/vcpkg_cache.py describe + - name: Restore latest shared dependency pool + if: steps.admit.outputs.allowed == 'true' + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.pool.outputs.cache-path }} + key: ${{ steps.pool.outputs.cache-key }} + restore-keys: ${{ steps.pool.outputs.cache-prefix }} + - name: Collect prepared dependency contributions + if: steps.admit.outputs.allowed == 'true' + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + artifact-ids: ${{ steps.admit.outputs.artifact-ids }} + run-id: ${{ steps.admit.outputs.source-run-id }} + github-token: ${{ github.token }} + path: build/cache/vcpkg-contributions + - name: Merge and prune shared dependency pool + if: steps.admit.outputs.allowed == 'true' + id: merge + env: + POOL_LIMIT_GIB: ${{ vars.CHERYL_VCPKG_CACHE_LIMIT_GIB || '2' }} + run: python .github/ci/vcpkg_cache.py merge --contributions build/cache/vcpkg-contributions --limit-gib $env:POOL_LIMIT_GIB + - name: Save updated shared dependency pool + if: steps.merge.outputs.changed == 'true' + id: save + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ${{ steps.pool.outputs.cache-path }} + key: ${{ steps.pool.outputs.cache-key }} + - name: Retire superseded shared snapshots + if: steps.save.outcome == 'success' && steps.merge.outputs.changed == 'true' + env: + GITHUB_TOKEN: ${{ github.token }} + run: python .github/ci/vcpkg_publication.py retire diff --git a/.github/workflows/versions.yml b/.github/workflows/versions.yml new file mode 100644 index 00000000..7f57ec17 --- /dev/null +++ b/.github/workflows/versions.yml @@ -0,0 +1,43 @@ +name: Manage versions + +on: + push: + branches: [develop] + workflow_dispatch: + inputs: + prepare_release: + description: Prepare the next stable project version and release PR. + type: boolean + default: false + +permissions: + contents: read + +concurrency: + group: versions-${{ github.repository }} + cancel-in-progress: false + +jobs: + versions: + name: Maintain development and release versions + if: github.repository == 'cppcooper/cheryl-engine' && github.ref == 'refs/heads/develop' + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + - name: Update version PR + env: + GITHUB_TOKEN: ${{ github.token }} + PREPARE_RELEASE: ${{ inputs.prepare_release || false }} + run: | + if [ "$PREPARE_RELEASE" = true ]; then + python .github/ci/versions.py prepare-release + else + python .github/ci/versions.py maintain + fi diff --git a/.gitignore b/.gitignore index a04c02d2..8f1fe1da 100644 --- a/.gitignore +++ b/.gitignore @@ -7,9 +7,8 @@ cmake-build*/ __pycache__/ # Generated documentation, including legacy API output -/docs/generation/generated/ -/docs/generated/ -/docs/api/generated/ +/docs/site/generation/generated/ + .git/ .idea/ @@ -23,17 +22,5 @@ notes/ *.png *.wav -assets/** -!assets/**/ -!assets/**/*.rml -!assets/**/*.json -!assets/**/*.frag -!assets/**/*.vert -!assets/fonts/* -assets/sfx -assets/graphics/**/*.txt - -!assets/graphics/ui/quadrants.png -!assets/graphics/ui/quadrants-alt.png !/projects/modules/graphics/opengl/tests/fixtures/rgba-two-rows.png !/projects/apps/first-game/assets/graphics/textures/actor.png diff --git a/.readthedocs.yaml b/.readthedocs.yaml index d7b6add0..46f4fe60 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -10,4 +10,4 @@ build: jobs: build: html: - - python docs/generation/bootstrap.py exec --force-pinned -- python docs/generation/build.py --output "$READTHEDOCS_OUTPUT" + - python docs/site/generation/bootstrap.py exec --force-pinned -- python docs/site/generation/build.py --clean --output "$READTHEDOCS_OUTPUT" diff --git a/AGENTS.md b/AGENTS.md index a0dad2d1..d7f9320f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,243 +1,44 @@ -# Cheryl Engine Agent Instructions - -## General instructions - -### Work modes and progression - -Each task has one primary work mode: - -- Review and planning -- Investigation and research -- Architecture and design -- Implementation, refactoring, and tooling -- Test design -- Test implementation -- Test execution -- Verification -- Documentation audit -- Documentation maintenance - -Keep independent work objectives separated by mode. Do not automatically transition into another mode merely because related work is discovered or the next phase seems obvious. - -Reading code, inspecting dependencies, checking Git state, and performing other necessary supporting operations do not constitute a mode transition. Keep supporting work proportional to the primary objective. - -#### Progression control - -User-directed progression is the default. Complete the requested work mode and stop at its boundary unless further work has been explicitly authorized. - -When a bounded sequence of work is predictable, reasonably well-defined, and likely to benefit from fewer interruptions, recommend plan-directed progression. Explain the proposed phases and decision checkpoints. Do not adopt the sequence without user approval. - -An approved plan may authorize progression across work modes. Complete each phase before advancing to the next, preserving their boundaries and respecting existing authorization restrictions. - -Neither progression model requires permission for every coherent subtask within an already authorized phase. - -#### Boundary handling - -A boundary is a point where continuing work may require a new decision, a change in scope, or a transition between work modes. Completing a subtask or encountering a dependency does not automatically constitute a stopping point. - -**Continue without interruption when:** - -- Work remains within the authorized mode, scope, and approved plan. -- A necessary dependency can be resolved without exceeding that authorization or introducing an unresolved architectural decision. -- The next subtask requires no additional user decision. -- Supporting operations are necessary to complete the authorized work. - -**Stop at the boundary when:** - -- The requested phase or scope is complete under user-directed progression. -- An approved plan reaches a mandatory decision checkpoint. -- Continuing requires an unauthorized work mode, scope expansion, or operation. -- An unresolved architectural decision, requirement ambiguity, or materially invalidated assumption requires user direction. -- Continuing would depend on unfinished user work or risk modifying protected changes in the shared working tree. -- A blocking condition prevents safe or correct completion. - -When a boundary is encountered, complete any remaining independent, authorized work that can safely proceed. Report the boundary, its implications, and the recommended next action. - -Advisory checkpoints, including documentation-maintenance recommendations, do not require stopping or independently authorize additional work. - -During plan-directed progression, advance through approved phases without interruption unless a mandatory decision checkpoint or unexpected blocking condition is encountered. - -Existing repository safety and authorization restrictions always take precedence. - -#### Phase completion and task completion - -A work phase and the overall development task are distinct. - -Completing a phase means its authorized objectives have been satisfied or its remaining work has been explicitly identified as blocked or deferred. It does not imply that the broader development task is complete. - -At phase completion: - -- Summarize completed work and any outstanding, blocked, or deferred work. -- Identify the recommended next phase when applicable. -- Recommend documentation maintenance when accumulated changes warrant it, without initiating maintenance automatically. -- Stop under user-directed progression, or continue according to an approved plan-directed sequence. - -Do not declare the overall task complete while known required work remains unfinished. Do not treat optional improvements or deferred documentation maintenance as blockers unless they are explicitly required by the task's acceptance criteria. - -### Source authority and context - -Prefer source code, configuration, and actual implementation when determining the repository's current state and behavior. - -Use documentation to understand intended architecture, requirements, design rationale, historical decisions, and planned work. Documentation may describe previous, intended, or partially implemented behavior. - -Verify documentation's claims about current implementation against the relevant code. When implementation and documented intent disagree, distinguish the observed state from the intended state rather than automatically treating either as correct. - -Acquire context purposefully. Inspect the code, documentation, history, and dependencies relevant to the current objective. Expand investigation when necessary rather than loading unrelated project context by default. - -Prefer concise phase handoffs containing established decisions, relevant constraints, outstanding work, and affected contracts. Do not create persistent handoff or planning documents unless explicitly requested. - -## Repository safety - -- Do not push commits or branches to any remote unless explicitly requested. -- Do not build or compile the project unless explicitly requested. -- Avoid requesting permission to build or test until reaching a blocking point. -- When building is permitted, make an attempt to avoid running consecutive builds. -- Do not run tests unless explicitly requested. -- When testing is permitted, make an attempt to avoid running consecutive tests. -- Instead of consecutive builds and tests, make efforts to achieve the same end result in as few steps as possible. -- If consecutive agent-run builds must be used, then reduce the process priority and or thread count involved to avoid melting the CPU. -- Resource-sharing limits apply to agent-run work. Commands supplied for the user run at normal priority with available parallelism; do not add low-priority wrappers or single-job limits to user-run commands. -- Static analysis and other checks that do not require compilation are permitted. -- Do not perform opportunistic refactoring, cleanup, formatting, or API changes merely because they are convenient while working nearby. -- If the task reveals additional work that is related but not required for the current development unit, identify it as discovered work and defer it to an appropriate phase boundary. Incorporate it into the active plan only when it falls within the authorized scope and work mode; otherwise report it for user direction. -- If newly discovered work is required for correctness or for the planned work to proceed, revise the plan within the authorized scope. If it requires an unauthorized work mode, stop at that boundary and request direction unless an approved plan already covers the transition. - -## Shared working tree - -The repository is a shared human/agent working tree. Pre-existing uncommitted changes may represent deliberate work in progress and may be incomplete, temporarily inconsistent, non-functional, transitional, preparatory for later refactoring, or difficult to reason about safely. - -Before modifying affected code: - -- Inspect the current Git status and relevant pre-existing diffs. -- Treat pre-existing user changes as authoritative work in progress. Do not revert, overwrite, normalize, reformat, or replace them merely to simplify the task. -- Do not assume pre-existing changes form a valid, complete, or stable implementation baseline. -- Do not build new task work on top of pre-existing unfinished user work merely because its current behavior is understandable. -- Avoid introducing new dependencies on unfinished user work, propagating its current structure into additional code, constraining its future evolution, or otherwise increasing the amount of work that would need to change when the user's work is completed. - -A file containing user changes is not automatically off-limits. Work with it only when the requested change is independent of the unfinished work, or when the change can be made without increasing the dependency surface, constraining future evolution, or increasing the likely scope of subsequent refactoring. - -If completing the requested work would require treating unfinished user work as a stable foundation, extending its current design into other code, or making additional code depend on it, treat that as a development boundary. - -When such a boundary is encountered: - -- Preserve the existing user work unchanged where possible. -- Stop at that boundary. -- Continue with independent planned work only when it is clearly unaffected by, and does not depend on, the blocked work. -- Clearly identify what cannot be completed safely and why. - -When committing: - -- Commit only task-related changes that can be cleanly separated from pre-existing user work. -- Do not include unrelated or unfinished user work in an agent-created commit. -- If task-related work and pre-existing user work cannot be separated safely, leave the overlapping work uncommitted rather than rewriting the user's work to manufacture a clean commit. - -## Planning and execution - -Before beginning non-trivial implementation work, establish a concise, mode-scoped implementation plan containing coherent tasks and subtasks. Keep the plan in the working context unless a persistent planning artifact is explicitly requested. - -Order the work deliberately so foundational decisions, architectural constraints, risky assumptions, and dependency boundaries are resolved as early as practical. Prefer sequencing that reduces the likelihood of later refactoring, prevents avoidable bugs, and allows later tasks to build on stable completed work. - -Identify boundaries where completing one task may expose additional required work or invalidate an assumption, and structure the plan so those discoveries occur before large amounts of dependent implementation accumulate. - -Follow the established plan within the authorized work scope unless implementation reveals information that materially changes the design or invalidates part of the plan. - -A plan does not independently authorize transitions into other work modes. When a new dependency or architectural decision requires work outside that scope, identify the boundary and request direction unless the transition was previously approved. - -Complete and commit coherent development units independently so the commit history reflects the progression of the implementation and preserves completed work as later development proceeds. - -## Testing requests - -Testing-request documentation is maintained only when explicitly authorized. Do not update `docs/testing-requests.md` automatically during implementation, refactoring, verification, or other non-documentation phases. - -Preserve discovered testing requirements in the task handoff until a testing-request maintenance phase is authorized. At that point, reconcile accumulated requirements against existing requests. - -Separate test design, test implementation, and test execution according to the authorized work mode. - -During authorized testing-request maintenance: - -- Maintain `docs/testing-requests.md` as the user-facing queue of testing needed for implemented work. Reconcile accumulated testing needs with the current source, targets, configuration, reported results, and required coverage. -- Keep a summary table near the top with exactly three columns: **TR#**, **Short description / title**, and **Long description (plain language)**. Give every active request one row, with its `TR` linked directly to its detailed section/heading. Include the same ID in that heading. Preserve request IDs when updating requests; do not renumber remaining requests or reuse retired IDs. -- Prefix each summary-table **Short description / title** cell with **Automated:** or **QA:**, matching the request type in its detailed section. -- Write each table's long description for a reader unfamiliar with engine or testing internals: explain what will be checked, why it matters, and any setup requirement or blocker affecting whether they can run it. Explain necessary jargon; keep exact commands and detailed acceptance criteria in the linked section. -- Aggregate deferred testing across development cycles. Extend an existing request when it covers the same behavior and configuration; avoid duplicate case runs through owner and aggregate runners, and batch needed build targets into as few invocations as practical. -- Label each request as **Automated** or **QA**, with its platform, prerequisites, readiness and acceptance scope. Keep development progress in the owning plan rather than duplicating its macro-task checklist here. -- For each automated request, provide a runnable fenced command block. Include required CMake configuration, explicit build targets, environment opt-ins, working directory and test selection. State when a matching existing build can be reused. -- Command blocks must establish their working directory explicitly, including QA launch blocks. Commands launched from a document may inherit that document's directory; locate the checkout root rather than assuming the shell already runs there, and preserve the caller's starting directory. -- User-run command-line CMake configuration must explicitly select Ninja with `-G Ninja`. Build and test commands use that configured build directory. -- For each QA request, provide a brief detailed summary and concise requirement bullets covering setup, actions, expected observations and required platform/runtime modes. -- Mark requests blocked when fixtures, hardware or an observation harness are unavailable, and identify the next action. Do not invent commands or count skipped, unselected or device-free checks as the missing acceptance. -- When the user reports results, reconcile requests and owning plans during the next authorized maintenance phase against the actual tested revision, configuration, platforms and coverage. Remove accepted requests, preserve unresolved portions and repair affected links; retain durable coverage limits in the relevant subject guide rather than appending routine execution logs. - -Submitting a request does not authorize agents to configure/build the project or execute tests. Existing repository authorization rules still apply. - -## Documentation discipline - -Reading documentation is permitted in any work mode when relevant to understanding requirements, intent, constraints, or history. Prefer the relevant source code when establishing current behavior, and consult the relevant roadmap or subject document for direction rather than loading the entire planning folder. - -Documentation auditing and maintenance are separate work modes. Do not create, update, reorganize, or delete documentation during other work modes unless explicitly requested. - -Essential code comments and runtime logging directly required by the implementation are permitted. Do not perform general comment cleanup, logging improvements, or explanatory documentation work opportunistically. - -Defer documentation synchronization until a dedicated maintenance phase. Prefer stabilizing related implementation work before reconciling documentation. - -Recommend a documentation checkpoint at a meaningful phase boundary when architectural contracts stabilize, substantial implementation changes accumulate, or documentation is likely to have become materially inaccurate. - -Documentation checkpoints are advisory. Do not conduct a documentation audit or begin maintenance merely to make the recommendation. The user decides whether to continue engineering work or authorize documentation maintenance unless the transition was already approved. - -### Documentation audit - -Inspect the relevant implementation and documentation to identify inaccurate, obsolete, missing, duplicated, or unnecessary information. - -Report findings and recommended changes without modifying documentation. Distinguish documentation describing intended behavior from documentation incorrectly describing the current implementation. - -### Documentation maintenance - -Documentation should help a reader understand the current system, its intended direction, or the work required to reach that direction. - -Prefer updating and consolidating existing authoritative documents over creating new ones. Remove obsolete, duplicate, or superseded documentation when its useful information has been preserved. Do not expand the document collection merely to record intermediate work, reasoning, or implementation progress. - -Apply these rules to affected documents across `docs/` during authorized maintenance: - -- Describe current contracts in the present tense. Keep plans focused on the target state, unresolved work, prerequisites, ownership/dependency constraints, ordered steps, discovery boundaries and acceptance criteria. -- Keep one concise progress checklist for each active macro task in the plan that owns its details. Reconcile accumulated subtask progress during maintenance, retaining checked subtasks while the macro task remains open. Keep future macro tasks at roadmap detail until they become active; link to the owning checklist instead of maintaining duplicate progress lists. -- Remove completed macro-task narratives and superseded alternatives. Document resulting durable behavior in its authoritative subject document, and link to it instead of duplicating contracts or source inventories. -- Preserve unique requirements, decisions and rationale that still guide implementation. Before shortening or deleting a plan, verify that each remains available in the surviving text or a linked authoritative document. Do not replace relevant detail with a vague summary or a pointer to Git alone. -- Git commits and diffs hold implementation history. A brief decision/history summary with specific Git references is useful when it explains the current direction or saves a future reader substantial reconstruction; do not reproduce the task-by-task journal. -- Do not append routine successful build/test/check reports, pass counts, transcripts or toolchain snapshots unless explicitly requested. Retain reusable validation procedures and meaningful coverage limits. Distinguish source completion from executable acceptance; a successful exit with skipped or unselected checks does not establish the missing coverage. -- Record unresolved failures, unavailable prerequisites and environment/compatibility limits only to the extent they affect future work, with the required next action. Remove resolved blockers unless they describe an enduring constraint. -- Reconcile affected plans and current-state documentation against accumulated completed work. When a macro task completes, remove its task section and checklist after preserving any reusable procedure, durable contract or still-relevant decision rationale and repairing inbound links. Delete a one-purpose plan when no unresolved work remains. Do not move its execution log elsewhere merely to empty the plan. - -Keep a paragraph when it explains how something works, how it should work, how to get there, or a decision/constraint needed to do so correctly. Concision must preserve that information. - -## Git - -For commits created by the agent, use: - -- `user.name = cppcooper` -- `user.email = cppcooper@users.noreply.github.com` - -Apply this identity per Git command using `git -c`. Do not modify the user's global Git configuration or the repository's local Git configuration. - -Commit messages must follow the project's convention and begin with a third-person singular present-tense verb, such as: - -- `Adds` -- `Updates` -- `Revises` -- `Deletes` -- `Fixes` -- `Requires` - -Use detailed commit messages appropriate to the completed development unit. - -Preserve the individual commit series. Do not squash, amend, or otherwise rewrite completed commits unless explicitly requested. - -Do not generate mailbox patches unless explicitly requested. - -## Cheryl conventions - -- Follow the repository's existing style and `.clang-format`. -- When organizing class declarations, place data members above methods. -- Keep unit-test names short and intuitive rather than long and descriptive. -- Tests must exercise behavior without intentionally emitting compiler warnings in normal builds. Consume results when they matter to the assertion; explicitly discard irrelevant `[[nodiscard]]` results with `static_cast(...)`, including inside exception assertions. Apply the same warning discipline to other intentional diagnostic triggers. -- Test compiler diagnostics themselves through isolated probes that capture and assert the expected diagnostic. Keep any required diagnostic suppression narrowly scoped to the tested construct and compiler; do not remove API attributes or disable warnings across a target/project to accommodate a test. -- Do not use "no callers" or "few callers" as grounds for removing an interface. Evaluate interfaces according to their architectural purpose and contract. \ No newline at end of file +# Cheryl Engine agent instructions + +This file is the entry point for repository instructions. Read the +[shared work rules](docs/for-agents/workflow.md) for every task, then the focused +guides that apply. Linked instructions are part of this instruction set. Read +applicable nested `AGENTS.md` files as well; `docs/AGENTS.md` routes documentation +work, including API comments outside `docs/`. + +## Essential safeguards + +- Work within the user's authorized scope and progression. Existing authorization + persists for relevant fixes and reruns; do not request it again unnecessarily. +- Inspect Git status and relevant diffs before edits. Protect unfinished user + work, active runs, outputs, build configurations and caches. Do not extend + unfinished work as a stable foundation without resolving that boundary. +- Follow the exact [operation permissions](docs/for-agents/testing.md#verification-and-resource-use) + before builds, native tests, full documentation generation, downloads, + installations, devices or external services. Available tools do not grant permission. +- Push only when explicitly requested. Follow the + [Git guide](docs/for-agents/git.md) for identity, commits and branch integration. +- Keep independent work objectives separate. Related cleanup, general documentation + changes and broader verification need their own scope; necessary implementation + investigation and directly related test maintenance belong to the requested fix. + +## Instruction index + +| Work | Read | +| --- | --- | +| Every task: scope, source authority, progression and shared-tree safety | [Shared work rules](docs/for-agents/workflow.md) | +| Review a problem, investigate behavior or develop a plan | [Investigation and planning](docs/for-agents/investigation-and-planning.md) | +| Design architecture, interfaces or ownership boundaries | [Architecture and design](docs/for-agents/architecture.md) | +| Implement a design, fix behavior, refactor or change tooling/SDK/CI | [Implementation](docs/for-agents/implementation.md) | +| Design, implement or run tests; verify results; use Windows tooling | [Testing and verification](docs/for-agents/testing.md) | +| Maintain the pending testing/QA queue | [Testing-request maintenance](docs/for-agents/testing-requests.md) | +| Use Git, commit, branch or consolidate into `fixes-develop` | [Git](docs/for-agents/git.md), including the established squash strategy | +| Audit, create, improve, consolidate or retire documentation | [Documentation workflow](docs/for-agents/documentation.md) | +| Document API types, fields, methods and templates | [API documentation](docs/for-agents/api-documentation.md), with the documentation workflow | +| Build/check the local documentation or diagnose stale output | [Documentation generation](docs/for-agents/documentation-building.md) | +| Maintain or extend these instruction sets | [Agent instruction library](docs/for-agents/README.md) | + +Choose guides by the work actually being done. A guide explains how to perform +its mode; it does not authorize starting an independent objective. Follow the +existing subject owner linked from that guide instead of loading the entire +library or planning directory. diff --git a/CMakeLists.txt b/CMakeLists.txt index ed59da74..5073ebc9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,9 +13,11 @@ endif() # Cheryl build metadata: options, target identities, artifact names, and linkage declarations. include(cmake/CherylOptions.cmake) cheryl_configure_root_options() +include(cmake/CherylLibraries.cmake) include(cmake/CherylTargets.cmake) include(cmake/CherylOutputs.cmake) include(cmake/CherylLinkage.cmake) +include(cmake/CherylPublicHeaders.cmake) set(CHERYL_REPOSITORY_ROOT "${CMAKE_CURRENT_SOURCE_DIR}") set(CHERYL_BUILD_ROOT "${CMAKE_CURRENT_BINARY_DIR}") @@ -53,6 +55,9 @@ endif() if(CHERYL_BUILD_NATIVE_GLFW) add_subdirectory(projects/modules/platform/native-glfw) endif() +if(CHERYL_BUILD_NATIVE_SDL3) + add_subdirectory(projects/modules/platform/native-sdl3) +endif() if(CHERYL_BUILD_OPENGL) add_subdirectory(projects/modules/graphics/opengl) endif() @@ -65,8 +70,34 @@ endif() if(CHERYL_BUILD_AUDIO_MINIAUDIO) add_subdirectory(projects/modules/audio/miniaudio) endif() +if(CHERYL_BUILD_WORKERS AND (CHERYL_BUILD_WORKER_TOPOLOGY_HWLOC OR CHERYL_BUILD_WORKER_TOPOLOGY_JSON)) + add_subdirectory(projects/modules/runtime/workers) +endif() +if(CHERYL_BUILD_STARTUP) + add_subdirectory(projects/modules/runtime/startup) +endif() + +cheryl_select_public_component(Cheryl::Engine) +if(CHERYL_BUILD_STARTUP) + cheryl_select_public_component(Cheryl::Startup) + if(CHERYL_STARTUP_OPENGL) + cheryl_select_public_component(Cheryl::OpenGL::Startup) + endif() +endif() +foreach(component IN ITEMS NATIVE_GLFW OPENGL UI_TGUI UI_RMLUI AUDIO_MINIAUDIO) + if(CHERYL_BUILD_${component}) + cheryl_select_public_component(${TARGET_LIB_MODULE_${component}}) + endif() +endforeach() +if(TARGET Cheryl::Workers) + cheryl_select_public_component(Cheryl::Workers) +endif() +if(CHERYL_BUILD_DEBUG_TERMINAL_LINUX) + cheryl_select_public_component(Cheryl::DebugTerminal::Linux) +endif() if(CHERYL_BUILD_TESTS) + add_subdirectory(projects/engine/tests) add_subdirectory(projects/tests) endif() @@ -82,11 +113,18 @@ endif() if(CHERYL_BUILD_FIRST_GAME AND CHERYL_BUILD_NATIVE_GLFW AND CHERYL_BUILD_OPENGL AND CHERYL_NATIVE_INPUT) add_subdirectory(projects/apps/first-game) endif() +if(CHERYL_BUILD_TUTORIAL_GAME) + add_subdirectory(projects/apps/tutorial-game) +endif() if(CHERYL_BUILD_SDK) include(cmake/CherylSdk.cmake) endif() +include(cmake/CherylRuntime.cmake) +cheryl_stage_shared_runtimes() +cheryl_write_dependency_inventory() + cheryl_check_option_requirements() if(CHERYL_BUILD_DOCS) diff --git a/README.md b/README.md index 080325a0..dd582f9b 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,10 @@ application. [Read the documentation](https://cheryl-engine.readthedocs.io/) for the API reference, setup guides and first-game walkthrough. -The default assembly includes Native GLFW, OpenGL, TGUI and the windowed demo. -RmlUi and miniaudio are independently selectable and default to `OFF`. -Linux GNU/Clang also selects an optional output-terminal module; its implementation -is linked automatically only in actual Debug configurations. +The default assembly includes Startup, Workers with hwloc/JSON, Native GLFW, +OpenGL, TGUI, RmlUi and miniaudio. The demo and tests are opt-in. +Single-configuration Linux GNU/Clang Debug builds also select the optional +output-terminal module by default. Other configurations default it to `OFF`. ## Contents @@ -27,8 +27,14 @@ is linked automatically only in actual Debug configurations. ## Setup You need Git, CMake 3.28 or newer, Ninja, and a C++23 compiler and standard library -with `std::format` support. Install the selected modules' -[system dependencies](docs/development/building.md#system-dependencies). +with `std::format` support. The default root configuration also requires Python +3.11 or newer for documentation targets and hwloc 2.5+ (2.x) for worker support. +Set `CHERYL_BUILD_DOCS=OFF` or `CHERYL_BUILD_WORKER_TOPOLOGY_HWLOC=OFF` to omit +the corresponding requirement. Install the selected modules' +[system dependencies](docs/site/development/building.md#system-dependencies). +Linux builds use ordinary shared development packages. Windows requires Windows +10 version 1903+ and the SDK's ICU C API; prepare its external shared dependencies +using the [dependency helper](docs/site/development/building.md#dependency-and-toolchain-settings). ```sh git clone https://github.com/cppcooper/cheryl-engine.git @@ -42,12 +48,19 @@ Configure and build the demo from anywhere inside the checkout: ( set -e cd "$(git rev-parse --show-toplevel)" - cmake -S . -B build/release -G Ninja -DCMAKE_BUILD_TYPE=Release + cmake -S . -B build/release -G Ninja -DCMAKE_BUILD_TYPE=Release \ + -DCHERYL_BUILD_DEMO=ON cmake --build build/release --target demo --parallel ./build/release/demo ) ``` +The command uses Linux system packages. Windows uses the prepared +`x64-windows` prefix and DLL CRT configuration from the build guide. FreeType, +HarfBuzz, GLFW, the UI toolkits and optional hwloc/HIDAPI are shared dependencies. +Windows uses built-in ICU; Linux uses its distribution's ICU. miniaudio, spdlog, +Gainput and GLAD remain embedded in their Cheryl owners. + The demo starts with checked-in shaders and an embedded font. Optional package images add tile/sprite samples; missing images do not prevent startup. WASD pans, R resets the camera, F2 selects text focus, Escape releases it and Q quits. @@ -66,7 +79,7 @@ After changing branches or updating the checkout, restore its pinned dependencie ) ``` -The [build guide](docs/development/building.md) lists configuration options, +The [build guide](docs/site/development/building.md) lists configuration options, dependency settings, Debug/Release behavior and common setup failures. Existing build directories retain cached options; reload CMake after changing selections. @@ -78,26 +91,26 @@ the public aliases to inherit headers and dependencies. | Build target | Public alias | Purpose | | --- | --- | --- | | `cengine` | `Cheryl::Engine` | Neutral engine contracts and implementation. | -| `cengine_startup` | `Cheryl::Startup` | Optional command-line parsing, validated configuration and custom backend startup. | -| `cengine_worker_topology_hwloc` | `Cheryl::WorkerTopology::Hwloc` | Optional runtime core/cache discovery and worker thread binding. | -| `cengine_worker_topology_json` | `Cheryl::WorkerTopology::Json` | Optional versioned worker settings loader; works without hwloc. | +| `module_startup` | `Cheryl::Startup` | Generic application bootstrap, validated command-line configuration and selected backend integration. | +| `module_workers` | `Cheryl::Workers` | Independently selectable hwloc discovery/binding and JSON worker settings; capability-specific legacy aliases refer to this binary. | | `module_native_glfw` | `Cheryl::NativeGLFW` | Display, windows and optional input. | | `module_debug_terminal_linux` | `Cheryl::DebugTerminal::Linux` | Optional Linux process output capture/viewer; an interface selects the implementation by configuration. | -| `cengine_debug_terminal_startup` | `Cheryl::DebugTerminal::Startup` | Small argument/startup attachment accepting a module implementation callback. | +| `cengine_debug_terminal_startup` | `Cheryl::DebugTerminal::Startup` | Private static argument/startup attachment accepting a module implementation callback. | | `module_opengl` | `Cheryl::OpenGL` | Rendering, context, presentation and resources; requires Native GLFW. | -| `module_opengl_startup` | `Cheryl::OpenGL::Startup` | GLFW/OpenGL command-line startup; includes common Startup and OpenGL requirements. | +| `module_opengl_startup` (interface) | `Cheryl::OpenGL::Startup` | Compatibility interface linking Startup and OpenGL; produces no additional binary. | | `module_ui_tgui` | `Cheryl::UI::TGUI` | TGUI widgets and retained scene bridge. | | `module_ui_rmlui` | `Cheryl::UI::RmlUi` | RML/RCSS documents and retained scene bridge. | | `module_audio_miniaudio` | `Cheryl::Audio::Miniaudio` | Audio decoding, mixing, native output and streaming. | | `demo` | — | Example native application. | -| `first-game` | — | Opt-in maintained game from the [walkthrough](docs/getting-started/first-game.md), with its own assets. | -| `docs`, `docs-tools` | — | Optional documentation generation and shared tool setup; also available through the docs-only CMake project. | +| `first-game` | — | Opt-in maintained game from the [walkthrough](docs/site/getting-started/first-game.md), with its own assets. | +| `tutorial-game` | — | Opt-in Controller/Character, joining players, patrol and TGUI HUD example supporting the [tutorials](docs/site/getting-started/create-a/index.md) and [task index](docs/site/getting-started/how-to/index.md). | +| `docs`, `docs-clean`, `docs-tools` | — | Optional incremental generation, full regeneration and shared documentation tool setup; also available through the docs-only CMake project. | | `cheryl-sdk` | — | Builds selected installable libraries when `CHERYL_BUILD_SDK=ON`. | See the [module index](projects/modules/README.md) for each owner's API and the -[consumption guide](docs/development/consuming-engine.md) for application CMake +[consumption guide](docs/site/development/consuming-engine.md) for application CMake composition. Applications can also use an installed prebuilt SDK through -[`find_package(Cheryl)`](docs/development/consuming-engine.md#prebuilt-development-sdks). +[`find_package(Cheryl 4 CONFIG REQUIRED)`](docs/site/development/consuming-engine.md#prebuilt-development-sdks). ## Tests @@ -114,18 +127,21 @@ With tests enabled, build and run the combined GoogleTest runner for selected ow ) ``` -The [test guide](docs/development/testing.md) explains focused runners, aggregates, +The [test guide](docs/site/development/testing.md) explains focused runners, aggregates, CTest selection, consumers and environment opt-ins. Native graphics and real-font checks need their stated prerequisites; skipped cases leave that coverage pending. The [testing queue](docs/testing-requests.md) contains pending automated checks and QA. ## CI and development SDKs -[GitHub CI](docs/development/building.md#github-ci-and-sdk-artifacts) selects Linux +[GitHub CI](docs/site/development/ci.md) selects Linux GCC/Clang and Windows MSVC builds, focused tests and installed SDK consumers. Successful main-push validation collects prebuilt development SDKs as workflow artifacts. They contain compiled libraries, public/inline/template headers, CMake -integration and licenses; Windows also bundles native dependency libraries/DLLs. +integration and licenses. SDKs use the ordinary build's dependency policy: +embedded libraries stay inside their shared Cheryl owners, shared native runtimes +are packaged or declared as platform requirements, and static Cheryl SDKs also +retain their embedded archive closure for final linking. The demo and C/C++ implementation files are excluded. GitHub release publication is separate, and exact hosted Linux/Windows acceptance remains pending the first run. @@ -141,34 +157,42 @@ root CMake file selects owners; `cmake/` supplies shared metadata and compositio helpers. `extern/` holds pinned dependencies and the `assets/` submodule holds definitions, textures, shader recipes, UI documents and font inputs. Initialize it alongside the dependencies; optional package images still follow the -[catalog](docs/assets/catalog.md). See [module authoring](docs/development/modules.md) +[catalog](docs/site/assets/catalog.md). See [module authoring](docs/site/development/modules.md) for the steps required to add an owner. ## Documentation and development +From the checkout root, run `./generate-docs.sh` to update the local manual and API +reference. Add `--clean` for full regeneration, `--extract-only` for Doxygen XML, +or `--help` for output and tool-selection options. The +[generation guide](docs/site/development/documentation.md) also covers Python and CMake +entry points and hosted publication. +The generated entry point is `docs/site/generation/generated/html/index.html`. + | Topic | Documentation | |----------------------------------------| --- | | Complete documentation index | [All guides and system references](docs/README.md) | -| Engine architecture | [Runtime ownership and backend boundaries](docs/runtime/runtime-architecture.md), [frame lifecycle](docs/runtime/runtime-frame-boundary.md) and [module roles](projects/modules/README.md) | -| **CMake configuration** and **builds** | [Options and defaults](docs/development/building.md#configuration-options), [build configurations](docs/development/building.md#build-configurations) and [engine-only builds](docs/development/building.md#engine-only-configuration) | -| Dependencies | [SDK selection and toolchain settings](docs/development/building.md#dependency-and-toolchain-settings), [system requirements](docs/development/building.md#system-dependencies) and [public headers and link requirements](docs/development/consuming-engine.md#dependencies-and-headers) | -| Targets and artifacts | [Module targets and aliases](projects/modules/README.md), [test targets](docs/development/testing.md#runner-selection) and [artifact name definitions](cmake/CherylOutputs.cmake) | -| Writing a game | [First-game walkthrough](docs/getting-started/first-game.md), [application composition and startup](docs/development/consuming-engine.md) and [game hooks and ownership](docs/development/consuming-engine.md#game-hooks-and-ownership) | -| Documentation generation and hosting | [Local generation, shared tools and versioned Read the Docs setup](docs/development/documentation.md), [generated API entry points](docs/api/index.md) | -| Adding a module | [Module authoring](docs/development/modules.md#add-a-module), [standalone composition](docs/development/modules.md#standalone-modules) and [UI adapters](docs/development/ui-adapters.md) | -| Platform and graphics APIs | [Native GLFW](projects/modules/platform/native-glfw/README.md) and [OpenGL](projects/modules/graphics/opengl/README.md) | -| UI APIs | [TGUI](projects/modules/ui/tgui/README.md) and [RmlUi](projects/modules/ui/rmlui/README.md) | -| Audio APIs | [Engine audio contracts](docs/runtime/audio.md) and [miniaudio integration](projects/modules/audio/miniaudio/README.md) | -| Input, timing and concurrency | [Input state and events](docs/runtime/input-state-model.md), [simulation timing](docs/runtime/simulation-timing.md) and [thread dispatch](docs/runtime/thread-dispatch.md) | -| Assets and text | [Asset loading](docs/assets/asset-loading.md), [manifest format](docs/assets/asset-manifests.md) and [Unicode text layout](docs/assets/text-layout.md) | -| Rendering and resources | [Pipelines and materials](docs/rendering/pipelines-and-materials.md), [CPU memory lifetime](docs/resources/resource-lifetime.md) and [resource residency](docs/resources/resource-residency.md) | -| Logging and crash handling | [Compile-time logging policy](docs/runtime/logging.md#compile-policy), [runtime logging](docs/runtime/logging.md#runtime-settings-and-ownership) and [crash bootstrap and exception traces](docs/development/consuming-engine.md#crash-and-exception-traces) | -| Native output terminal | [Linux module, activation and viewer lifetime](projects/modules/platform/debug-terminal-linux/README.md) | -| Demo | [Controls](projects/apps/demo/README.md#controls), [command-line options](projects/apps/demo/README.md#command-line-options) and [asset packages](docs/assets/catalog.md) | -| Automated tests | [Runners and CTest selection](docs/development/testing.md), [acceptance checks](docs/development/testing.md#acceptance-and-consumers) and [logging acceptance](docs/development/logging-acceptance.md) | -| Consumers and header probes | [Selection and targets](docs/development/testing.md#acceptance-and-consumers) and [independent consumer builds](docs/development/consuming-engine.md#independent-consumers-and-header-probes) | -| Development SDKs and CI | [SDK production](docs/development/building.md#development-sdk-builds), [prebuilt consumption](docs/development/consuming-engine.md#prebuilt-development-sdks) and [hosted CI matrix](docs/development/building.md#github-ci-and-sdk-artifacts) | -| Architecture validation and QA | [Validation procedures](docs/development/architecture-validation.md), [desktop checks](docs/development/native-desktop-checks.md) and [pending testing requests](docs/testing-requests.md) | -| Troubleshooting | [Build and dependency issues](docs/development/building.md#troubleshooting) and [compiler discovery and standard headers](docs/development/consuming-engine.md#standard-headers-in-an-existing-build) | -| Contributing | [Development workflow](docs/development/contributing.md), [C++ style](docs/development/code-style.md) and [agent instructions](AGENTS.md) | -| Planning | [Planning catalogue](docs/planning/README.md) and [development roadmap](docs/planning/develop-review-and-development-plan.md) | +| Engine systems and integration | [System relationships](docs/site/engine-systems/index.md), [application integration](docs/site/development/consuming-engine.md), [frame lifecycle](docs/site/runtime/runtime-frame-boundary.md) and [module selection](docs/site/modules/index.md) | +| Engine systems | [Purpose, ownership, execution and integration by system](docs/site/engine-systems/index.md) | +| **CMake configuration** and **builds** | [Options and defaults](docs/site/development/building.md#configuration-options), [build configurations](docs/site/development/building.md#build-configurations) and [engine-only builds](docs/site/development/building.md#engine-only-configuration) | +| Dependencies | [SDK selection and toolchain settings](docs/site/development/building.md#dependency-and-toolchain-settings), [system requirements](docs/site/development/building.md#system-dependencies) and [public headers and link requirements](docs/site/development/consuming-engine.md#dependencies-and-headers) | +| Targets and artifacts | [Module targets and aliases](projects/modules/README.md), [test targets](docs/site/development/testing.md#runner-selection) and [artifact name definitions](cmake/CherylOutputs.cmake) | +| Writing a game | [Project setup](docs/site/getting-started/project-setup.md), [Tutorials](docs/site/getting-started/create-a/index.md), [Task index](docs/site/getting-started/how-to/index.md) and [Guides by topic](docs/site/guides/index.md) | +| Documentation generation and hosting | [Local generation, shared tools and versioned Read the Docs setup](docs/site/development/documentation.md), [generated API entry points](docs/site/api/index.md) | +| Adding a module | [Module authoring](docs/site/development/modules.md#add-a-module), [standalone composition](docs/site/development/modules.md#standalone-modules) and [UI adapters](docs/site/development/ui-adapters.md) | +| Platform and graphics APIs | [Native GLFW](docs/site/modules/native-glfw.md) and [OpenGL](docs/site/modules/opengl.md) | +| UI APIs | [TGUI](docs/site/modules/tgui.md) and [RmlUi](docs/site/modules/rmlui.md) | +| Audio APIs | [Engine audio contracts](docs/site/runtime/audio.md) and [miniaudio integration](docs/site/modules/miniaudio.md) | +| Input, timing and concurrency | [Input state and events](docs/site/runtime/input-state-model.md), [simulation timing](docs/site/runtime/simulation-timing.md) and [thread dispatch](docs/site/runtime/thread-dispatch.md) | +| Assets and text | [Asset loading](docs/site/assets/asset-loading.md), [manifest format](docs/site/assets/asset-manifests.md) and [Unicode text layout](docs/site/assets/text-layout.md) | +| Rendering and resources | [Pipelines and materials](docs/site/rendering/pipelines-and-materials.md), [CPU memory lifetime](docs/site/resources/resource-lifetime.md) and [resource residency](docs/site/resources/resource-residency.md) | +| Logging and crash handling | [Compile-time logging policy](docs/site/runtime/logging.md#compile-policy), [runtime logging](docs/site/runtime/logging.md#runtime-settings-and-ownership) and [crash bootstrap and exception traces](docs/site/development/consuming-engine.md#crash-and-exception-traces) | +| Native output terminal | [Linux module, activation and viewer lifetime](docs/site/modules/debug-terminal-linux.md) | +| Demo | [Controls](projects/apps/demo/README.md#controls), [command-line options](projects/apps/demo/README.md#command-line-options) and [asset packages](docs/site/assets/catalog.md) | +| Automated tests | [Runners and CTest selection](docs/site/development/testing.md), [acceptance checks](docs/site/development/testing.md#acceptance-and-consumers) and [logging acceptance](docs/site/development/logging-acceptance.md) | +| Consumers and header probes | [Selection and targets](docs/site/development/testing.md#acceptance-and-consumers) and [independent consumer builds](docs/site/development/architecture-validation.md#independent-consumers-and-header-probes) | +| Development SDKs and CI | [SDK production](docs/site/development/building.md#development-sdk-builds), [prebuilt consumption](docs/site/development/consuming-engine.md#prebuilt-development-sdks) and [hosted CI matrix](docs/site/development/ci.md) | +| Architecture validation and QA | [Validation procedures](docs/site/development/architecture-validation.md), [desktop checks](docs/site/development/native-desktop-checks.md) and [pending testing requests](docs/testing-requests.md) | +| Troubleshooting | [Build and dependency issues](docs/site/development/building.md#troubleshooting) and [compiler discovery and standard headers](docs/site/development/building.md#standard-headers-in-an-existing-build) | +| Contributing | [Development workflow](docs/site/development/contributing.md), [C++ style](docs/site/development/code-style.md) and [agent instructions](AGENTS.md) | +| Planning | [Status and selected work](docs/STATUS.md) and [planning catalogue](docs/planning/README.md) | diff --git a/assets b/assets index 5603c197..98453b2a 160000 --- a/assets +++ b/assets @@ -1 +1 @@ -Subproject commit 5603c1974bff4054459edb23d36a52869cba863d +Subproject commit 98453b2a6e0edfcfba22eea3fde801526d72b861 diff --git a/cmake/CherylDependencies.cmake b/cmake/CherylDependencies.cmake index b1524c71..ccb2db77 100644 --- a/cmake/CherylDependencies.cmake +++ b/cmake/CherylDependencies.cmake @@ -3,7 +3,10 @@ if(NOT TARGET Threads::Threads) find_package(Threads REQUIRED) endif() if(NOT TARGET Freetype::Freetype) - find_package(Freetype REQUIRED) + find_package(Freetype CONFIG QUIET) + if(NOT TARGET Freetype::Freetype) + find_package(Freetype REQUIRED) + endif() endif() if(NOT TARGET harfbuzz::harfbuzz) if(TARGET harfbuzz) @@ -17,8 +20,20 @@ if(NOT TARGET harfbuzz::harfbuzz) endif() endif() endif() -if(NOT TARGET ICU::uc OR NOT TARGET ICU::i18n) - find_package(ICU REQUIRED COMPONENTS uc i18n) +if(NOT TARGET Cheryl::ICU) + add_library(cheryl_icu INTERFACE) + add_library(Cheryl::ICU ALIAS cheryl_icu) + if(WIN32) + # Windows 10 1903+ supplies the complete C API used by text layout. + target_link_libraries(cheryl_icu INTERFACE icu) + set_property(TARGET cheryl_icu PROPERTY CHERYL_SHARED_DEPENDENCY_SYSTEM TRUE) + set(ICU_VERSION "system") + else() + if(NOT TARGET ICU::uc OR NOT TARGET ICU::i18n OR NOT TARGET ICU::data) + find_package(ICU REQUIRED COMPONENTS uc i18n data) + endif() + target_link_libraries(cheryl_icu INTERFACE ICU::uc ICU::i18n ICU::data) + endif() endif() if(CHERYL_BUILD_TESTS AND NOT TARGET gtest) add_subdirectory("${CHERYL_REPOSITORY_ROOT}/extern/googletest" extern/googletest EXCLUDE_FROM_ALL) @@ -28,12 +43,17 @@ if(NOT TARGET ctti) add_subdirectory("${CHERYL_REPOSITORY_ROOT}/extern/ctti" extern/ctti EXCLUDE_FROM_ALL) endif() if(NOT TARGET Backward::Interface) - set(BACKWARD_SHARED STATIC) + set(BACKWARD_SHARED OFF) add_subdirectory("${CHERYL_REPOSITORY_ROOT}/extern/backward-cpp" extern/backward-cpp EXCLUDE_FROM_ALL) endif() if(NOT TARGET spdlog::spdlog) - set(SPDLOG_USE_STD_FORMAT ON) - add_subdirectory("${CHERYL_REPOSITORY_ROOT}/extern/spdlog" extern/spdlog EXCLUDE_FROM_ALL) + block(SCOPE_FOR VARIABLES) + set(SPDLOG_USE_STD_FORMAT ON) + set(BUILD_SHARED_LIBS OFF) + set(SPDLOG_BUILD_SHARED OFF) + add_subdirectory("${CHERYL_REPOSITORY_ROOT}/extern/spdlog" extern/spdlog EXCLUDE_FROM_ALL) + endblock() + set_property(TARGET spdlog PROPERTY CHERYL_OWNED_DEPENDENCY TRUE) endif() if(NOT TARGET glm::glm-header-only) set(GLM_ENABLE_EXPERIMENTAL ON) diff --git a/cmake/CherylDocumentation.cmake b/cmake/CherylDocumentation.cmake index 55bb9bf8..3adeb6e0 100644 --- a/cmake/CherylDocumentation.cmake +++ b/cmake/CherylDocumentation.cmake @@ -1,4 +1,5 @@ include_guard(GLOBAL) +include("${CMAKE_CURRENT_LIST_DIR}/CherylOptions.cmake") function(cheryl_add_documentation_targets repository_root) if(TARGET docs) @@ -8,9 +9,7 @@ function(cheryl_add_documentation_targets repository_root) set(Python3_EXECUTABLE "${CHERYL_DOCS_PYTHON}") endif() find_package(Python3 3.11 REQUIRED COMPONENTS Interpreter) - set(CHERYL_DOCS_PYTHON "${Python3_EXECUTABLE}" CACHE FILEPATH "Python interpreter for documentation tool selection") - set(CHERYL_DOCS_CACHE_DIR "" CACHE PATH "Shared documentation tool cache; empty uses the user cache") - option(CHERYL_DOCS_FORCE_PINNED "Use locked documentation tools instead of compatible installed tools" OFF) + cheryl_configure_documentation_options("${Python3_EXECUTABLE}") set(tool_arguments) if(CHERYL_DOCS_CACHE_DIR) @@ -20,14 +19,20 @@ function(cheryl_add_documentation_targets repository_root) list(APPEND tool_arguments --force-pinned) endif() add_custom_target(docs-tools - COMMAND "${CHERYL_DOCS_PYTHON}" "${repository_root}/docs/generation/bootstrap.py" setup ${tool_arguments} + COMMAND "${CHERYL_DOCS_PYTHON}" "${repository_root}/docs/site/generation/bootstrap.py" setup ${tool_arguments} WORKING_DIRECTORY "${repository_root}" COMMENT "Preparing shared documentation tools" USES_TERMINAL VERBATIM) add_custom_target(docs - COMMAND "${CHERYL_DOCS_PYTHON}" "${repository_root}/docs/generation/bootstrap.py" exec ${tool_arguments} - -- python "${repository_root}/docs/generation/build.py" + COMMAND "${CHERYL_DOCS_PYTHON}" "${repository_root}/docs/site/generation/bootstrap.py" exec ${tool_arguments} + -- python "${repository_root}/docs/site/generation/build.py" WORKING_DIRECTORY "${repository_root}" - COMMENT "Generating Cheryl documentation" + COMMENT "Updating Cheryl documentation" + USES_TERMINAL VERBATIM) + add_custom_target(docs-clean + COMMAND "${CHERYL_DOCS_PYTHON}" "${repository_root}/docs/site/generation/bootstrap.py" exec ${tool_arguments} + -- python "${repository_root}/docs/site/generation/build.py" --clean + WORKING_DIRECTORY "${repository_root}" + COMMENT "Regenerating Cheryl documentation from scratch" USES_TERMINAL VERBATIM) endfunction() diff --git a/cmake/CherylLibraries.cmake b/cmake/CherylLibraries.cmake new file mode 100644 index 00000000..9d8e049e --- /dev/null +++ b/cmake/CherylLibraries.cmake @@ -0,0 +1,395 @@ +include_guard(GLOBAL) + +include("${CMAKE_CURRENT_LIST_DIR}/CherylOptions.cmake") +cheryl_configure_library_options() + +set(CHERYL_LIBRARY_TYPE STATIC) +if(CHERYL_BUILD_SHARED_LIBS) + set(CHERYL_LIBRARY_TYPE SHARED) + # Owned static dependencies may be incorporated into a Cheryl shared library. + set(CMAKE_POSITION_INDEPENDENT_CODE ON) +endif() + +function(cheryl_configure_library target component) + get_target_property(kind ${target} TYPE) + get_target_property(version ${target} VERSION) + get_target_property(output_name ${target} OUTPUT_NAME) + set_target_properties(${target} PROPERTIES + DEFINE_SYMBOL "CE_BUILDING_${component}" + LIBRARY_OUTPUT_DIRECTORY "${CHERYL_BUILD_ROOT}" + RUNTIME_OUTPUT_DIRECTORY "${CHERYL_BUILD_ROOT}") + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + file(MAKE_DIRECTORY "${CHERYL_BUILD_ROOT}/link-maps") + target_link_options(${target} PRIVATE "LINKER:-Map,${CHERYL_BUILD_ROOT}/link-maps/${target}.map") + elseif(MSVC) + target_link_options(${target} PRIVATE "/MAP:${CHERYL_BUILD_ROOT}/${target}.map") + endif() + if(kind STREQUAL "STATIC_LIBRARY") + target_compile_definitions(${target} PUBLIC "CE_${component}_STATIC") + else() + set_target_properties(${target} PROPERTIES + CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN YES) + if(version) + string(REGEX MATCH "^[0-9]+" abi_version "${version}") + set_target_properties(${target} PROPERTIES SOVERSION "${abi_version}") + endif() + if(UNIX AND NOT APPLE) + set_target_properties(${target} PROPERTIES INSTALL_RPATH "$ORIGIN") + elseif(APPLE) + set_target_properties(${target} PROPERTIES INSTALL_RPATH "@loader_path") + endif() + if(NOT component STREQUAL "ENGINE" AND TARGET Cheryl::Engine) + get_target_property(engine_type Cheryl::Engine TYPE) + if(engine_type STREQUAL "STATIC_LIBRARY") + message(FATAL_ERROR "Shared Cheryl components require a shared Cheryl::Engine") + endif() + endif() + endif() + if(version AND output_name AND (kind STREQUAL "STATIC_LIBRARY" OR WIN32)) + set_target_properties(${target} PROPERTIES OUTPUT_NAME "${output_name}-${version}") + endif() +endfunction() + +function(cheryl_write_dependency_inventory) + get_property(dependencies GLOBAL PROPERTY CHERYL_PRIVATE_DEPENDENCIES) + set(entries) + foreach(declaration IN LISTS dependencies) + string(REPLACE "|" ";" fields "${declaration}") + list(GET fields 0 dependency) + list(GET fields 1 owner) + list(APPEND entries "{\"dependency\":\"${dependency}\",\"target_owner\":\"${owner}\",\"archive\":\"$\",\"binary\":\"$\"}") + endforeach() + list(JOIN entries ",\n" records) + file(GENERATE OUTPUT "${CHERYL_BUILD_ROOT}/cheryl-dependency-ownership.json" + CONTENT "{\"schema\":1,\"dependencies\":[\n${records}\n]}\n") + + get_property(shared_dependencies GLOBAL PROPERTY CHERYL_SHARED_DEPENDENCIES) + set(entries) + foreach(declaration IN LISTS shared_dependencies) + string(REPLACE "|" ";" fields "${declaration}") + list(GET fields 0 dependency) + list(GET fields 1 consumer) + get_target_property(kind "${dependency}" TYPE) + get_target_property(system "${dependency}" CHERYL_SHARED_DEPENDENCY_SYSTEM) + get_target_property(owned "${dependency}" CHERYL_OWNED_DEPENDENCY) + set(library "") + if(NOT kind STREQUAL "INTERFACE_LIBRARY") + set(library "$") + endif() + list(APPEND entries "{\"dependency\":\"${dependency}\",\"consumer\":\"${consumer}\",\"library\":\"${library}\",\"system\":$,true,false>,\"owned\":$,true,false>}") + endforeach() + list(JOIN entries ",\n" records) + file(GENERATE OUTPUT "${CHERYL_BUILD_ROOT}/cheryl-shared-dependencies.json" + CONTENT "{\"schema\":1,\"dependencies\":[\n${records}\n]}\n") +endfunction() + + +# Platform runtime archives are external system inputs, including glibc's empty +# compatibility archives. A system directory alone never exempts a private SDK. +function(cheryl_is_system_archive result path) + set(${result} OFF PARENT_SCOPE) + string(REPLACE "\\" "/" normalized "${path}") + cmake_path(NORMAL_PATH normalized) + get_filename_component(name "${normalized}" NAME) + if(UNIX AND name MATCHES "^lib(c|c_nonshared|m|dl|rt|pthread)\\.a$") + set(system_directories /lib /lib64 /usr/lib /usr/lib64 + ${CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES} + ${CMAKE_C_IMPLICIT_LINK_DIRECTORIES} ${CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES}) + foreach(root IN ITEMS "${CMAKE_SYSROOT}" ${CMAKE_FIND_ROOT_PATH}) + if(root) + list(APPEND system_directories "${root}/lib" "${root}/lib64" "${root}/usr/lib" "${root}/usr/lib64") + endif() + endforeach() + foreach(directory IN LISTS system_directories) + cmake_path(IS_PREFIX directory "${normalized}" NORMALIZE within_system) + if(within_system) + set(${result} ON PARENT_SCOPE) + return() + endif() + endforeach() + elseif(WIN32) + string(TOLOWER "${normalized}" normalized) + string(TOLOWER "${name}" name) + if(normalized MATCHES "/(windows kits/[0-9.]+|microsoft sdks/windows/v[0-9.a-z]+)/lib/" AND + name MATCHES "^(advapi32|bcrypt|cfgmgr32|comctl32|comdlg32|crypt32|dbghelp|dnsapi|d3d11|d3d12|dwmapi|dxgi|dxguid|gdi32|glu32|hid|imm32|iphlpapi|kernel32|netapi32|ncrypt|normaliz|ntdll|ole32|oleaut32|opengl32|powrprof|psapi|secur32|setupapi|shell32|shlwapi|ucrt|ucrtd|user32|uuid|version|wbemuuid|windowscodecs|winmm|winspool|winusb|ws2_32)\\.lib$") + set(${result} ON PARENT_SCOPE) + endif() + endif() +endfunction() + +# Shared dependencies own their implementations in their own runtime libraries. +# OWNER names the Cheryl consumer, independently of development or SDK selection. +function(cheryl_shared_dependency target) + cmake_parse_arguments(dependency "OWNED;SYSTEM" "OWNER" "" ${ARGN}) + if(NOT dependency_OWNER OR NOT TARGET "${dependency_OWNER}" OR NOT TARGET "${target}") + message(FATAL_ERROR "cheryl_shared_dependency requires an existing dependency and OWNER target") + endif() + get_target_property(concrete "${target}" ALIASED_TARGET) + if(NOT concrete) + set(concrete "${target}") + endif() + get_target_property(consumer "${dependency_OWNER}" ALIASED_TARGET) + if(NOT consumer) + set(consumer "${dependency_OWNER}") + endif() + string(SHA256 identity "${concrete}") + set_property(GLOBAL PROPERTY "CHERYL_SHARED_ADMITTED_${identity}" TRUE) + get_target_property(kind "${concrete}" TYPE) + get_target_property(imported "${concrete}" IMPORTED) + if(dependency_OWNED AND imported) + message(FATAL_ERROR "OWNED cannot change the imported dependency ${target}") + endif() + if(kind STREQUAL "INTERFACE_LIBRARY" AND NOT dependency_SYSTEM) + get_target_property(links "${concrete}" INTERFACE_LINK_LIBRARIES) + set(found OFF) + foreach(link IN LISTS links) + if(TARGET "${link}") + cheryl_shared_dependency("${link}" OWNER "${consumer}") + set(found ON) + elseif(IS_ABSOLUTE "${link}" AND link MATCHES "\\.(so(\\.[0-9.]+)?|dylib|dll)$") + string(SHA256 library_identity "${link}") + set(library "cheryl_shared_${library_identity}") + if(NOT TARGET "${library}") + add_library("${library}" SHARED IMPORTED GLOBAL) + set_target_properties("${library}" PROPERTIES IMPORTED_LOCATION "${link}") + endif() + cheryl_shared_dependency("${library}" OWNER "${consumer}") + set(found ON) + endif() + endforeach() + if(NOT found) + message(FATAL_ERROR "Cannot establish a shared implementation behind ${target}") + endif() + return() + elseif(kind STREQUAL "UNKNOWN_LIBRARY") + get_target_property(configurations "${concrete}" IMPORTED_CONFIGURATIONS) + set(locations) + foreach(configuration IN ITEMS "" ${configurations}) + set(suffix "") + if(configuration) + string(TOUPPER "_${configuration}" suffix) + endif() + get_target_property(location "${concrete}" "IMPORTED_LOCATION${suffix}") + if(location) + list(APPEND locations "${location}") + endif() + endforeach() + if(NOT locations) + message(FATAL_ERROR "Cannot establish the shared implementation of ${target}") + endif() + foreach(location IN LISTS locations) + if(WIN32 AND location MATCHES "\\.[lL][iI][bB]$") + message(FATAL_ERROR + "${target} needs an imported SHARED target with its runtime DLL in IMPORTED_LOCATION and its import library in IMPORTED_IMPLIB; ${location} alone cannot describe DLL deployment") + elseif(NOT location MATCHES "\\.(so(\\.[0-9.]+)?|dylib|dll)$") + message(FATAL_ERROR "${target} requires a shared implementation; found ${location}") + endif() + endforeach() + elseif(NOT kind STREQUAL "SHARED_LIBRARY" AND NOT (kind STREQUAL "INTERFACE_LIBRARY" AND dependency_SYSTEM)) + message(FATAL_ERROR "${target} requires a shared implementation; found ${kind}") + endif() + if(dependency_OWNED) + set_property(TARGET "${concrete}" PROPERTY CHERYL_OWNED_DEPENDENCY TRUE) + endif() + if(dependency_SYSTEM) + set_property(TARGET "${concrete}" PROPERTY CHERYL_SHARED_DEPENDENCY_SYSTEM TRUE) + endif() + get_property(declarations GLOBAL PROPERTY CHERYL_SHARED_DEPENDENCIES) + set(declaration "${concrete}|${consumer}") + if(NOT declaration IN_LIST declarations) + set_property(GLOBAL APPEND PROPERTY CHERYL_SHARED_DEPENDENCIES "${declaration}") + endif() +endfunction() + +# A compiled external implementation belongs to one Cheryl binary. Supplied +# targets are inspected without changing their build or interface settings. +function(cheryl_private_dependency target) + cmake_parse_arguments(dependency "OWNED;PUBLIC_API;TRANSITIVE" "OWNER;RESULT" "" ${ARGN}) + if(dependency_RESULT) + set(${dependency_RESULT} OFF PARENT_SCOPE) + endif() + if(NOT dependency_OWNER OR NOT TARGET "${dependency_OWNER}" OR NOT TARGET "${target}") + message(FATAL_ERROR "cheryl_private_dependency requires an existing dependency and OWNER target") + endif() + get_target_property(concrete "${target}" ALIASED_TARGET) + if(NOT concrete) + set(concrete "${target}") + endif() + get_target_property(owner "${dependency_OWNER}" ALIASED_TARGET) + if(NOT owner) + set(owner "${dependency_OWNER}") + endif() + if(dependency_TRANSITIVE) + get_property(public_components GLOBAL PROPERTY CHERYL_AVAILABLE_COMPONENTS) + if(concrete IN_LIST public_components OR target MATCHES "^(Threads|X11|OpenGL)::") + return() + endif() + endif() + string(SHA256 identity "${concrete}") + get_property(shared_dependency GLOBAL PROPERTY "CHERYL_SHARED_ADMITTED_${identity}") + if(dependency_TRANSITIVE AND shared_dependency) + return() + endif() + get_property(previous_owner GLOBAL PROPERTY "CHERYL_DEPENDENCY_OWNER_${identity}") + if(previous_owner AND NOT previous_owner STREQUAL owner) + message(FATAL_ERROR "${target} already belongs to ${previous_owner}; it cannot also belong to ${owner}") + endif() + get_target_property(kind "${concrete}" TYPE) + get_target_property(imported "${concrete}" IMPORTED) + if(dependency_OWNED AND imported) + message(FATAL_ERROR "OWNED cannot change the imported dependency ${target}") + endif() + set(found_implementation OFF) + set(locations) + if(kind STREQUAL "INTERFACE_LIBRARY") + # Header-only children are valid; a top-level wrapper must lead to an archive. + elseif(kind STREQUAL "OBJECT_LIBRARY") + message(FATAL_ERROR "${owner} requires an archive dependency; OBJECT_LIBRARY ${target} is not supported") + elseif(kind STREQUAL "UNKNOWN_LIBRARY") + get_target_property(configurations "${concrete}" IMPORTED_CONFIGURATIONS) + foreach(configuration IN ITEMS "" ${configurations}) + set(suffix "") + if(configuration) + string(TOUPPER "_${configuration}" suffix) + endif() + get_target_property(location "${concrete}" "IMPORTED_LOCATION${suffix}") + get_target_property(import_library "${concrete}" "IMPORTED_IMPLIB${suffix}") + if(import_library) + message(FATAL_ERROR "${target} supplies a shared-library import; ${owner} needs a static implementation") + endif() + if(location) + list(APPEND locations "${location}") + endif() + endforeach() + if(NOT locations) + message(FATAL_ERROR "Cannot establish the static implementation of ${target}") + endif() + foreach(location IN LISTS locations) + if(NOT location MATCHES "\\.([aA]|[lL][iI][bB])$") + message(FATAL_ERROR "${owner} requires a static ${target}; found ${location}. Prepare static dependencies or supply compatible static targets.") + endif() + endforeach() + set(found_implementation ON) + elseif(kind STREQUAL "STATIC_LIBRARY") + set(found_implementation ON) + else() + message(FATAL_ERROR "${owner} requires a static ${target}; found ${kind}. Prepare static dependencies or supply compatible static targets.") + endif() + + # Break cycles by owner and concrete target. Compiled nodes still count as + # implementations when reached through an in-progress static dependency cycle. + string(SHA256 visit_identity "${owner}|${concrete}") + get_property(visiting GLOBAL PROPERTY "CHERYL_DEPENDENCY_VISITING_${visit_identity}") + if(visiting) + if(dependency_RESULT) + set(${dependency_RESULT} "${found_implementation}" PARENT_SCOPE) + endif() + return() + endif() + set_property(GLOBAL PROPERTY "CHERYL_DEPENDENCY_VISITING_${visit_identity}" TRUE) + + if(imported AND kind STREQUAL "STATIC_LIBRARY") + get_target_property(configurations "${concrete}" IMPORTED_CONFIGURATIONS) + foreach(configuration IN ITEMS "" ${configurations}) + set(suffix "") + if(configuration) + string(TOUPPER "_${configuration}" suffix) + endif() + get_target_property(location "${concrete}" "IMPORTED_LOCATION${suffix}") + if(location) + list(APPEND locations "${location}") + endif() + endforeach() + endif() + foreach(location IN LISTS locations) + get_filename_component(archive "${location}" ABSOLUTE) + if(EXISTS "${location}") + file(REAL_PATH "${location}" archive) + endif() + if(WIN32) + string(TOLOWER "${archive}" archive) + endif() + string(SHA256 archive_identity "${archive}") + get_property(archive_owner GLOBAL PROPERTY "CHERYL_ARCHIVE_OWNER_${archive_identity}") + if(archive_owner AND NOT archive_owner STREQUAL owner) + message(FATAL_ERROR "The archive ${location} already belongs to ${archive_owner}; it cannot also belong to ${owner}") + endif() + set_property(GLOBAL PROPERTY "CHERYL_ARCHIVE_OWNER_${archive_identity}" "${owner}") + endforeach() + if(NOT kind STREQUAL "INTERFACE_LIBRARY") + if(dependency_OWNED) + set_target_properties("${concrete}" PROPERTIES POSITION_INDEPENDENT_CODE ON + C_VISIBILITY_PRESET hidden CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN YES) + endif() + get_target_property(owner_kind ${owner} TYPE) + set(exclusion "LINKER:--exclude-libs,$") + get_property(public_api GLOBAL PROPERTY "CHERYL_DEPENDENCY_PUBLIC_API_${identity}") + if(dependency_PUBLIC_API) + set(public_api TRUE) + set_property(GLOBAL PROPERTY "CHERYL_DEPENDENCY_PUBLIC_API_${identity}" TRUE) + get_target_property(owner_options "${owner}" LINK_OPTIONS) + if(owner_options) + list(REMOVE_ITEM owner_options "${exclusion}") + set_property(TARGET "${owner}" PROPERTY LINK_OPTIONS "${owner_options}") + endif() + endif() + if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND owner_kind STREQUAL "SHARED_LIBRARY" AND NOT public_api) + target_link_options(${owner} PRIVATE "${exclusion}") + endif() + set_property(GLOBAL PROPERTY "CHERYL_DEPENDENCY_OWNER_${identity}" "${owner}") + if(NOT previous_owner) + set_property(GLOBAL APPEND PROPERTY CHERYL_PRIVATE_DEPENDENCIES "${concrete}|${owner}") + endif() + endif() + + get_target_property(interface_links "${concrete}" INTERFACE_LINK_LIBRARIES) + get_target_property(direct_links "${concrete}" LINK_LIBRARIES) + set(links ${interface_links} ${direct_links}) + list(REMOVE_DUPLICATES links) + foreach(link IN LISTS links) + # These wrappers do not change ownership. Inspect configuration branches + # conservatively so every archive a supported configuration can use counts. + while(link MATCHES "^\\$<(LINK_ONLY|BUILD_INTERFACE|TARGET_NAME_IF_EXISTS):(.*)>$") + set(link "${CMAKE_MATCH_2}") + endwhile() + if(link MATCHES "^\\$$") + continue() + endif() + if(link MATCHES "[<>]") + # Flatten remaining generator syntax into candidate link items while + # retaining namespaced target spellings. This also covers conditional + # branches, LINK_LIBRARY and LINK_GROUP without evaluating build config. + string(REPLACE "::" "@CHERYL_NAMESPACE@" candidates "${link}") + string(REGEX REPLACE "[<>:,]" ";" candidates "${candidates}") + string(REPLACE "$" "" candidates "${candidates}") + string(REPLACE "@CHERYL_NAMESPACE@" "::" candidates "${candidates}") + else() + set(candidates "${link}") + endif() + foreach(candidate IN LISTS candidates) + if(TARGET "${candidate}") + set(child_options TRANSITIVE) + if(dependency_PUBLIC_API) + list(APPEND child_options PUBLIC_API) + endif() + cheryl_private_dependency("${candidate}" OWNER "${owner}" ${child_options} RESULT child_implementation) + if(child_implementation) + set(found_implementation ON) + endif() + elseif(IS_ABSOLUTE "${candidate}" AND candidate MATCHES "\\.([aA]|[lL][iI][bB])$") + cheryl_is_system_archive(system_archive "${candidate}") + if(NOT system_archive) + message(FATAL_ERROR + "${target} links unidentified archive ${candidate}. Supply an imported STATIC or UNKNOWN target so Cheryl can validate its ownership.") + endif() + endif() + endforeach() + endforeach() + set_property(GLOBAL PROPERTY "CHERYL_DEPENDENCY_VISITING_${visit_identity}" FALSE) + if(kind STREQUAL "INTERFACE_LIBRARY" AND NOT found_implementation AND NOT dependency_TRANSITIVE) + message(FATAL_ERROR "Cannot establish a static implementation behind ${target}") + endif() + if(dependency_RESULT) + set(${dependency_RESULT} "${found_implementation}" PARENT_SCOPE) + endif() +endfunction() diff --git a/cmake/CherylLinkage.cmake b/cmake/CherylLinkage.cmake index aa538090..67d7763b 100644 --- a/cmake/CherylLinkage.cmake +++ b/cmake/CherylLinkage.cmake @@ -9,9 +9,7 @@ set(LINKAGE_PUBLIC_LIB_ENGINE Threads::Threads ${TARGET_LIB_ENGINE_S_LOGGING_CONFIG} ctti - spdlog::spdlog glm::glm-header-only - Backward::Interface ) set(LINKAGE_INTERFACE_LIB_ENGINE @@ -19,10 +17,11 @@ set(LINKAGE_INTERFACE_LIB_ENGINE ) set(LINKAGE_PRIVATE_LIB_ENGINE + spdlog::spdlog + Backward::Interface Freetype::Freetype harfbuzz::harfbuzz - ICU::uc - ICU::i18n + Cheryl::ICU ) set(LINKAGE_PUBLIC_LIB_ENGINE_S_SIGNAL_HANDLERS @@ -67,11 +66,10 @@ set(LINKAGE_PRIVATE_LIB_MODULE_NATIVE_GLFW_INPUT_LINUX set(LINKAGE_PUBLIC_LIB_MODULE_OPENGL Cheryl::Engine Cheryl::NativeGLFW - ${TARGET_LIB_MODULE_OPENGL_S_GL46} ) set(LINKAGE_PRIVATE_LIB_MODULE_OPENGL - glfw + ${TARGET_LIB_MODULE_OPENGL_S_GL46} OpenGL::GL ) @@ -163,8 +161,7 @@ set(LINKAGE_PUBLIC_TEST_WORKER_TOPOLOGY_COMPOSITION ${TARGET_LIB_ENGINE_S_WORKER_TOPOLOGY_EXAMPLE}) set(LINKAGE_PUBLIC_TEST_MODULE_NATIVE_GLFW - Cheryl::NativeGLFW - glfw) + Cheryl::NativeGLFW) set(LINKAGE_PUBLIC_TEST_MODULE_OPENGL Cheryl::OpenGL) diff --git a/cmake/CherylModules.cmake b/cmake/CherylModules.cmake index 1fb2f955..bb632eb6 100644 --- a/cmake/CherylModules.cmake +++ b/cmake/CherylModules.cmake @@ -1,17 +1,31 @@ include("${CMAKE_CURRENT_LIST_DIR}/CherylVersions.cmake") include("${CMAKE_CURRENT_LIST_DIR}/CherylOptions.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/CherylLibraries.cmake") include("${CMAKE_CURRENT_LIST_DIR}/CherylOutputs.cmake") include("${CMAKE_CURRENT_LIST_DIR}/CherylLinkage.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/CherylPublicHeaders.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/CherylRuntime.cmake") +if(NOT CHERYL_REPOSITORY_ROOT) + get_filename_component(CHERYL_REPOSITORY_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE) +endif() include_guard(GLOBAL) # A module reuses supplied targets or bootstraps one explicitly selected checkout. -# Local variable scopes suppress recursive module/demo/test selection without +# Local variable scopes suppress unrelated build selections without # rewriting the host's cached options or compiling private engine source copies. function(cheryl_prepare_module) cmake_parse_arguments(module "REQUIRES_NATIVE" "" "" ${ARGN}) if(NOT TARGET Cheryl::Engine) block(SCOPE_FOR VARIABLES) + set(CHERYL_BUILD_DOCS OFF) + set(CHERYL_BUILD_SDK OFF) + set(CHERYL_BUILD_STARTUP OFF) + set(CHERYL_STARTUP_OPENGL OFF) + set(CHERYL_BUILD_WORKERS OFF) + set(CHERYL_BUILD_WORKER_TOPOLOGY_HWLOC OFF) + set(CHERYL_BUILD_WORKER_TOPOLOGY_JSON OFF) set(CHERYL_BUILD_NATIVE_GLFW OFF) + set(CHERYL_BUILD_NATIVE_SDL3 OFF) set(CHERYL_BUILD_DEBUG_TERMINAL_LINUX OFF) set(CHERYL_BUILD_OPENGL OFF) set(CHERYL_BUILD_UI_TGUI OFF) @@ -20,6 +34,8 @@ function(cheryl_prepare_module) set(CHERYL_BUILD_TESTS OFF) set(CHERYL_BUILD_CONSUMER_TESTS OFF) set(CHERYL_BUILD_DEMO OFF) + set(CHERYL_BUILD_FIRST_GAME OFF) + set(CHERYL_BUILD_TUTORIAL_GAME OFF) add_subdirectory("${CHERYL_REPOSITORY_ROOT}" "${CMAKE_CURRENT_BINARY_DIR}/engine" EXCLUDE_FROM_ALL) endblock() endif() @@ -47,4 +63,5 @@ function(cheryl_prepare_module) add_subdirectory("${CHERYL_NATIVE_GLFW_SOURCE}" "${CMAKE_CURRENT_BINARY_DIR}/native-glfw" EXCLUDE_FROM_ALL) endblock() endif() + cheryl_stage_shared_runtimes() endfunction() diff --git a/cmake/CherylOptions.cmake b/cmake/CherylOptions.cmake index 6d0bb3b2..f23e70d9 100644 --- a/cmake/CherylOptions.cmake +++ b/cmake/CherylOptions.cmake @@ -11,6 +11,11 @@ function(cheryl_option name description default_value) return() endif() set_property(GLOBAL APPEND PROPERTY CHERYL_DECLARED_OPTIONS "${name}") + get_property(summary_scheduled GLOBAL PROPERTY CHERYL_OPTIONS_SUMMARY_SCHEDULED) + if(NOT summary_scheduled) + cmake_language(DEFER DIRECTORY "${CMAKE_SOURCE_DIR}" CALL cheryl_print_options) + set_property(GLOBAL PROPERTY CHERYL_OPTIONS_SUMMARY_SCHEDULED TRUE) + endif() # Preserve the entry point's value through scoped Engine bootstrapping. set_property(GLOBAL PROPERTY "CHERYL_OPTION_${name}_VALUE" "${${name}}") set_property(GLOBAL PROPERTY "CHERYL_OPTION_${name}_REQUIRES" "${configured_REQUIRES}") @@ -72,9 +77,6 @@ function(cheryl_print_options) message(STATUS "----------------------------------") endfunction() -# Include options declared by modules and standalone entry points in one summary. -cmake_language(DEFER DIRECTORY "${CMAKE_SOURCE_DIR}" CALL cheryl_print_options) - function(cheryl_check_option_requirements) get_property(declared_options GLOBAL PROPERTY CHERYL_DECLARED_OPTIONS) foreach(name IN LISTS declared_options) @@ -104,47 +106,129 @@ function(cheryl_check_option_requirements) endforeach() endfunction() +# Entry points declare only the groups they consume; all defaults live here. +function(cheryl_configure_module_options) + cheryl_option(CHERYL_BUILD_TESTS "Build module-local checks" OFF) +endfunction() + +function(cheryl_configure_native_options) + cheryl_option(CHERYL_NATIVE_INPUT "Include the Gainput-backed native input adapter" ON) + cheryl_option(CHERYL_NATIVE_NULL_PLATFORM "Use GLFW's null platform without X11 or Wayland" OFF) +endfunction() + +function(cheryl_configure_opengl_options) + set(CHERYL_NATIVE_GLFW_SOURCE "" CACHE PATH "Native GLFW module source directory when its target is not supplied") +endfunction() + +function(cheryl_configure_startup_options) + set(opengl_default "${CHERYL_BUILD_OPENGL}") + if(TARGET Cheryl::OpenGL) + set(opengl_default ON) + endif() + cheryl_option(CHERYL_STARTUP_OPENGL "Include private GLFW/OpenGL integration in Startup" "${opengl_default}") + set(CHERYL_OPENGL_SOURCE "" CACHE PATH "OpenGL module source directory when its target is not supplied") +endfunction() + +function(cheryl_configure_tgui_options) + set(CHERYL_TGUI_SOURCE "" CACHE PATH "TGUI 1.13.0 source directory; empty selects the pinned source or a supplied package") +endfunction() + +function(cheryl_configure_rmlui_options) + set(CHERYL_RMLUI_SOURCE "" CACHE PATH "RmlUi 6.3 source directory; empty selects the pinned source or a supplied package") +endfunction() + +function(cheryl_configure_rmlui_test_options) + set(CHERYL_RMLUI_TEST_FONT "" CACHE FILEPATH "RmlUi check font fixture; empty discovers an available TTF/OTF file") +endfunction() + +function(cheryl_configure_miniaudio_options) + set(CHERYL_MINIAUDIO_SOURCE "" CACHE PATH "Miniaudio 0.11.25 source directory, overriding the pinned submodule") +endfunction() + +function(cheryl_configure_debug_terminal_options) + set(CHERYL_DEBUG_TERMINAL_EMULATOR "auto" CACHE STRING "Linux native terminal emulator: auto, konsole, or xterm") + set_property(CACHE CHERYL_DEBUG_TERMINAL_EMULATOR PROPERTY STRINGS auto konsole xterm) + if(NOT CHERYL_DEBUG_TERMINAL_EMULATOR MATCHES "^(auto|konsole|xterm)$") + message(FATAL_ERROR "Unknown CHERYL_DEBUG_TERMINAL_EMULATOR: ${CHERYL_DEBUG_TERMINAL_EMULATOR}") + endif() +endfunction() + +function(cheryl_configure_logging_options) + set(CHERYL_LOG_PROFILE "auto" CACHE STRING "Logging defaults: auto, developer, support, or release") + set_property(CACHE CHERYL_LOG_PROFILE PROPERTY STRINGS auto developer support release) + set(CHERYL_LOG_COMPILED_MASK "" CACHE STRING "Optional severity-bit override (0x3f trace, 0x1f debug, 0x0f info, 0x07 warn, 0 off)") +endfunction() + +function(cheryl_configure_hwloc_options) + set(CHERYL_HWLOC_TARGET "" CACHE STRING "Supplied hwloc 2.5+ (2.x) SDK target") +endfunction() + +function(cheryl_configure_documentation_options interpreter) + set(CHERYL_DOCS_PYTHON "${interpreter}" CACHE FILEPATH "Python interpreter for documentation tool selection") + set(CHERYL_DOCS_CACHE_DIR "" CACHE PATH "Shared documentation tool cache; empty uses the user cache") + cheryl_option(CHERYL_DOCS_FORCE_PINNED "Use locked documentation tools instead of compatible installed tools" OFF) +endfunction() + +function(cheryl_configure_first_game_options) + set(CHERYL_FIRST_GAME_ENGINE_SOURCE "" CACHE PATH "Cheryl checkout for source consumption; empty uses an installed SDK") +endfunction() + +function(cheryl_configure_library_options) + cheryl_option(CHERYL_BUILD_SHARED_LIBS + "Build Cheryl Engine, modules and support as shared libraries" ON) +endfunction() + function(cheryl_configure_root_options) + cheryl_configure_library_options() cheryl_option(CHERYL_BUILD_DOCS - "Add documentation tooling and generation targets" OFF) + "Add documentation tooling and generation targets" ON) cheryl_option(CHERYL_BUILD_SDK - "Enable installation of the selected libraries, headers and dependencies" OFF) + "Enable installation of the selected libraries, headers and dependencies" ON) cheryl_option(WARN "Enable compiler warnings [-Wall]" OFF) cheryl_option(CHERYL_BUILD_WORKER_TOPOLOGY_HWLOC - "Build optional hwloc worker topology and thread placement support" OFF) + "Build optional hwloc worker topology and thread placement support" ON) cheryl_option(CHERYL_BUILD_WORKER_TOPOLOGY_JSON - "Build optional worker configuration JSON loading support" OFF) + "Build optional worker configuration JSON loading support" ON) + cheryl_option(CHERYL_BUILD_WORKERS + "Build the optional worker configuration/topology module" ON) + cheryl_option(CHERYL_BUILD_STARTUP + "Build the application bootstrap module" ON) cheryl_option(CHERYL_BUILD_NATIVE_GLFW "Build the native GLFW window/input integration" ON) + cheryl_option(CHERYL_BUILD_NATIVE_SDL3 + "Add the Native SDL3 scaffold (runtime adapter is not implemented)" OFF) set(cheryl_terminal_linux_default OFF) - if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang" AND + NOT CMAKE_CONFIGURATION_TYPES AND CMAKE_BUILD_TYPE STREQUAL "Debug") set(cheryl_terminal_linux_default ON) endif() cheryl_option(CHERYL_BUILD_DEBUG_TERMINAL_LINUX - "Select the Linux Debug terminal module for applications and test runners" ${cheryl_terminal_linux_default}) - cheryl_option(CHERYL_SANDBOX_BUILD - "Deprecated native GLFW null-platform selection without Gainput" OFF) + "Include the Linux output terminal in applications and test runners (defaults ON in supported Debug builds)" ${cheryl_terminal_linux_default}) cheryl_option(CHERYL_BUILD_OPENGL "Build the OpenGL backend" ON) + cheryl_configure_startup_options() cheryl_option(CHERYL_BUILD_UI_TGUI "Build the optional TGUI UI integration" ON) cheryl_option(CHERYL_BUILD_UI_RMLUI - "Build the optional RmlUi UI integration" OFF) + "Build the optional RmlUi UI integration" ON) cheryl_option(CHERYL_BUILD_AUDIO_MINIAUDIO - "Build the optional miniaudio playback integration" OFF) + "Build the optional miniaudio playback integration" ON) cheryl_option(CHERYL_BUILD_DEMO - "Build the windowed demonstration application" ON + "Build the windowed demonstration application" OFF REQUIRES CHERYL_BUILD_NATIVE_GLFW CHERYL_BUILD_OPENGL CHERYL_NATIVE_INPUT) cheryl_option(CHERYL_BUILD_FIRST_GAME "Build the maintained getting-started game" OFF REQUIRES CHERYL_BUILD_NATIVE_GLFW CHERYL_BUILD_OPENGL CHERYL_NATIVE_INPUT) + cheryl_option(CHERYL_BUILD_TUTORIAL_GAME + "Build the Player/Enemy/HUD and How-to teaching game" OFF + REQUIRES CHERYL_BUILD_STARTUP CHERYL_STARTUP_OPENGL CHERYL_BUILD_UI_TGUI CHERYL_NATIVE_INPUT) cheryl_option(CHERYL_BUILD_TESTS - "Build Cheryl's tests and acceptance tools" ON) + "Build Cheryl's tests and acceptance tools" OFF) cheryl_option(CHERYL_BUILD_ALL_TESTS "Include owner and cross-project aggregate test runners in the default build" OFF REQUIRES CHERYL_BUILD_TESTS) @@ -159,17 +243,3 @@ function(cheryl_configure_root_options) message(FATAL_ERROR "Cheryl OpenGL requires Native GLFW; enable CHERYL_BUILD_NATIVE_GLFW or disable CHERYL_BUILD_OPENGL") endif() endfunction() - -function(cheryl_debug_terminal_configuration result) - set(CHERYL_DEBUG_TERMINAL "AUTO" CACHE STRING "Native output terminal inclusion: AUTO (Debug only), ON, or OFF") - set_property(CACHE CHERYL_DEBUG_TERMINAL PROPERTY STRINGS AUTO ON OFF) - if(CHERYL_DEBUG_TERMINAL STREQUAL "AUTO") - set(${result} "$" PARENT_SCOPE) - elseif(CHERYL_DEBUG_TERMINAL STREQUAL "ON") - set(${result} "1" PARENT_SCOPE) - elseif(CHERYL_DEBUG_TERMINAL STREQUAL "OFF") - set(${result} "0" PARENT_SCOPE) - else() - message(FATAL_ERROR "Unknown CHERYL_DEBUG_TERMINAL: ${CHERYL_DEBUG_TERMINAL}") - endif() -endfunction() diff --git a/cmake/CherylOutputs.cmake b/cmake/CherylOutputs.cmake index 0c32fcfb..992d0e15 100644 --- a/cmake/CherylOutputs.cmake +++ b/cmake/CherylOutputs.cmake @@ -4,13 +4,15 @@ set(OUTPUT_LIB_ENGINE cheryl-engine) # Engine support -set(OUTPUT_LIB_ENGINE_S_STARTUP cheryl-engine-startup) +set(OUTPUT_LIB_ENGINE_S_STARTUP cheryl-module-startup) set(OUTPUT_LIB_ENGINE_S_DEBUG_TERMINAL cheryl-debug-terminal-startup) set(OUTPUT_LIB_ENGINE_S_WORKER_TOPOLOGY_HWLOC cheryl-worker-topology-hwloc) set(OUTPUT_LIB_ENGINE_S_WORKER_TOPOLOGY_JSON cheryl-worker-topology-json) set(OUTPUT_LIB_ENGINE_S_WORKER_TOPOLOGY_EXAMPLE cheryl-worker-topology-example) # Modules +set(OUTPUT_LIB_MODULE_STARTUP cheryl-module-startup) +set(OUTPUT_LIB_MODULE_WORKERS cheryl-module-workers) set(OUTPUT_LIB_MODULE_NATIVE_GLFW cheryl-module-native-glfw) set(OUTPUT_LIB_MODULE_DEBUG_TERMINAL_LINUX cheryl-module-debug-terminal-linux) set(OUTPUT_LIB_MODULE_OPENGL cheryl-module-opengl) diff --git a/cmake/CherylPublicHeaders.cmake b/cmake/CherylPublicHeaders.cmake new file mode 100644 index 00000000..b75a3118 --- /dev/null +++ b/cmake/CherylPublicHeaders.cmake @@ -0,0 +1,50 @@ +include_guard(GLOBAL) +include("${CMAKE_CURRENT_LIST_DIR}/CherylTargets.cmake") + +# Keep the umbrella's feature set aligned with target usage requirements in both +# source assemblies and installed SDKs. Engine itself remains dependency-neutral. +function(cheryl_register_public_component target) + cmake_parse_arguments(component "BUILD_ONLY" "FEATURE" "" ${ARGN}) + if(component_FEATURE) + target_compile_definitions(${target} INTERFACE "${component_FEATURE}=1") + endif() + if(NOT TARGET Cheryl::Cheryl) + add_library(${TARGET_LIB_CHERYL} INTERFACE) + add_library(Cheryl::Cheryl ALIAS ${TARGET_LIB_CHERYL}) + set_property(TARGET ${TARGET_LIB_CHERYL} PROPERTY CHERYL_OWNED_ASSEMBLY TRUE) + target_link_libraries(${TARGET_LIB_CHERYL} INTERFACE Cheryl::Engine) + endif() + set_property(GLOBAL APPEND PROPERTY CHERYL_AVAILABLE_COMPONENTS "${target}") + set_property(TARGET ${target} PROPERTY CHERYL_COMPONENT_BUILD_ONLY "${component_BUILD_ONLY}") +endfunction() + +function(cheryl_select_public_component target) + if(NOT TARGET "${target}") + message(FATAL_ERROR "Cannot select unavailable Cheryl component ${target}") + endif() + get_target_property(concrete "${target}" ALIASED_TARGET) + if(NOT concrete) + set(concrete "${target}") + endif() + get_target_property(assembly Cheryl::Cheryl ALIASED_TARGET) + if(NOT assembly) + set(assembly Cheryl::Cheryl) + endif() + get_target_property(owned_assembly ${assembly} CHERYL_OWNED_ASSEMBLY) + if(NOT owned_assembly) + return() # Supplied assemblies retain their original component selection. + endif() + get_property(selected GLOBAL PROPERTY CHERYL_SELECTED_COMPONENTS) + if(concrete IN_LIST selected) + return() + endif() + set_property(GLOBAL APPEND PROPERTY CHERYL_SELECTED_COMPONENTS "${concrete}") + if(NOT concrete STREQUAL "${TARGET_LIB_ENGINE}") + get_target_property(build_only ${concrete} CHERYL_COMPONENT_BUILD_ONLY) + if(build_only) + target_link_libraries(${assembly} INTERFACE "$") + else() + target_link_libraries(${assembly} INTERFACE ${concrete}) + endif() + endif() +endfunction() diff --git a/cmake/CherylRuntime.cmake b/cmake/CherylRuntime.cmake new file mode 100644 index 00000000..da16d876 --- /dev/null +++ b/cmake/CherylRuntime.cmake @@ -0,0 +1,115 @@ +include_guard(GLOBAL) + +function(cheryl_runtime_executables directory result) + get_property(targets DIRECTORY "${directory}" PROPERTY BUILDSYSTEM_TARGETS) + set(executables) + foreach(target IN LISTS targets) + get_target_property(kind "${target}" TYPE) + if(kind STREQUAL "EXECUTABLE") + list(APPEND executables "${target}") + endif() + endforeach() + get_property(children DIRECTORY "${directory}" PROPERTY SUBDIRECTORIES) + foreach(child IN LISTS children) + cheryl_runtime_executables("${child}" child_executables) + list(APPEND executables ${child_executables}) + endforeach() + set(${result} "${executables}" PARENT_SCOPE) +endfunction() + +# Called after composition regardless of SDK installation. Owned shared targets +# and project executables use this runtime directory on Windows. Package DLLs +# must be available there even when an individual library is built directly. +function(cheryl_stage_shared_runtimes) + if(NOT WIN32) + return() + endif() + get_property(scheduled GLOBAL PROPERTY CHERYL_RUNTIME_FINALIZER_SCHEDULED) + if(NOT scheduled) + set_property(GLOBAL PROPERTY CHERYL_RUNTIME_FINALIZER_SCHEDULED TRUE) + cmake_language(DEFER DIRECTORY "${CMAKE_SOURCE_DIR}" CALL cheryl_finalize_shared_runtimes) + endif() +endfunction() + +function(cheryl_finalize_shared_runtimes) + if(NOT WIN32) + return() + endif() + get_property(dependencies GLOBAL PROPERTY CHERYL_SHARED_DEPENDENCIES) + get_property(components GLOBAL PROPERTY CHERYL_AVAILABLE_COMPONENTS) + set(libraries) + foreach(entry IN LISTS dependencies) + string(REPLACE "|" ";" fields "${entry}") + list(GET fields 0 dependency) + get_target_property(system "${dependency}" CHERYL_SHARED_DEPENDENCY_SYSTEM) + get_target_property(imported "${dependency}" IMPORTED) + get_target_property(kind "${dependency}" TYPE) + if(imported AND NOT system AND NOT kind STREQUAL "INTERFACE_LIBRARY") + list(APPEND libraries "$") + endif() + endforeach() + list(REMOVE_DUPLICATES libraries) + cheryl_runtime_executables("${CMAKE_SOURCE_DIR}" executables) + set(directories) + foreach(target IN LISTS components executables) + get_target_property(kind "${target}" TYPE) + if(NOT kind STREQUAL "INTERFACE_LIBRARY") + list(APPEND directories "$") + endif() + endforeach() + list(REMOVE_DUPLICATES directories) + set(script "${CMAKE_BINARY_DIR}/runtime-staging/$/packages.cmake") + string(CONCAT contents + "include(\"${CMAKE_CURRENT_FUNCTION_LIST_DIR}/CherylRuntimeDependencies.cmake\")\n" + "set(libraries \"${libraries}\")\n" + "cheryl_runtime_dependencies(libraries \${libraries})\n" + "set(directories \"${directories}\")\n" + "foreach(directory IN LISTS directories)\n" + " file(MAKE_DIRECTORY \"\${directory}\")\n" + " file(LOCK \"\${directory}/.cheryl-runtime-stage.lock\" GUARD PROCESS TIMEOUT 120)\n" + " foreach(library IN LISTS libraries)\n" + " get_filename_component(name \"\${library}\" NAME)\n" + " set(destination \"\${directory}/\${name}\")\n" + " if(NOT library STREQUAL destination)\n" + " file(COPY_FILE \"\${library}\" \"\${destination}\" ONLY_IF_DIFFERENT)\n" + " endif()\n" + " endforeach()\n" + " file(LOCK \"\${directory}/.cheryl-runtime-stage.lock\" RELEASE)\n" + "endforeach()\n") + if(libraries) + file(GENERATE OUTPUT "${script}" CONTENT "${contents}") + add_custom_target(cheryl-runtime-stage + COMMAND "${CMAKE_COMMAND}" -P "${script}" VERBATIM) + foreach(target IN LISTS components executables) + add_dependencies("${target}" cheryl-runtime-stage) + endforeach() + endif() + foreach(target IN LISTS executables) + # Standalone compositions may put their bootstrapped Engine in a sibling + # directory. Inspect its DLL imports too: supplied shared targets can hide + # private native dependencies from CMake's target graph. + set(script "${CMAKE_BINARY_DIR}/runtime-staging/$/${target}.cmake") + string(CONCAT contents + "include(\"${CMAKE_CURRENT_FUNCTION_LIST_DIR}/CherylRuntimeDependencies.cmake\")\n" + "set(libraries \"$\")\n" + "if(libraries)\n" + " cheryl_runtime_dependencies(libraries \${libraries})\n" + "endif()\n" + "set(directory \"$\")\n" + "file(MAKE_DIRECTORY \"\${directory}\")\n" + "file(LOCK \"\${directory}/.cheryl-runtime-stage.lock\" GUARD PROCESS TIMEOUT 120)\n" + "foreach(library IN LISTS libraries)\n" + " get_filename_component(name \"\${library}\" NAME)\n" + " set(destination \"\${directory}/\${name}\")\n" + " if(NOT library STREQUAL destination)\n" + " file(COPY_FILE \"\${library}\" \"\${destination}\" ONLY_IF_DIFFERENT)\n" + " endif()\n" + "endforeach()\n") + file(GENERATE OUTPUT "${script}" CONTENT "${contents}") + string(MAKE_C_IDENTIFIER "cheryl_runtime_${target}" staging_target) + add_custom_target("${staging_target}" + COMMAND "${CMAKE_COMMAND}" -P "${script}" + DEPENDS "$" VERBATIM) + add_dependencies("${target}" "${staging_target}") + endforeach() +endfunction() diff --git a/cmake/CherylRuntimeDependencies.cmake b/cmake/CherylRuntimeDependencies.cmake new file mode 100644 index 00000000..8980bb40 --- /dev/null +++ b/cmake/CherylRuntimeDependencies.cmake @@ -0,0 +1,163 @@ +include_guard(GLOBAL) + +function(cheryl_runtime_dependency_location result name) + set(locations ${ARGN}) + list(GET locations 0 selected) + file(SHA256 "${selected}" selected_hash) + foreach(location IN LISTS locations) + file(SHA256 "${location}" location_hash) + if(NOT location_hash STREQUAL selected_hash) + message(FATAL_ERROR "Native DLL dependencies have conflicting locations: ${name}: ${locations}") + endif() + endforeach() + set(${result} "${selected}" PARENT_SCOPE) +endfunction() + +# Resolve the deployment closure while leaving operating-system interfaces and +# the compiler runtime as platform prerequisites. +function(cheryl_runtime_dependencies result) + cmake_policy(PUSH) + if(POLICY CMP0207) + cmake_policy(SET CMP0207 NEW) + endif() + set(libraries ${ARGN}) + list(REMOVE_DUPLICATES libraries) + set(directories) + foreach(library IN LISTS libraries) + get_filename_component(directory "${library}" DIRECTORY) + list(APPEND directories "${directory}") + endforeach() + list(REMOVE_DUPLICATES directories) + if(NOT libraries) + set(${result} "" PARENT_SCOPE) + cmake_policy(POP) + return() + endif() + set(system_paths) + if(WIN32) + set(system_patterns "^[Aa][Pp][Ii]-[Mm][Ss]-" "^[Ee][Xx][Tt]-[Mm][Ss]-" + "^[Ii][Cc][Uu]\\.[Dd][Ll][Ll]$" "^[Oo][Pp][Ee][Nn][Gg][Ll]32\\.[Dd][Ll][Ll]$" + "^[Vv][Cc][Rr][Uu][Nn][Tt][Ii][Mm][Ee]" "^[Mm][Ss][Vv][Cc][PpRr]" + "^[Cc][Oo][Nn][Cc][Rr][Tt]" "^[Uu][Cc][Rr][Tt][Bb][Aa][Ss][Ee]") + file(TO_CMAKE_PATH "$ENV{SystemRoot}" system_root) + if(system_root) + string(TOLOWER "${system_root}" system_pattern) + string(REGEX REPLACE "/+$" "" system_pattern "${system_pattern}") + string(REGEX REPLACE "([][+.*()^$?|\\\\])" "\\\\\\1" system_pattern "${system_pattern}") + string(REGEX MATCHALL "[a-z]" system_letters "${system_pattern}") + list(REMOVE_DUPLICATES system_letters) + foreach(letter IN LISTS system_letters) + string(TOUPPER "${letter}" upper) + string(REPLACE "${letter}" "[${letter}${upper}]" system_pattern "${system_pattern}") + endforeach() + list(APPEND system_paths "^${system_pattern}/") + endif() + else() + set(system_patterns "^(ld-linux|ld64|ld-musl)" + "^lib(c|m|mvec|dl|pthread|rt|resolv|util|anl|BrokenLocale|nss_[A-Za-z0-9_]+|stdc\\+\\+|gcc_s)\\.so(\\.|$)" + "^lib(GL|GLX|EGL|OpenGL|GLdispatch|GLESv[0-9]+|X[A-Za-z0-9_]*|xcb[^/]*|wayland[^/]*|xkbcommon[^/]*|decor[^/]*|drm[^/]*|gbm)\\.so(\\.|$)" + "^lib(asound|pulse[^/]*|jack[^/]*|udev)\\.so(\\.|$)") + endif() + file(GET_RUNTIME_DEPENDENCIES LIBRARIES ${libraries} DIRECTORIES ${directories} + RESOLVED_DEPENDENCIES_VAR resolved UNRESOLVED_DEPENDENCIES_VAR unresolved + PRE_EXCLUDE_REGEXES ${system_patterns} + # Filtering the finished list still follows private Windows imports. + POST_EXCLUDE_REGEXES ${system_paths} + CONFLICTING_DEPENDENCIES_PREFIX conflicts) + # Source builds can resolve the same DLL beside Engine and in its original + # dependency prefix. Copies are compatible only when their bytes match. + foreach(name IN LISTS conflicts_FILENAMES) + cheryl_runtime_dependency_location(location "${name}" ${conflicts_${name}}) + list(APPEND resolved "${location}") + endforeach() + if(unresolved) + message(FATAL_ERROR "Cannot resolve required native runtime dependencies: ${unresolved}") + endif() + list(APPEND libraries ${resolved}) + list(REMOVE_DUPLICATES libraries) + set(${result} "${libraries}" PARENT_SCOPE) + cmake_policy(POP) +endfunction() + +# Generated install scripts do not inherit the project's policy version. +# Functions capture policies at definition, including IN_LIST support. +cmake_policy(PUSH) +cmake_policy(SET CMP0057 NEW) +function(cheryl_sdk_install_runtimes destination) + cheryl_runtime_dependencies(libraries ${ARGN}) + if(NOT libraries) + return() + endif() + if(WIN32) + file(INSTALL DESTINATION "${destination}" TYPE FILE FILES ${libraries}) + return() + endif() + find_program(CHERYL_PATCHELF_EXECUTABLE NAMES patchelf REQUIRED) + foreach(library IN LISTS libraries) + file(INSTALL DESTINATION "${destination}" TYPE SHARED_LIBRARY FOLLOW_SYMLINK_CHAIN FILES "${library}") + endforeach() + # An imported target may name its versioned real file directly. Its SONAME + # alias then appears in neither the source symlink chain nor its own closure. + # Validate aliases before patching changes the copied files' contents. + foreach(library IN LISTS libraries) + execute_process(COMMAND "${CHERYL_PATCHELF_EXECUTABLE}" --print-soname "${library}" + RESULT_VARIABLE status OUTPUT_VARIABLE soname ERROR_VARIABLE diagnostic + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT status EQUAL 0) + message(FATAL_ERROR "Cannot read the SDK runtime SONAME for ${library}: ${diagnostic}") + endif() + if(NOT soname) + continue() + endif() + if(NOT soname MATCHES "^[A-Za-z0-9_+.-]+$" OR soname STREQUAL "." OR soname STREQUAL "..") + message(FATAL_ERROR "Unsafe SDK runtime SONAME: ${soname}") + endif() + file(REAL_PATH "${library}" original_library) + get_filename_component(name "${original_library}" NAME) + set(installed_library "$ENV{DESTDIR}${destination}/${name}") + set(alias "$ENV{DESTDIR}${destination}/${soname}") + if(EXISTS "${alias}" OR IS_SYMLINK "${alias}") + if(NOT EXISTS "${alias}") + message(FATAL_ERROR "Broken SDK runtime SONAME alias: ${soname}") + endif() + file(SHA256 "${alias}" alias_hash) + file(SHA256 "${installed_library}" library_hash) + if(NOT alias_hash STREQUAL library_hash) + message(FATAL_ERROR "Conflicting SDK runtime SONAME: ${soname}") + endif() + else() + file(CREATE_LINK "${name}" "${alias}" SYMBOLIC RESULT status) + if(NOT status STREQUAL "0") + message(FATAL_ERROR "Cannot create SDK runtime SONAME alias ${soname}: ${status}") + endif() + endif() + endforeach() + # RUNPATH applies only to direct imports. Every native library therefore + # needs its own relative search path for a relocated SDK's transitive imports. + file(GLOB installed_libraries "$ENV{DESTDIR}${destination}/*.so" "$ENV{DESTDIR}${destination}/*.so.*") + set(patched) + foreach(library IN LISTS installed_libraries) + file(REAL_PATH "${library}" real_library) + if(real_library IN_LIST patched) + continue() + endif() + list(APPEND patched "${real_library}") + execute_process(COMMAND "${CHERYL_PATCHELF_EXECUTABLE}" --set-rpath "$ORIGIN" "${real_library}" + RESULT_VARIABLE status ERROR_VARIABLE diagnostic) + if(NOT status EQUAL 0) + message(FATAL_ERROR "Cannot set the SDK runtime search path for ${library}: ${diagnostic}") + endif() + endforeach() + find_program(CHERYL_NOTICE_PYTHON NAMES python3 REQUIRED) + set(notice_arguments --destination "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/share/licenses/native") + if(CHERYL_SDK_NATIVE_PREFIX) + list(APPEND notice_arguments --native-prefix "${CHERYL_SDK_NATIVE_PREFIX}") + endif() + execute_process(COMMAND "${CHERYL_NOTICE_PYTHON}" + "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../.github/ci/native_notices.py" ${notice_arguments} ${libraries} + RESULT_VARIABLE status OUTPUT_VARIABLE output ERROR_VARIABLE diagnostic) + if(NOT status EQUAL 0) + message(FATAL_ERROR "Cannot collect SDK redistribution notices: ${output}${diagnostic}") + endif() +endfunction() +cmake_policy(POP) diff --git a/cmake/CherylSdk.cmake b/cmake/CherylSdk.cmake index e616d69c..40b1780c 100644 --- a/cmake/CherylSdk.cmake +++ b/cmake/CherylSdk.cmake @@ -1,97 +1,294 @@ include(GNUInstallDirs) include(CMakePackageConfigHelpers) +include("${CMAKE_CURRENT_LIST_DIR}/CherylSdkHeaders.cmake") + if(NOT DEFINED CHERYL_NATIVE_INPUT) set(CHERYL_NATIVE_INPUT OFF) endif() +set(sdk_linkage static) +if(CHERYL_BUILD_SHARED_LIBS) + set(sdk_linkage shared) +endif() +set(CHERYL_SDK_NATIVE_PREFIX "" CACHE PATH "Optional native dependency prefix for SDK license notices") +set(sdk_native_prefix "${CHERYL_SDK_NATIVE_PREFIX}") +if(NOT sdk_native_prefix AND VCPKG_INSTALLED_DIR AND VCPKG_TARGET_TRIPLET) + set(sdk_native_prefix "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}") +endif() -# SDK exports describe Cheryl-owned builds of the pinned dependencies. Keep -# ordinary build-tree composition and supplied dependency targets unchanged. -function(cheryl_sdk_target target exported_name) - get_target_property(aliased ${target} ALIASED_TARGET) - if(aliased) - set(target "${aliased}") - endif() - get_target_property(imported ${target} IMPORTED) - if(imported) - message(FATAL_ERROR "The SDK requires the owned dependency target ${target}") - endif() - set_target_properties(${target} PROPERTIES EXPORT_NAME "${exported_name}") - get_target_property(target_type ${target} TYPE) - if(MSVC AND NOT target_type STREQUAL "INTERFACE_LIBRARY") - set_target_properties(${target} PROPERTIES COMPILE_PDB_NAME "${target}" - COMPILE_PDB_OUTPUT_DIRECTORY "${CHERYL_BUILD_ROOT}/sdk-symbols") - install(FILES "${CHERYL_BUILD_ROOT}/sdk-symbols/${target}.pdb" - DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT CherylSDK OPTIONAL) +# Declare the complete map before rewriting installed link expressions. A target +# is exported once even when several public compatibility names refer to it. +function(cheryl_sdk_declare target name) + if(NOT TARGET "${target}") + return() endif() - get_target_property(includes ${target} INTERFACE_INCLUDE_DIRECTORIES) - set(build_includes) - if(includes) - foreach(path IN LISTS includes) - list(APPEND build_includes "$") - endforeach() + get_target_property(concrete "${target}" ALIASED_TARGET) + if(NOT concrete) + set(concrete "${target}") endif() - set_property(TARGET ${target} PROPERTY INTERFACE_INCLUDE_DIRECTORIES - "${build_includes};$") - get_target_property(system_includes ${target} INTERFACE_SYSTEM_INCLUDE_DIRECTORIES) - if(system_includes) - set(build_system_includes) - foreach(path IN LISTS system_includes) - list(APPEND build_system_includes "$") - endforeach() - set_property(TARGET ${target} PROPERTY INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${build_system_includes}") - endif() - if(exported_name MATCHES "^(Engine|Startup|NativeGLFW|OpenGL|UI::|Audio::|WorkerTopology::)") - target_include_directories(${target} INTERFACE "$") + get_property(targets GLOBAL PROPERTY CHERYL_SDK_TARGETS) + if(NOT concrete IN_LIST targets) + get_target_property(imported "${concrete}" IMPORTED) + if(imported) + message(FATAL_ERROR "SDK production requires an owned ${target}; supplied targets remain supported for source composition") + endif() + set_property(GLOBAL APPEND PROPERTY CHERYL_SDK_TARGETS "${concrete}") + set_property(GLOBAL APPEND PROPERTY CHERYL_SDK_EXPORTS "${concrete}|${name}") + set_target_properties("${concrete}" PROPERTIES EXPORT_NAME "${name}") endif() - if(exported_name STREQUAL "Engine") - target_include_directories(${target} INTERFACE "$") + set_property(GLOBAL APPEND PROPERTY CHERYL_SDK_NAMES "${target}|${name}" "${concrete}|${name}") +endfunction() + +cheryl_sdk_declare(${TARGET_LIB_ENGINE} Engine) +cheryl_sdk_declare(${TARGET_LIB_CHERYL} Cheryl) +cheryl_sdk_declare(${TARGET_LIB_ENGINE_S_LOGGING_CONFIG} LoggingConfig) +cheryl_sdk_declare(ctti DependencyCTTI) +cheryl_sdk_declare(glm::glm-header-only DependencyGLM) +if(TARGET Cheryl::Startup) + cheryl_sdk_declare(${TARGET_LIB_MODULE_STARTUP} Startup) + set_property(TARGET ${TARGET_LIB_MODULE_STARTUP} APPEND PROPERTY EXPORT_PROPERTIES CHERYL_STARTUP_OPENGL) + cheryl_sdk_declare(CLI11::CLI11 DependencyCLI11) + cheryl_sdk_declare(${TARGET_LIB_MODULE_OPENGL_S_STARTUP} OpenGL::Startup) +endif() +if(TARGET Cheryl::Workers) + cheryl_sdk_declare(${TARGET_LIB_MODULE_WORKERS} Workers) +endif() +foreach(module IN ITEMS NATIVE_GLFW OPENGL UI_TGUI UI_RMLUI AUDIO_MINIAUDIO) + if(CHERYL_BUILD_${module}) + if(module STREQUAL "NATIVE_GLFW") + set(name NativeGLFW) + set_property(TARGET ${TARGET_LIB_MODULE_NATIVE_GLFW} APPEND PROPERTY EXPORT_PROPERTIES CHERYL_NATIVE_INPUT) + elseif(module STREQUAL "OPENGL") + set(name OpenGL) + elseif(module STREQUAL "UI_TGUI") + set(name UI::TGUI) + elseif(module STREQUAL "UI_RMLUI") + set(name UI::RmlUi) + else() + set(name Audio::Miniaudio) + endif() + cheryl_sdk_declare(${TARGET_LIB_MODULE_${module}} "${name}") endif() - get_target_property(links ${target} INTERFACE_LINK_LIBRARIES) - if(links) - set(installed_links "${links}") - if(VCPKG_INSTALLED_DIR) - string(REPLACE "${VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}" - "$/dependencies" installed_links "${installed_links}") +endforeach() + +get_property(sdk_private_dependencies GLOBAL PROPERTY CHERYL_PRIVATE_DEPENDENCIES) +if(NOT CHERYL_BUILD_SHARED_LIBS) + cheryl_sdk_declare(${TARGET_LIB_ENGINE_S_SIGNAL_HANDLERS} SignalHandlers) + cheryl_sdk_declare(Backward::Interface DependencyBackward) + cheryl_sdk_declare(cheryl_icu DependencyICU) + foreach(entry IN LISTS sdk_private_dependencies) + string(REPLACE "|" ";" fields "${entry}") + list(GET fields 0 dependency) + list(GET fields 1 owner) + get_target_property(imported "${dependency}" IMPORTED) + if(imported) + message(FATAL_ERROR "A static SDK needs an owned embedded archive: ${dependency}") + else() + string(MAKE_C_IDENTIFIER "${dependency}" identifier) + cheryl_sdk_declare("${dependency}" "Private_${identifier}") endif() - if(target STREQUAL "${TARGET_LIB_ENGINE_S_WORKER_TOPOLOGY_HWLOC}") - set(installed_links "Cheryl::Engine;$") + endforeach() + # These interface targets carry the link closure of owned static archives. + foreach(target IN ITEMS gainput_requirements) + if(TARGET "${target}") + cheryl_sdk_declare("${target}" "Private_${target}") endif() - string(REPLACE "$" - "$" installed_links "${installed_links}") - set_property(TARGET ${target} PROPERTY INTERFACE_LINK_LIBRARIES - "$;$") + endforeach() +endif() + +# Backward's optional diagnostics carry native link inputs rather than imported +# targets. Record their shared implementations without changing producer links. +if(UNIX AND NOT APPLE AND TARGET Backward::Interface) + get_target_property(backward_links Backward::Interface INTERFACE_LINK_LIBRARIES) + foreach(link IN LISTS backward_links) + set(library "${link}") + if(link MATCHES "^\\$<\\$]+)>:([^>]+)>$") + set(library "${CMAKE_MATCH_2}") + endif() + if(IS_ABSOLUTE "${library}" AND library MATCHES "\\.so(\\.[0-9.]+)?$") + string(SHA256 identity "${library}") + set(dependency "cheryl_shared_${identity}") + if(NOT TARGET "${dependency}") + add_library("${dependency}" SHARED IMPORTED GLOBAL) + set_property(TARGET "${dependency}" PROPERTY IMPORTED_LOCATION "${library}") + endif() + cheryl_shared_dependency("${dependency}" OWNER "${TARGET_LIB_ENGINE}") + elseif(IS_ABSOLUTE "${library}" AND library MATCHES "(^|/)libpthread\\.a$") + set_property(GLOBAL APPEND PROPERTY CHERYL_SDK_NAMES "${library}|@NATIVE@Threads::Threads") + endif() + endforeach() +endif() + +# Shared native libraries keep the same linkage with or without SDK installation. +# Non-system libraries relocate with the SDK on both supported platforms. +get_property(sdk_shared_dependencies GLOBAL PROPERTY CHERYL_SHARED_DEPENDENCIES) +set(sdk_shared_records) +set(sdk_native_targets "") +set(sdk_external_targets) +set(sdk_runtime_present OFF) +set(sdk_native_udev OFF) +foreach(entry IN LISTS sdk_shared_dependencies) + string(REPLACE "|" ";" fields "${entry}") + list(GET fields 0 dependency) + list(GET fields 1 consumer) + get_target_property(imported "${dependency}" IMPORTED) + get_target_property(kind "${dependency}" TYPE) + get_target_property(system "${dependency}" CHERYL_SHARED_DEPENDENCY_SYSTEM) + set(deployment system) + set(artifact "") + if(NOT kind STREQUAL "INTERFACE_LIBRARY") + set(artifact "$") endif() - set(public_headers) - if(target STREQUAL "spdlog") - get_target_property(header_files ${target} PUBLIC_HEADER) - get_target_property(target_source ${target} SOURCE_DIR) - set(absolute_headers) - foreach(header IN LISTS header_files) - if(IS_ABSOLUTE "${header}") - list(APPEND absolute_headers "${header}") - else() - list(APPEND absolute_headers "${target_source}/${header}") + if(NOT system AND NOT imported) + string(MAKE_C_IDENTIFIER "${dependency}" identifier) + cheryl_sdk_declare("${dependency}" "Dependency_${identifier}") + set_property(TARGET "${dependency}" PROPERTY CHERYL_SDK_DEPENDENCY_NAME "Dependency_${identifier}") + set(deployment sdk) + set(sdk_runtime_present ON) + elseif(NOT system) + string(MAKE_C_IDENTIFIER "${dependency}" identifier) + set(name "Native_${identifier}") + set_property(TARGET "${dependency}" PROPERTY CHERYL_SDK_DEPENDENCY_NAME "${name}") + set_property(GLOBAL APPEND PROPERTY CHERYL_SDK_NAMES "${dependency}|${name}") + get_target_property(configurations "${dependency}" IMPORTED_CONFIGURATIONS) + foreach(configuration IN ITEMS "" ${configurations}) + set(suffix "") + if(configuration AND NOT configuration MATCHES "-NOTFOUND$") + string(TOUPPER "_${configuration}" suffix) endif() + foreach(property IN ITEMS IMPORTED_LOCATION IMPORTED_IMPLIB) + get_target_property(location "${dependency}" "${property}${suffix}") + if(IS_ABSOLUTE "${location}") + set_property(GLOBAL APPEND PROPERTY CHERYL_SDK_NAMES "${location}|${name}") + endif() + endforeach() endforeach() - set_property(TARGET ${target} PROPERTY PUBLIC_HEADER "${absolute_headers}") - set(public_headers PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/spdlog COMPONENT CherylSDK) + if(NOT dependency IN_LIST sdk_external_targets) + list(APPEND sdk_external_targets "${dependency}") + set(runtime_directory "${CMAKE_INSTALL_LIBDIR}") + if(WIN32) + set(runtime_directory "${CMAKE_INSTALL_BINDIR}") + endif() + string(APPEND sdk_native_targets + "if(NOT TARGET Cheryl::${name})\n add_library(Cheryl::${name} SHARED IMPORTED)\n" + " set_target_properties(Cheryl::${name} PROPERTIES\n" + " IMPORTED_LOCATION \"\${PACKAGE_PREFIX_DIR}/${runtime_directory}/$\"\n" + " INTERFACE_INCLUDE_DIRECTORIES \"\${PACKAGE_PREFIX_DIR}/${CMAKE_INSTALL_INCLUDEDIR}\")\n") + if(WIN32) + string(APPEND sdk_native_targets + " set_property(TARGET Cheryl::${name} PROPERTY IMPORTED_IMPLIB \"\${PACKAGE_PREFIX_DIR}/${CMAKE_INSTALL_LIBDIR}/$\")\n") + endif() + get_target_property(definitions "${dependency}" INTERFACE_COMPILE_DEFINITIONS) + if(definitions) + string(APPEND sdk_native_targets + " set_property(TARGET Cheryl::${name} PROPERTY INTERFACE_COMPILE_DEFINITIONS \"${definitions}\")\n") + endif() + foreach(property IN ITEMS CHERYL_TGUI_ENGINE_SERVICES CHERYL_TGUI_IMAGE_DECODER_ABI + CHERYL_RMLUI_ENGINE_SERVICES CHERYL_RMLUI_RASTERIZER_ABI CHERYL_RMLUI_PLACEHOLDER_FIX) + get_target_property(value "${dependency}" "${property}") + if(value) + string(APPEND sdk_native_targets + " set_property(TARGET Cheryl::${name} PROPERTY ${property} \"${value}\")\n") + endif() + endforeach() + string(APPEND sdk_native_targets "endif()\n") + if(WIN32) + install(FILES "$" DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT CherylSDK) + install(FILES "$" DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT CherylSDK) + else() + install(CODE "file(INSTALL DESTINATION \"\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}\" TYPE SHARED_LIBRARY FOLLOW_SYMLINK_CHAIN FILES \"$\")" + COMPONENT CherylSDK) + endif() + endif() + set(deployment sdk) + set(sdk_runtime_present ON) endif() - install(TARGETS ${target} EXPORT CherylSdkTargets ${public_headers} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT CherylSDK - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT CherylSDK - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT CherylSDK - OBJECTS DESTINATION ${CMAKE_INSTALL_LIBDIR}/objects COMPONENT CherylSDK) - set_property(GLOBAL APPEND PROPERTY CHERYL_SDK_TARGETS ${target}) -endfunction() + if(deployment STREQUAL "sdk") + set_property(GLOBAL APPEND PROPERTY CHERYL_SDK_RUNTIME_LIBRARIES "$") + endif() + set(sdk_dependency_target "${dependency}") + if(deployment STREQUAL "sdk") + get_target_property(sdk_dependency_name "${dependency}" CHERYL_SDK_DEPENDENCY_NAME) + set(sdk_dependency_target "Cheryl::${sdk_dependency_name}") + endif() + list(APPEND sdk_shared_records + "{\"dependency\":\"${dependency}\",\"consumer\":\"${consumer}\",\"consumer_artifact\":\"$\",\"artifact\":\"${artifact}\",\"deployment\":\"${deployment}\",\"sdk_target\":\"${sdk_dependency_target}\"}") +endforeach() -function(cheryl_sdk_headers directory) - install(DIRECTORY "${directory}/" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} - COMPONENT CherylSDK FILES_MATCHING - PATTERN "*.h" PATTERN "*.hpp" PATTERN "*.inl" PATTERN "*.ipp" PATTERN "*.tpp") +if(UNIX AND NOT APPLE) + if(TARGET PkgConfig::CHERYL_NATIVE_UDEV) + set(sdk_native_udev ON) + endif() +endif() +list(JOIN sdk_shared_records "," sdk_shared_runtime_dependencies) +file(GENERATE OUTPUT "${CHERYL_BUILD_ROOT}/CherylNativeTargets.cmake" CONTENT "${sdk_native_targets}") +install(FILES "${CHERYL_BUILD_ROOT}/CherylNativeTargets.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Cheryl COMPONENT CherylSDK) +function(cheryl_sdk_dependency_name target result) + set(visited ${ARGN}) + if(target IN_LIST visited) + set(${result} "" PARENT_SCOPE) + return() + endif() + list(APPEND visited "${target}") + get_target_property(concrete "${target}" ALIASED_TARGET) + if(NOT concrete) + set(concrete "${target}") + endif() + get_target_property(name "${concrete}" CHERYL_SDK_DEPENDENCY_NAME) + if(NOT name) + get_target_property(name "${concrete}" EXPORT_NAME) + endif() + if(NOT name) + get_target_property(kind "${concrete}" TYPE) + if(kind STREQUAL "INTERFACE_LIBRARY") + get_target_property(links "${concrete}" INTERFACE_LINK_LIBRARIES) + foreach(link IN LISTS links) + if(TARGET "${link}") + cheryl_sdk_dependency_name("${link}" name ${visited}) + elseif(IS_ABSOLUTE "${link}") + get_property(dependencies GLOBAL PROPERTY CHERYL_SHARED_DEPENDENCIES) + foreach(entry IN LISTS dependencies) + string(REPLACE "|" ";" fields "${entry}") + list(GET fields 0 dependency) + get_target_property(location "${dependency}" IMPORTED_LOCATION) + if(location STREQUAL link) + cheryl_sdk_dependency_name("${dependency}" name ${visited}) + break() + endif() + endforeach() + endif() + if(name) + break() + endif() + endforeach() + endif() + endif() + set(${result} "${name}" PARENT_SCOPE) endfunction() +foreach(alias IN ITEMS Cheryl::ICU spdlog::spdlog gainput::gainput TGUI::TGUI RmlUi::Core hidapi::hidapi + Freetype::Freetype harfbuzz::harfbuzz ${CHERYL_HWLOC_TARGET} + hwloc::hwloc HWLOC::hwloc unofficial::hwloc::hwloc hwloc PkgConfig::CHERYL_HWLOC) + if(TARGET "${alias}") + cheryl_sdk_dependency_name("${alias}" name) + if(name) + set_property(GLOBAL APPEND PROPERTY CHERYL_SDK_NAMES "${alias}|${name}") + endif() + endif() +endforeach() -cheryl_sdk_target(${TARGET_LIB_ENGINE} Engine) -cheryl_sdk_target(${TARGET_LIB_ENGINE_S_LOGGING_CONFIG} LoggingConfig) +set(sdk_public_native_aliases) +foreach(alias IN ITEMS TGUI::TGUI RmlUi::Core) + if(TARGET "${alias}") + cheryl_sdk_dependency_name("${alias}" name) + if(name) + list(APPEND sdk_public_native_aliases "\"${alias}|Cheryl::${name}\"") + endif() + endif() +endforeach() +list(JOIN sdk_public_native_aliases " " sdk_public_native_aliases) + +# Logging profile is fixed by the producer configuration, while its source build +# retains the generator expressions used by multi-configuration development. get_target_property(logging_definitions ${TARGET_LIB_ENGINE_S_LOGGING_CONFIG} INTERFACE_COMPILE_DEFINITIONS) if(CHERYL_LOG_PROFILE STREQUAL "auto") if(CMAKE_BUILD_TYPE STREQUAL "Debug") @@ -114,80 +311,153 @@ if(NOT CHERYL_LOG_COMPILED_MASK STREQUAL "") endif() set_property(TARGET ${TARGET_LIB_ENGINE_S_LOGGING_CONFIG} PROPERTY INTERFACE_COMPILE_DEFINITIONS "$;$") -cheryl_sdk_target(${TARGET_LIB_ENGINE_S_SIGNAL_HANDLERS} SignalHandlers) -cheryl_sdk_target(${TARGET_LIB_ENGINE_S_STARTUP} Startup) -cheryl_sdk_target(ctti DependencyCTTI) -cheryl_sdk_target(spdlog DependencySPDLog) -cheryl_sdk_target(glm-header-only DependencyGLM) -cheryl_sdk_target(backward_interface DependencyBackward) -cheryl_sdk_target(CLI11 DependencyCLI11) -cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/projects/engine/include") -cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/projects/engine/support/startup/include") -cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/extern/ctti/include") -cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/extern/spdlog/include") -cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/extern/cli11/include") -install(DIRECTORY "${CHERYL_REPOSITORY_ROOT}/extern/glm/glm" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} - COMPONENT CherylSDK FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp" PATTERN "*.inl") -install(FILES "${CHERYL_REPOSITORY_ROOT}/extern/backward-cpp/backward.hpp" - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} COMPONENT CherylSDK) -foreach(provider IN ITEMS HWLOC JSON) - if(CHERYL_BUILD_WORKER_TOPOLOGY_${provider}) - if(provider STREQUAL "HWLOC") - cheryl_sdk_target(${TARGET_LIB_ENGINE_S_WORKER_TOPOLOGY_HWLOC} WorkerTopology::Hwloc) - else() - cheryl_sdk_target(${TARGET_LIB_ENGINE_S_WORKER_TOPOLOGY_JSON} WorkerTopology::Json) - endif() - cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/projects/engine/support/worker-topology/include") +function(cheryl_sdk_install_target target exported_name) + get_target_property(kind "${target}" TYPE) + if(MSVC AND NOT kind STREQUAL "INTERFACE_LIBRARY") + set_target_properties("${target}" PROPERTIES COMPILE_PDB_NAME "${target}" + COMPILE_PDB_OUTPUT_DIRECTORY "${CHERYL_BUILD_ROOT}/sdk-symbols") + install(FILES "${CHERYL_BUILD_ROOT}/sdk-symbols/${target}.pdb" + DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT CherylSDK OPTIONAL) endif() -endforeach() -if(CHERYL_BUILD_NATIVE_GLFW) - set_property(TARGET ${TARGET_LIB_MODULE_NATIVE_GLFW} APPEND PROPERTY EXPORT_PROPERTIES CHERYL_NATIVE_INPUT) - cheryl_sdk_target(${TARGET_LIB_MODULE_NATIVE_GLFW} NativeGLFW) - cheryl_sdk_target(glfw DependencyGLFW) - cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/projects/modules/platform/native-glfw/include") - cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/extern/glfw/include") - if(CHERYL_NATIVE_INPUT) - foreach(candidate IN LISTS LINKAGE_PUBLIC_LIB_MODULE_NATIVE_GLFW_INPUT_GAINPUT_CANDIDATES) - if(TARGET ${candidate}) - cheryl_sdk_target(${candidate} DependencyGainput) - break() + foreach(property IN ITEMS INTERFACE_INCLUDE_DIRECTORIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES) + get_target_property(includes "${target}" "${property}") + set(build_includes) + foreach(path IN LISTS includes) + if(NOT path MATCHES "-NOTFOUND$") + list(APPEND build_includes "$") endif() endforeach() - cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/extern/gainput/lib/include") + set_property(TARGET "${target}" PROPERTY "${property}" "${build_includes}") + endforeach() + target_include_directories("${target}" INTERFACE "$") + if(exported_name MATCHES "^(Engine|Startup|Workers|NativeGLFW|OpenGL|UI::|Audio::)") + target_include_directories("${target}" INTERFACE "$") endif() + if(exported_name STREQUAL "Engine") + target_include_directories("${target}" INTERFACE "$") + endif() + get_target_property(links "${target}" INTERFACE_LINK_LIBRARIES) + if(links) + set(installed_links "${links}") + if(target STREQUAL "${TARGET_LIB_CHERYL}") + list(FILTER installed_links EXCLUDE REGEX "^\\$:)${source_pattern}([;,>]|$)" + "\\1${placeholder}\\2" installed_links "${installed_links}") + if(installed_links STREQUAL previous_links) + break() + endif() + endwhile() + list(APPEND replacements "${placeholder}|${replacement}") + math(EXPR replacement_index "${replacement_index} + 1") + endforeach() + foreach(entry IN LISTS replacements) + string(REPLACE "|" ";" fields "${entry}") + list(GET fields 0 placeholder) + list(GET fields 1 replacement) + string(REPLACE "${placeholder}" "${replacement}" installed_links "${installed_links}") + endforeach() + set_property(TARGET "${target}" PROPERTY INTERFACE_LINK_LIBRARIES + "$;$") + endif() + get_target_property(public_headers "${target}" PUBLIC_HEADER) + if(public_headers) + set_property(TARGET "${target}" PROPERTY PUBLIC_HEADER "") + endif() + # Upstream PUBLIC_HEADER properties belong to upstream install rules. SDK + # headers are selected explicitly below, including private static closure. + install(TARGETS "${target}" EXPORT CherylSdkTargets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT CherylSDK + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT CherylSDK + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT CherylSDK + OBJECTS DESTINATION ${CMAKE_INSTALL_LIBDIR}/objects COMPONENT CherylSDK + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/private COMPONENT CherylSDK) + if(public_headers) + set_property(TARGET "${target}" PROPERTY PUBLIC_HEADER "${public_headers}") + endif() +endfunction() + +get_property(sdk_exports GLOBAL PROPERTY CHERYL_SDK_EXPORTS) +foreach(entry IN LISTS sdk_exports) + string(REPLACE "|" ";" fields "${entry}") + list(GET fields 0 target) + list(GET fields 1 name) + cheryl_sdk_install_target("${target}" "${name}") +endforeach() + +get_property(sdk_runtime_libraries GLOBAL PROPERTY CHERYL_SDK_RUNTIME_LIBRARIES) +get_property(sdk_targets GLOBAL PROPERTY CHERYL_SDK_TARGETS) +foreach(target IN LISTS sdk_targets) + get_target_property(kind "${target}" TYPE) + if(kind STREQUAL "SHARED_LIBRARY") + list(APPEND sdk_runtime_libraries "$") + endif() +endforeach() +if(sdk_runtime_libraries) + list(REMOVE_DUPLICATES sdk_runtime_libraries) + set(sdk_runtime_directory "${CMAKE_INSTALL_LIBDIR}") + if(WIN32) + set(sdk_runtime_directory "${CMAKE_INSTALL_BINDIR}") + endif() + install(CODE "include(\"${CMAKE_CURRENT_LIST_DIR}/CherylRuntimeDependencies.cmake\")\nset(CHERYL_SDK_NATIVE_PREFIX \"${sdk_native_prefix}\")\nset(runtime_libraries \"${sdk_runtime_libraries}\")\ncheryl_sdk_install_runtimes(\"\${CMAKE_INSTALL_PREFIX}/${sdk_runtime_directory}\" \${runtime_libraries})" + COMPONENT CherylSDK) +endif() + +function(cheryl_sdk_headers directory) + install(DIRECTORY "${directory}/" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + COMPONENT CherylSDK FILES_MATCHING + PATTERN "*.h" PATTERN "*.hpp" PATTERN "*.inl" PATTERN "*.ipp" PATTERN "*.tpp") +endfunction() +cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/projects/engine/include") +cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/extern/ctti/include") +install(DIRECTORY "${CHERYL_REPOSITORY_ROOT}/extern/glm/glm" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + COMPONENT CherylSDK FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp" PATTERN "*.inl") +if(TARGET Cheryl::Startup) + cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/projects/modules/runtime/startup/include") + cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/extern/cli11/include") endif() -if(CHERYL_BUILD_OPENGL) - cheryl_sdk_target(${TARGET_LIB_MODULE_OPENGL} OpenGL) - cheryl_sdk_target(${TARGET_LIB_MODULE_OPENGL_S_STARTUP} OpenGL::Startup) - cheryl_sdk_target(${TARGET_LIB_MODULE_OPENGL_S_GL46} DependencyGLAD) - cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/projects/modules/graphics/opengl/include") - cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/projects/modules/graphics/opengl/support/startup/include") - cheryl_sdk_headers("${CHERYL_BUILD_ROOT}/gladsources/gl46/include") +if(TARGET Cheryl::Workers) + cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/projects/modules/runtime/workers/include") endif() +foreach(entry IN ITEMS "NATIVE_GLFW|platform/native-glfw" "OPENGL|graphics/opengl" + "UI_TGUI|ui/tgui" "UI_RMLUI|ui/rmlui" "AUDIO_MINIAUDIO|audio/miniaudio") + string(REPLACE "|" ";" fields "${entry}") + list(GET fields 0 feature) + list(GET fields 1 directory) + if(CHERYL_BUILD_${feature}) + cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/projects/modules/${directory}/include") + endif() +endforeach() if(CHERYL_BUILD_UI_TGUI) - cheryl_sdk_target(${TARGET_LIB_MODULE_UI_TGUI} UI::TGUI) - cheryl_sdk_target(tgui DependencyTGUI) - cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/projects/modules/ui/tgui/include") - cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/extern/tgui/include") + cheryl_sdk_native_headers(TGUI::TGUI TGUI TGUI.hpp Config.hpp) endif() if(CHERYL_BUILD_UI_RMLUI) - set_property(TARGET rmlui_core APPEND PROPERTY EXPORT_PROPERTIES CHERYL_RMLUI_PLACEHOLDER_FIX) - cheryl_sdk_target(${TARGET_LIB_MODULE_UI_RMLUI} UI::RmlUi) - cheryl_sdk_target(rmlui_core DependencyRmlUi) - cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/projects/modules/ui/rmlui/include") - cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/extern/rmlui/Include") -endif() -if(CHERYL_BUILD_AUDIO_MINIAUDIO) - cheryl_sdk_target(${TARGET_LIB_MODULE_AUDIO_MINIAUDIO} Audio::Miniaudio) - cheryl_sdk_target(${TARGET_LIB_MODULE_AUDIO_MINIAUDIO_S_SDK} DependencyMiniaudio) - cheryl_sdk_headers("${CHERYL_REPOSITORY_ROOT}/projects/modules/audio/miniaudio/include") - install(FILES "${CHERYL_REPOSITORY_ROOT}/extern/miniaudio/miniaudio.h" - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} COMPONENT CherylSDK) + cheryl_sdk_native_headers(RmlUi::Core RmlUi Core.h) endif() -install(FILES "${CHERYL_REPOSITORY_ROOT}/LICENSE" DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/cheryl - COMPONENT CherylSDK) +install(FILES "${CHERYL_REPOSITORY_ROOT}/LICENSE" DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/cheryl COMPONENT CherylSDK) install(FILES "${CHERYL_REPOSITORY_ROOT}/assets/fonts/DejaVuSansLicense.txt" DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/DejaVuSans COMPONENT CherylSDK) foreach(dependency IN ITEMS ctti spdlog backward-cpp glm cli11 glfw gainput glad tgui rmlui miniaudio stb json) @@ -196,12 +466,66 @@ foreach(dependency IN ITEMS ctti spdlog backward-cpp glm cli11 glfw gainput glad "${CHERYL_REPOSITORY_ROOT}/extern/${dependency}/copying.txt") foreach(license IN LISTS licenses) if(NOT IS_DIRECTORY "${license}") - install(FILES "${license}" DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/${dependency} - COMPONENT CherylSDK) + install(FILES "${license}" DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/${dependency} COMPONENT CherylSDK) endif() endforeach() endforeach() +# Private native implementations still require their license notices in shared SDKs. +if(sdk_native_prefix AND IS_DIRECTORY "${sdk_native_prefix}/share") + install(DIRECTORY "${sdk_native_prefix}/share/" DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/native + COMPONENT CherylSDK FILES_MATCHING PATTERN "copyright" PATTERN "LICENSE*") +endif() +set(sdk_component_versions "\"engine\": \"${VERSION_PROJECT_ENGINE}\"") +set(sdk_workers OFF) +if(TARGET Cheryl::Workers) + set(sdk_workers ON) +endif() +foreach(component IN ITEMS startup native_glfw opengl tgui rmlui miniaudio hwloc json) + set(sdk_${component} OFF) +endforeach() +foreach(pair IN ITEMS "startup|Cheryl::Startup" "native_glfw|Cheryl::NativeGLFW" "opengl|Cheryl::OpenGL" + "tgui|Cheryl::UI::TGUI" "rmlui|Cheryl::UI::RmlUi" "miniaudio|Cheryl::Audio::Miniaudio" + "hwloc|Cheryl::WorkerTopology::Hwloc" "json|Cheryl::WorkerTopology::Json") + string(REPLACE "|" ";" fields "${pair}") + list(GET fields 0 feature) + list(GET fields 1 target) + if(TARGET "${target}") + set(sdk_${feature} ON) + endif() +endforeach() +set(sdk_startup_opengl OFF) +if(sdk_startup) + get_target_property(sdk_startup_opengl Cheryl::Startup CHERYL_STARTUP_OPENGL) +endif() +set(sdk_native_input OFF) +if(sdk_native_glfw) + get_target_property(sdk_native_input Cheryl::NativeGLFW CHERYL_NATIVE_INPUT) +endif() +foreach(module IN ITEMS STARTUP WORKERS NATIVE_GLFW OPENGL UI_TGUI UI_RMLUI AUDIO_MINIAUDIO) + if(TARGET "${TARGET_LIB_MODULE_${module}}") + string(TOLOWER "${module}" component) + string(APPEND sdk_component_versions ", \"${component}\": \"${VERSION_PROJECT_MODULE_${module}}\"") + endif() +endforeach() +set(sdk_dependency_ownership "") +foreach(entry IN LISTS sdk_private_dependencies) + string(REPLACE "|" ";" fields "${entry}") + list(GET fields 0 dependency) + list(GET fields 1 owner) + if(sdk_dependency_ownership) + string(APPEND sdk_dependency_ownership ",") + endif() + string(APPEND sdk_dependency_ownership + "{\"dependency\":\"${dependency}\",\"owner\":\"${owner}\",\"archive\":\"$\",\"owner_artifact\":\"$\"}") +endforeach() +set(sdk_engine_directory "${CMAKE_INSTALL_LIBDIR}") +set(sdk_engine_import_library "") +if(CHERYL_BUILD_SHARED_LIBS AND WIN32) + set(sdk_engine_directory "${CMAKE_INSTALL_BINDIR}") + set(sdk_engine_import_library "${CMAKE_INSTALL_LIBDIR}/$") +endif() +set(sdk_engine_library "${sdk_engine_directory}/$") get_property(sdk_targets GLOBAL PROPERTY CHERYL_SDK_TARGETS) add_custom_target(cheryl-sdk) add_dependencies(cheryl-sdk ${sdk_targets}) @@ -212,17 +536,19 @@ configure_package_config_file("${CMAKE_CURRENT_LIST_DIR}/templates/CherylConfig. write_basic_package_version_file("${CHERYL_BUILD_ROOT}/CherylConfigVersion.cmake" VERSION ${VERSION_PROJECT_CHERYL} COMPATIBILITY SameMajorVersion) install(FILES "${CHERYL_BUILD_ROOT}/CherylConfig.cmake" "${CHERYL_BUILD_ROOT}/CherylConfigVersion.cmake" + "${CMAKE_CURRENT_LIST_DIR}/CherylOptions.cmake" DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Cheryl COMPONENT CherylSDK) set(sdk_msvc_runtime "") +set(sdk_windows_minimum_version "") +if(WIN32) + set(sdk_windows_minimum_version "10.0.18362") +endif() if(MSVC) set(sdk_msvc_runtime "${CMAKE_MSVC_RUNTIME_LIBRARY}") if(NOT sdk_msvc_runtime) - if(CMAKE_BUILD_TYPE STREQUAL "Debug") - set(sdk_msvc_runtime MultiThreadedDebugDLL) - else() - set(sdk_msvc_runtime MultiThreadedDLL) - endif() + set(sdk_msvc_runtime "$,MultiThreadedDebugDLL,MultiThreadedDLL>") endif() endif() configure_file("${CMAKE_CURRENT_LIST_DIR}/templates/CherylSdk.json.in" - "${CHERYL_BUILD_ROOT}/cheryl-sdk.json" @ONLY) + "${CHERYL_BUILD_ROOT}/cheryl-sdk-configured.json" @ONLY) +file(GENERATE OUTPUT "${CHERYL_BUILD_ROOT}/cheryl-sdk.json" INPUT "${CHERYL_BUILD_ROOT}/cheryl-sdk-configured.json") diff --git a/cmake/CherylSdkHeaders.cmake b/cmake/CherylSdkHeaders.cmake new file mode 100644 index 00000000..95916014 --- /dev/null +++ b/cmake/CherylSdkHeaders.cmake @@ -0,0 +1,64 @@ +include_guard(GLOBAL) + +function(cheryl_sdk_header_roots target namespace result) + set(visited ${ARGN}) + if(target IN_LIST visited) + set(${result} "" PARENT_SCOPE) + return() + endif() + list(APPEND visited "${target}") + set(roots) + foreach(property IN ITEMS INTERFACE_INCLUDE_DIRECTORIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES) + get_target_property(includes "${target}" "${property}") + foreach(directory IN LISTS includes) + while(directory MATCHES "^\\$$") + set(directory "${CMAKE_MATCH_1}") + endwhile() + if(NOT directory MATCHES "\\$<" AND IS_DIRECTORY "${directory}/${namespace}") + list(APPEND roots "${directory}") + endif() + endforeach() + endforeach() + get_target_property(links "${target}" INTERFACE_LINK_LIBRARIES) + foreach(link IN LISTS links) + if(TARGET "${link}") + cheryl_sdk_header_roots("${link}" "${namespace}" child_roots ${visited}) + list(APPEND roots ${child_roots}) + endif() + endforeach() + list(REMOVE_DUPLICATES roots) + set(${result} "${roots}" PARENT_SCOPE) +endfunction() + +function(cheryl_sdk_native_headers target namespace) + cheryl_sdk_header_roots("${target}" "${namespace}" roots) + foreach(header IN LISTS ARGN) + set(found OFF) + foreach(root IN LISTS roots) + if(EXISTS "${root}/${namespace}/${header}") + set(found ON) + break() + endif() + endforeach() + if(NOT found) + message(FATAL_ERROR "SDK packaging cannot find ${namespace}/${header} in ${target}'s public include directories; supply the headers matching this native runtime") + endif() + endforeach() + # Select each path once so generated headers in earlier public include + # directories take precedence regardless of source file timestamps. + set(selected) + foreach(root IN LISTS roots) + file(GLOB_RECURSE headers RELATIVE "${root}/${namespace}" LIST_DIRECTORIES FALSE + "${root}/${namespace}/*.h" "${root}/${namespace}/*.hpp" "${root}/${namespace}/*.inl" + "${root}/${namespace}/*.ipp" "${root}/${namespace}/*.tpp") + foreach(header IN LISTS headers) + if(header IN_LIST selected) + continue() + endif() + list(APPEND selected "${header}") + get_filename_component(directory "${header}" DIRECTORY) + install(FILES "${root}/${namespace}/${header}" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${namespace}/${directory}" COMPONENT CherylSDK) + endforeach() + endforeach() +endfunction() diff --git a/cmake/CherylTargets.cmake b/cmake/CherylTargets.cmake index 907feeed..d591e57f 100644 --- a/cmake/CherylTargets.cmake +++ b/cmake/CherylTargets.cmake @@ -1,18 +1,21 @@ # Reload declarations into the caller's scope, including standalone entry points. # Engine +set(TARGET_LIB_CHERYL cheryl_assembly) set(TARGET_LIB_ENGINE cengine) # Engine support set(TARGET_LIB_ENGINE_S_LOGGING_CONFIG cengine_logging_config) set(TARGET_LIB_ENGINE_S_SIGNAL_HANDLERS cengine_signal_handlers) -set(TARGET_LIB_ENGINE_S_STARTUP cengine_startup) +set(TARGET_LIB_ENGINE_S_STARTUP module_startup) set(TARGET_LIB_ENGINE_S_DEBUG_TERMINAL cengine_debug_terminal_startup) -set(TARGET_LIB_ENGINE_S_WORKER_TOPOLOGY_HWLOC cengine_worker_topology_hwloc) -set(TARGET_LIB_ENGINE_S_WORKER_TOPOLOGY_JSON cengine_worker_topology_json) +set(TARGET_LIB_ENGINE_S_WORKER_TOPOLOGY_HWLOC module_workers) +set(TARGET_LIB_ENGINE_S_WORKER_TOPOLOGY_JSON module_workers) set(TARGET_LIB_ENGINE_S_WORKER_TOPOLOGY_EXAMPLE example_worker_topology) # Modules +set(TARGET_LIB_MODULE_STARTUP module_startup) +set(TARGET_LIB_MODULE_WORKERS module_workers) set(TARGET_LIB_MODULE_NATIVE_GLFW module_native_glfw) set(TARGET_LIB_MODULE_DEBUG_TERMINAL_LINUX module_debug_terminal_linux) set(TARGET_LIB_MODULE_OPENGL module_opengl) @@ -78,6 +81,7 @@ set(TARGET_TEST_CONSUMER_MODULE_UI_RMLUI consumer-module-ui-rmlui) set(TARGET_TEST_CONSUMER_MODULE_AUDIO_MINIAUDIO consumer-module-audio-miniaudio) # Header probes +set(TARGET_TEST_CONSUMER_CHERYL_HEADERS consumer-headers-cheryl) set(TARGET_TEST_CONSUMER_ENGINE_HEADERS consumer-headers-cengine) set(TARGET_TEST_CONSUMER_MODULE_NATIVE_GLFW_HEADERS consumer-module-headers-native-glfw) set(TARGET_TEST_CONSUMER_MODULE_OPENGL_HEADERS consumer-module-headers--opengl) diff --git a/cmake/CherylVersions.cmake b/cmake/CherylVersions.cmake index a95d27e8..fde2a59b 100644 --- a/cmake/CherylVersions.cmake +++ b/cmake/CherylVersions.cmake @@ -7,11 +7,13 @@ set(VERSION_PROJECT_CHERYL 4.0.0) set(VERSION_PROJECT_ENGINE 4.0.0) # Modules -set(VERSION_PROJECT_MODULE_NATIVE_GLFW 1.0.0) +set(VERSION_PROJECT_MODULE_STARTUP ${VERSION_PROJECT_ENGINE}) +set(VERSION_PROJECT_MODULE_WORKERS ${VERSION_PROJECT_ENGINE}) +set(VERSION_PROJECT_MODULE_NATIVE_GLFW 2.0.0) set(VERSION_PROJECT_MODULE_DEBUG_TERMINAL_LINUX 1.0.0) -set(VERSION_PROJECT_MODULE_OPENGL 1.0.0) -set(VERSION_PROJECT_MODULE_UI_TGUI 1.0.0) -set(VERSION_PROJECT_MODULE_UI_RMLUI 1.0.0) +set(VERSION_PROJECT_MODULE_OPENGL 2.0.0) +set(VERSION_PROJECT_MODULE_UI_TGUI 2.0.0) +set(VERSION_PROJECT_MODULE_UI_RMLUI 2.0.0) set(VERSION_PROJECT_MODULE_AUDIO_MINIAUDIO 1.0.0) # Applications diff --git a/cmake/templates/CherylConfig.cmake.in b/cmake/templates/CherylConfig.cmake.in index 94d18f7b..28173a6a 100644 --- a/cmake/templates/CherylConfig.cmake.in +++ b/cmake/templates/CherylConfig.cmake.in @@ -1,67 +1,50 @@ @PACKAGE_INIT@ include(CMakeFindDependencyMacro) +include("${CMAKE_CURRENT_LIST_DIR}/CherylNativeTargets.cmake") -# Windows native dependencies are packaged in their own relocatable prefix. -set(cheryl_sdk_prefix_path "${CMAKE_PREFIX_PATH}") -set(cheryl_sdk_root "${PACKAGE_PREFIX_DIR}") -if(EXISTS "${cheryl_sdk_root}/dependencies") - list(PREPEND CMAKE_PREFIX_PATH "${cheryl_sdk_root}/dependencies") - set(Cheryl_RUNTIME_DIRECTORIES "${cheryl_sdk_root}/dependencies/bin") +set(Cheryl_RUNTIME_DIRECTORIES) +if("@sdk_linkage@" STREQUAL "shared" OR @sdk_runtime_present@) + if(WIN32) + list(APPEND Cheryl_RUNTIME_DIRECTORIES "${PACKAGE_PREFIX_DIR}/@CMAKE_INSTALL_BINDIR@") + else() + list(APPEND Cheryl_RUNTIME_DIRECTORIES "${PACKAGE_PREFIX_DIR}/@CMAKE_INSTALL_LIBDIR@") + endif() endif() + find_dependency(Threads) -# HarfBuzz may import the FreeType config, so prefer its complete export set. -find_package(Freetype CONFIG QUIET) -if(NOT TARGET Freetype::Freetype) - find_dependency(Freetype) -endif() -find_package(harfbuzz CONFIG QUIET) -if(NOT TARGET harfbuzz::harfbuzz) - find_dependency(PkgConfig) - pkg_check_modules(CHERYL_HARFBUZZ REQUIRED IMPORTED_TARGET harfbuzz) - add_library(harfbuzz::harfbuzz ALIAS PkgConfig::CHERYL_HARFBUZZ) -elseif(NOT TARGET PkgConfig::CHERYL_HARFBUZZ) - add_library(PkgConfig::CHERYL_HARFBUZZ INTERFACE IMPORTED) - set_target_properties(PkgConfig::CHERYL_HARFBUZZ PROPERTIES INTERFACE_LINK_LIBRARIES harfbuzz::harfbuzz) -endif() -find_dependency(ICU COMPONENTS uc i18n) -string(REGEX MATCH "^[0-9]+" cheryl_sdk_icu_major "@ICU_VERSION@") -string(REGEX MATCH "^[0-9]+" cheryl_host_icu_major "${ICU_VERSION}") -if(NOT cheryl_sdk_icu_major STREQUAL cheryl_host_icu_major) - set(Cheryl_FOUND FALSE) - set(Cheryl_NOT_FOUND_MESSAGE "This SDK requires ICU ${cheryl_sdk_icu_major}.x; found ${ICU_VERSION}") - return() -endif() -if(@CHERYL_BUILD_OPENGL@) - find_dependency(OpenGL) -endif() -if(@CHERYL_BUILD_NATIVE_GLFW@ AND UNIX AND NOT APPLE) - find_dependency(X11) -endif() -if(@CHERYL_BUILD_WORKER_TOPOLOGY_HWLOC@ AND NOT TARGET CherylSdk::Hwloc) - find_path(cheryl_sdk_hwloc_include hwloc.h REQUIRED) - find_library(cheryl_sdk_hwloc_library NAMES hwloc libhwloc REQUIRED) - file(STRINGS "${cheryl_sdk_hwloc_include}/hwloc.h" cheryl_sdk_hwloc_api_line - REGEX "^#define[ \t]+HWLOC_API_VERSION[ \t]+") - string(REGEX MATCH "0x[0-9a-fA-F]+" cheryl_sdk_hwloc_api "${cheryl_sdk_hwloc_api_line}") - if(NOT cheryl_sdk_hwloc_api) - set(Cheryl_FOUND FALSE) - set(Cheryl_NOT_FOUND_MESSAGE "Cannot identify the installed hwloc API") - return() +if("@sdk_linkage@" STREQUAL "static") + # Private third-party shared-library imports are installed with the SDK. + # Only platform graphics/input APIs remain consumer-side discoveries. + if(NOT WIN32) + if("@sdk_native_udev@") + find_dependency(PkgConfig) + pkg_check_modules(CHERYL_NATIVE_UDEV REQUIRED IMPORTED_TARGET libudev) + endif() endif() - math(EXPR cheryl_sdk_hwloc_api "${cheryl_sdk_hwloc_api}") - if(cheryl_sdk_hwloc_api LESS 0x00020500 OR cheryl_sdk_hwloc_api GREATER_EQUAL 0x00030000) - set(Cheryl_FOUND FALSE) - set(Cheryl_NOT_FOUND_MESSAGE "This SDK requires the hwloc 2.5+ (2.x) API") - return() + if(@sdk_opengl@) + find_dependency(OpenGL) + endif() + if(@sdk_native_glfw@ AND UNIX AND NOT APPLE) + find_dependency(X11) endif() - add_library(CherylSdk::Hwloc UNKNOWN IMPORTED) - set_target_properties(CherylSdk::Hwloc PROPERTIES - IMPORTED_LOCATION "${cheryl_sdk_hwloc_library}" - INTERFACE_INCLUDE_DIRECTORIES "${cheryl_sdk_hwloc_include}") -endif() -if(WIN32 AND @CHERYL_BUILD_NATIVE_GLFW@ AND @CHERYL_NATIVE_INPUT@) - find_dependency(hidapi CONFIG) endif() -set(CMAKE_PREFIX_PATH "${cheryl_sdk_prefix_path}") include("${CMAKE_CURRENT_LIST_DIR}/CherylTargets.cmake") +foreach(pair IN ITEMS @sdk_public_native_aliases@) + string(REPLACE "|" ";" fields "${pair}") + list(GET fields 0 alias) + list(GET fields 1 implementation) + if(TARGET "${implementation}" AND NOT TARGET "${alias}") + add_library("${alias}" ALIAS "${implementation}") + endif() +endforeach() +if(TARGET Cheryl::Workers) + if(@sdk_hwloc@ AND NOT TARGET Cheryl::WorkerTopology::Hwloc) + add_library(Cheryl::WorkerTopology::Hwloc INTERFACE IMPORTED) + set_target_properties(Cheryl::WorkerTopology::Hwloc PROPERTIES INTERFACE_LINK_LIBRARIES Cheryl::Workers) + endif() + if(@sdk_json@ AND NOT TARGET Cheryl::WorkerTopology::Json) + add_library(Cheryl::WorkerTopology::Json INTERFACE IMPORTED) + set_target_properties(Cheryl::WorkerTopology::Json PROPERTIES INTERFACE_LINK_LIBRARIES Cheryl::Workers) + endif() +endif() check_required_components(Cheryl) diff --git a/cmake/templates/CherylSdk.json.in b/cmake/templates/CherylSdk.json.in index 9cf903b5..57b2e29e 100644 --- a/cmake/templates/CherylSdk.json.in +++ b/cmake/templates/CherylSdk.json.in @@ -1,5 +1,9 @@ { "version": "@VERSION_PROJECT_CHERYL@", + "component_versions": {@sdk_component_versions@}, + "linkage": "@sdk_linkage@", + "engine_library": "@sdk_engine_library@", + "engine_import_library": "@sdk_engine_import_library@", "system": "@CMAKE_SYSTEM_NAME@", "system_version": "@CMAKE_SYSTEM_VERSION@", "architecture": "@CMAKE_SYSTEM_PROCESSOR@", @@ -9,14 +13,21 @@ "msvc_runtime": "@sdk_msvc_runtime@", "logging_profile": "@sdk_log_profile@", "native_versions": {"icu": "@ICU_VERSION@", "freetype": "@FREETYPE_VERSION_STRING@"}, + "dependency_ownership": [@sdk_dependency_ownership@], + "shared_runtime_dependencies": [@sdk_shared_runtime_dependencies@], + "windows_minimum_version": "@sdk_windows_minimum_version@", + "public_template_dependencies": ["CTTI", "GLM", "CLI11 (when Startup is selected)", "selected UI toolkit headers"], "features": { - "native_glfw": "@CHERYL_BUILD_NATIVE_GLFW@", - "native_input": "@CHERYL_NATIVE_INPUT@", - "opengl": "@CHERYL_BUILD_OPENGL@", - "tgui": "@CHERYL_BUILD_UI_TGUI@", - "rmlui": "@CHERYL_BUILD_UI_RMLUI@", - "miniaudio": "@CHERYL_BUILD_AUDIO_MINIAUDIO@", - "hwloc": "@CHERYL_BUILD_WORKER_TOPOLOGY_HWLOC@", - "json": "@CHERYL_BUILD_WORKER_TOPOLOGY_JSON@" + "startup": "@sdk_startup@", + "startup_opengl": "@sdk_startup_opengl@", + "workers": "@sdk_workers@", + "native_glfw": "@sdk_native_glfw@", + "native_input": "@sdk_native_input@", + "opengl": "@sdk_opengl@", + "tgui": "@sdk_tgui@", + "rmlui": "@sdk_rmlui@", + "miniaudio": "@sdk_miniaudio@", + "hwloc": "@sdk_hwloc@", + "json": "@sdk_json@" } } diff --git a/cmake/templates/Module.cmake.in b/cmake/templates/Module.cmake.in index dff44030..48d99470 100644 --- a/cmake/templates/Module.cmake.in +++ b/cmake/templates/Module.cmake.in @@ -3,13 +3,14 @@ include("${CHERYL_REPOSITORY_ROOT}/cmake/CherylModules.cmake") if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) project(@MODULE_NAME@ VERSION ${VERSION_PROJECT_MODULE_@MODULE_ID@} LANGUAGES CXX) endif() -cheryl_option(CHERYL_BUILD_TESTS "Build module-local checks" OFF) +cheryl_configure_module_options() cheryl_prepare_module() file(GLOB_RECURSE module_sources CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") add_library(${TARGET_LIB_MODULE_@MODULE_ID@} STATIC ${module_sources}) add_library(Cheryl::@MODULE_ALIAS@ ALIAS ${TARGET_LIB_MODULE_@MODULE_ID@}) +cheryl_register_public_component(${TARGET_LIB_MODULE_@MODULE_ID@} FEATURE CHERYL_HAS_@MODULE_ID@) set_target_properties(${TARGET_LIB_MODULE_@MODULE_ID@} PROPERTIES VERSION "${VERSION_PROJECT_MODULE_@MODULE_ID@}" OUTPUT_NAME "${OUTPUT_LIB_MODULE_@MODULE_ID@}" diff --git a/docs/AGENTS.md b/docs/AGENTS.md new file mode 100644 index 00000000..5fde1111 --- /dev/null +++ b/docs/AGENTS.md @@ -0,0 +1,21 @@ +# Documentation work + +The root [AGENTS.md](../AGENTS.md) indexes the repository instruction sets. +Read the [shared workflow](for-agents/workflow.md) and the guides for the current +mode. Those rules also apply when documentation is written in source headers. + +For authorized documentation auditing or maintenance: + +1. Follow [Documentation workflow](for-agents/documentation.md) for ownership, + evidence, writing, usefulness review and maintenance policy. +2. Also read [API documentation](for-agents/api-documentation.md) for source + comments and [Documentation generation](for-agents/documentation-building.md) + when generating or inspecting the site. +3. Read the affected subject's canonical guide and source owner; follow links + that answer a question needed for this task. + +The [documentation index](README.md) maps published and repository-only material. +The [published hierarchy](site/index.md) owns site navigation. The +[tooling guide](site/development/documentation.md) owns generation commands and +output/cache behavior. For changes to the instruction sets themselves, also use +[Agent instruction library](for-agents/README.md#maintain-this-library). diff --git a/docs/README.md b/docs/README.md index 0bda30e3..728fb1eb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,87 +1,90 @@ # Cheryl Engine documentation -Choose an entry point for the work you are doing: +Choose the entry point for your work: | Task | Start here | | --- | --- | -| Check implemented, partial, planned or deferred capabilities | [Status inventory and review baseline](STATUS.md) | -| Understand the engine | [Runtime architecture](runtime/runtime-architecture.md) and [module roles](../projects/modules/README.md) | -| Write a game | [First-game walkthrough](getting-started/first-game.md), then [application composition and game hooks](development/consuming-engine.md) | -| Browse the generated API or build the manual | [API entry points](api/index.md) and [documentation tooling and publishing](development/documentation.md) | -| Configure, build or troubleshoot | [Setup](../README.md#setup) and [build reference](development/building.md) | -| Run checks | [Test selection](development/testing.md) and [pending checks and QA](testing-requests.md) | -| Extend the repository | [Module authoring](development/modules.md), [UI adapters](development/ui-adapters.md) and [contributing](development/contributing.md) | -| Pick up planned work | [Planning catalogue](planning/README.md) | - -The system references below describe API contracts, ownership, units and failure -behavior. Read the relevant subject rather than the whole documentation tree. - -## Runtime - -- [Runtime architecture and backend boundaries](runtime/runtime-architecture.md) -- [Frame ownership and runtime lifecycle](runtime/runtime-frame-boundary.md) -- [Display and window ownership](runtime/display-and-window-contract.md) -- [Native callbacks and failure reporting](runtime/failure-reporting.md) -- [Input actions, state, events, text, and focus](runtime/input-state-model.md) -- [Audio ownership and playback](runtime/audio.md) -- [Simulation timing and recovery](runtime/simulation-timing.md) -- [State, timing and math utilities](runtime/utility-contracts.md) -- [Thread dispatch](runtime/thread-dispatch.md) -- [Event buses and persistent registration](runtime/event-delivery.md) -- [Worker pools, topology providers and C++/JSON settings](runtime/worker-execution.md) -- [Logging configuration and emission](runtime/logging.md) -- [Subsystem diagnostics](runtime/subsystem-diagnostics.md) -- [Linux Debug output terminal and process lifetime](../projects/modules/platform/debug-terminal-linux/README.md) - -## Assets - -- [Asset package downloads and image placement](assets/catalog.md) -- [Asset loading](assets/asset-loading.md) -- [Asset values, playback and CPU submission](assets/asset-values-and-playback.md) -- [Text encoding and source indices](assets/text-encoding.md) -- [Unicode text layout and font resources](assets/text-layout.md) -- [File indexing and font discovery](assets/file-and-font-discovery.md) -- [Asset manifest format](assets/asset-manifests.md) - -## Rendering - -- [Pipelines, materials, and render submission](rendering/pipelines-and-materials.md) - -## Resources - -- [Cache ownership, immutable publication and native retirement](resources/resource-residency.md) -- [CPU memory lifetime and reservation](resources/resource-lifetime.md) -- [Legacy bitmap font inputs and migration](resources/legacy-ffont.md) - -## Development - -- [First-game walkthrough](getting-started/first-game.md) -- [Generated C++ API entry points](api/index.md) -- [Documentation generation and versioned Read the Docs publishing](development/documentation.md) -- [Pending automated and QA testing requests](testing-requests.md) -- [Build reference, CI and SDK production](development/building.md) -- [Test selection](development/testing.md) -- [Contributing](development/contributing.md) -- [C++ code style](development/code-style.md) -- [Architecture validation procedures](development/architecture-validation.md) -- [Logging acceptance](development/logging-acceptance.md) -- [Source/SDK consumption and command-line startup](development/consuming-engine.md) -- [Engine and integration modules](development/modules.md) -- [Module index and owner guides](../projects/modules/README.md) -- [Linux Debug terminal module](../projects/modules/platform/debug-terminal-linux/README.md) -- [TGUI adapter and session](../projects/modules/ui/tgui/README.md) -- [RmlUi adapter and native documents](../projects/modules/ui/rmlui/README.md) -- [RmlUi placeholder investigation and contribution guidance](external-work/rmlui-placeholder-issue.md) -- [Writing a UI adapter](development/ui-adapters.md) -- [Neutral UI consumer checks](development/architecture-validation.md#neutral-ui-consumer) -- [Desktop smoke checks](development/native-desktop-checks.md) - -## Planning - -- [Planning catalogue](planning/README.md): horizons, owning plans and prerequisites. -- [Development roadmap](planning/develop-review-and-development-plan.md): active sequencing. - -Keep new documents in the matching topic folder and add them to this index. Use -lowercase, hyphenated filenames, with `README.md` reserved for documentation -indexes. Use relative links between documents and to source files so navigation -works from each document's location. +| Look up a declaration | [C++ API](site/api/index.md) | +| Set up a game project | [Project setup](site/getting-started/project-setup.md) | +| Follow a complete example | [Tutorials](site/getting-started/create-a/index.md): First Game, actor/player/AI controllers, Character, and shared UIController/HUD | +| Find an operation | [Task index](site/getting-started/how-to/index.md) | +| Browse a subject | [Guides by topic](site/guides/index.md) | +| Understand system responsibilities and public types | [Engine Systems](site/engine-systems/index.md) | +| Change or extend Cheryl | [Contributing](site/development/contributing.md) and [Module authoring](site/development/modules.md) | +| Check capabilities or planned work | [Status inventory](STATUS.md) and [planning catalogue](planning/README.md) | + +## Source layout + +| Location | Owner and audience | +| --- | --- | +| `site/` | Canonical sources for the published manual and module references. | +| `site/generation/` | Generator, configuration, theme assets and tooling checks. Local HTML is written to `site/generation/generated/html/`. | +| `for-agents/` | Focused agent work instructions, indexed by root `AGENTS.md`: Git, investigation, design, implementation, testing and documentation. | +| `planning/`, `STATUS.md`, `testing-requests.md` | Repository-only plans, investigation follow-up, status and pending acceptance. | + +The generator reads `site/`. Keep published prose there. Module `README.md` +files beside the code are relative symlinks to their canonical module pages; +edit and preview those pages through `site/modules/`. Repository-only pages use +ordinary relative links to the site sources. The site converts links to +repository-only documents into versioned source links. + +## Game Development + +[Project setup](site/getting-started/project-setup.md) provides the first working +source/SDK configuration. [Choose modules](site/modules/index.md) maps capabilities to +implementations; [Source and SDK integration](site/development/consuming-engine.md) +owns detailed linking, context assembly and deployment. + +The [tutorial sequence](site/getting-started/create-a/index.md) starts with First Game, +then covers actor commands, Player/AI controllers, a Character, and a shared TGUI +UIController/HUD. The optional final discussion considers abstract interfaces. The +[maintained tutorial application](../projects/apps/tutorial-game/README.md) +implements the class-based lessons. + +The [Task index](site/getting-started/how-to/index.md) links to instructions directly. +Browse the same procedures and references by subject through: + +- [Assets and animation](site/assets/index.md) +- [Rendering and resources](site/rendering/index.md) +- [Input](site/input/index.md) +- [Text and fonts](site/text/index.md) +- [GUI](site/gui/index.md) +- [Audio](site/audio/index.md) +- [Runtime and concurrency](site/runtime/index.md), including ownership, cleanup and work delivery +- [Memory, state and utilities](site/utilities/index.md) +- [Logging and diagnostics](site/diagnostics/index.md) + +[Engine Systems](site/engine-systems/index.md) completes the category with a map of +responsibilities, public types, ownership and relationships. Concrete module +references belong to the topic they implement. Their canonical prose lives in +`site/modules/`; the +[repository module catalogue](../projects/modules/README.md) maps their source owners. + +## Engine Development + +- [Contributing](site/development/contributing.md) and [C++ code style](site/development/code-style.md) +- [Build the engine and SDKs](site/development/building.md) +- [Module authoring](site/development/modules.md), [Integration contracts](site/architecture/interfaces-adapters.md) and [UI adapter authoring](site/development/ui-adapters.md) +- [Testing and validation](site/development/testing.md), [Component and SDK checks](site/development/architecture-validation.md), [Logging checks](site/development/logging-acceptance.md) and [Desktop and controller checks](site/development/native-desktop-checks.md) +- [Documentation tooling](site/development/documentation.md) and [Publishing](site/development/publishing-documentation.md) +- [CI and SDK artifacts](site/development/ci.md) + +[Pending checks and QA](testing-requests.md) records outstanding acceptance. +The [planning catalogue](planning/README.md) links active work and its prerequisites. + +## Documentation organization + +[Agent instructions](../AGENTS.md#instruction-index) route work through the +focused [instruction library](for-agents/README.md), including page outlines, +writing and precision checks, API comments and documentation generation. These +instructions are repository-only. + +[The published navigation](site/index.md) and each parent page's toctree own the page +hierarchy. Give a page one navigation parent; use ordinary links for other routes +to its instructions. Keep task groups in the Task index's local contents and +implementation-specific contracts in their owning references. + +Published paths can remain stable when a page's navigation home changes. Preserve +useful old anchors when consolidating content. Use relative links to documents +and source files. Keep published pages in the relevant topic folder under `site/`, with +lowercase, hyphenated filenames and `README.md` reserved for repository indexes. diff --git a/docs/STATUS.md b/docs/STATUS.md index 65044e03..cc618118 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -14,7 +14,7 @@ new build/test/native results. Independent Engine-only graph inspection and the Linux terminal configuration matrix are accepted. Hosted Linux/Windows builds and relocated SDK consumers are -accepted within the [CI coverage limits](development/building.md#github-ci-and-sdk-artifacts). +accepted within the [CI coverage limits](site/development/ci.md). The first main-push SDK collection remains [TR20](testing-requests.md#tr20-automated-main-push-sdk-collection); terminal desktop and demo material/artwork observations remain pending. These acceptance updates do @@ -25,32 +25,49 @@ reorder-safe sorting, color emoji, an alternative audio SDK and the in-game cons Deferred work includes absent capabilities and Windows worker affinity in phase 4; this scheduling update does not change the implementation review baseline above. +**Planning consolidation:** 2026-10-18 — This inventory owns selected work and +deferred review order. The [planning catalogue](planning/README.md) routes to +subject prerequisites and unfinished checklists. This consolidation does not +extend implementation or executable acceptance. + +**Event integration reconciliation:** 2026-10-19 — Through source `982f74b`, +the selected non-font integration includes committed views, optional UI sizing/time, +runtime lifecycle and negotiable close, owner service phases, gamepad/upload +observations, asset generations/native provenance and playback outcomes. The +[current event contracts](site/runtime/event-delivery.md#engine-and-module-notifications) +own behavior. Focused Linux controlled/native checks and offline audio outcomes +are accepted within their selected scope; physical devices and other platforms +retain their separate limits. Font catalogue and capability-dependent follow-ups +remain [deferred](planning/short-term/engine-events.md#deferred-capability-boundaries). + **Classification:** **Partial** = implemented functionality with a selected integration gap; **Planned** = selected work awaiting investigation, design or implementation; **Deferred** = deliberately postponed, unselected, or blocked work, including capabilities with no complete implementation; **Implemented** = substantive source implementation exists, but does not imply a verified build or universal platform coverage. -The following **four classification sections** comprise 146 distinct entries. Links are relative to `docs/STATUS.md`, so they follow the branch in which the document is viewed. +The following **four classification sections** comprise 147 distinct entries. Links are relative to `docs/STATUS.md`, so they follow the branch in which the document is viewed. ## 1. Partial -No current entries in this classification. +| Feature / integration | Status | Description / scope | Investigate | +| --- | --- | --- | --- | *0 entries.* ## 2. Planned -The [roadmap](planning/develop-review-and-development-plan.md#selected-planned-work) -owns these selected tasks. Their prerequisites and design checkpoints remain; -selection does not authorize implementation or establish executable acceptance. +These tasks are selected for investigation and design. Each feature needs settled +scope and acceptance before implementation. Selection does not promote deferred +general batching or other integrations, authorize implementation or establish +executable acceptance. Subject plans own the prerequisites and design checkpoints. | Feature / integration | Status | Description / scope | Investigate | | --- | --- | --- | --- | -| GoogleTest submodule review | Planned | Review upstream history since the pinned revision, assess test/CMake/toolchain compatibility and decide whether to update the submodule; validate affected test owners if an update is selected. | [dependency reviews](planning/develop-review-and-development-plan.md#dependency-reviews) | -| nlohmann/json submodule review | Planned | Review upstream history since the pinned revision and assess parsing, serialization, configuration and package compatibility before deciding whether to update the JSON submodule. | [dependency reviews](planning/develop-review-and-development-plan.md#dependency-reviews) | -| spdlog submodule review | Planned | Review upstream history since the pinned revision and assess logging/sink behavior, formatting dependencies, exported targets and toolchain compatibility before deciding whether to update the submodule. | [dependency reviews](planning/develop-review-and-development-plan.md#dependency-reviews) | -| RmlUi rendering effects | Planned | Core document/font/input/clipping and retained-upload integration exists. Plan masks, transforms, layers, filters, custom shaders and repeating texture coordinates, including any neutral rendering-contract and graphics-backend changes needed to support them. | [UI extension plan](planning/long-term/README.md#ui-adapters) | +| GoogleTest submodule review | Planned | Review upstream history since the pinned revision, assess test/CMake/toolchain compatibility and decide whether to update the submodule; validate affected test owners if an update is selected. | [dependency reviews](planning/README.md#dependency-reviews) | +| nlohmann/json submodule review | Planned | Review upstream history since the pinned revision and assess parsing, serialization, configuration and package compatibility before deciding whether to update the JSON submodule. | [dependency reviews](planning/README.md#dependency-reviews) | +| spdlog submodule review | Planned | Review upstream history since the pinned revision and assess logging/sink behavior, formatting dependencies, exported targets and toolchain compatibility before deciding whether to update the submodule. | [dependency reviews](planning/README.md#dependency-reviews) | +| RmlUi rendering effects | Planned | Core document/font/input/clipping and retained-upload integration exists. Plan masks, transforms, layers, filters, custom shaders and repeating texture coordinates, including any neutral rendering-contract and graphics-backend changes needed to support them. | [plan](planning/long-term/README.md#ui-adapters) | | Reorder-safe render sorting | Planned | Sorting is not performed; identify explicitly reorderable regions and preserve required authored draw order, clipping and retained generations before implementing a measured policy. | [sorting scope](planning/mid-term/README.md#reorder-safe-render-sorting) | | Color emoji glyphs | Planned | Specify accepted color-font formats, RGBA page handling, material semantics and multi-codepoint fallback beyond the accepted initial grayscale scope. | [emoji scope](planning/mid-term/README.md#color-emoji) | -| Alternative audio SDK (e.g., FMOD) | Planned · SDK selection pending | Select and assess an alternative SDK/module against the existing audio contracts, including licensing, deployment, coexistence and any additional authoring requirements. | [audio scope](planning/long-term/README.md#other-engine-extensions) | -| In-game developer console | Planned | Define and implement an engine-owned graphical command/output panel with history, scrolling and input routing, independently of the optional native terminal. | [console scope](planning/long-term/README.md#other-engine-extensions) | +| Alternative audio SDK (e.g., FMOD) | Planned · SDK selection pending | Select and assess an alternative SDK/module against the existing audio contracts, including licensing, deployment, coexistence and any additional authoring requirements. | [plan](planning/long-term/README.md#alternative-audio-sdk) | +| In-game developer console | Planned | Define and implement an engine-owned graphical command/output panel with history, scrolling and input routing, independently of the optional native terminal. | [plan](planning/long-term/README.md#graphical-developer-console) | *8 entries.* @@ -68,30 +85,30 @@ within each phase. | Feature / integration | Status | Description / scope | Investigate | | --- | --- | --- | --- | -| General render batching | Deferred · phase 1 · unselected extension | Arbitrary contiguous packet merging requires a separate measured design; opt-in positioned text runs are implemented without sorting or changing UI packet policy. | [batching scope](planning/mid-term/README.md#general-render-batching), [text runs](assets/text-layout.md#positioned-text-runs) | +| General render batching | Deferred · phase 1 · unselected extension | Arbitrary contiguous packet merging requires a separate measured design; opt-in positioned text runs are implemented without sorting or changing UI packet policy. | [batching scope](planning/mid-term/README.md#general-render-batching), [text runs](site/assets/text-layout.md#positioned-text-runs) | | Gainput HID backend | Deferred · phase 1 · blocked TR6 | Backend initialization, device identity, retained reports and lifecycle notifications remain unfinished. HID acceptance is deferred with backend work and the observation harness. | [HID plan](planning/long-term/README.md#deferred-hid-integration) | -| Manual collision checks (clipping) | Deferred · phase 1 | No engine-level manual overlap/intersection queries for detecting object clipping; specify shape, coordinate and boundary rules independently of physics simulation. | [collision scope](planning/long-term/README.md#other-engine-extensions) | +| Manual collision checks (clipping) | Deferred · phase 1 | No engine-level manual overlap/intersection queries for detecting object clipping; specify shape, coordinate and boundary rules independently of physics simulation. | [plan](planning/long-term/README.md#game-systems) | | Multiple active rendering windows | Deferred · phase 2 | A display can own several windows, but switching the selected rendering window rejects. Multiple active contexts/presentation and their ownership require a separately selected extension. | [window plan](planning/long-term/README.md#multiple-active-rendering-windows) | | Windows native acceptance | Deferred · phase 2 | Verify GLFW/Gainput input, resize/events, controller behavior and HID notifications on Windows; some guarded platform-specific code already exists. | [platform-acceptance.md](planning/long-term/platform-acceptance.md) | -| Alternative graphics APIs | Deferred · phase 2 | No selected Vulkan, Metal or Direct3D renderer; future modules should test existing neutral graphics/resource contracts. | [README.md](planning/long-term/README.md) | -| Adaptive timing adviser | Deferred · phase 2 | Potential profiling-driven timing recommendations without changing explicit simulation/input policy or silently retiming updates. | [README.md](planning/mid-term/README.md) | -| Steam Input | Deferred · phase 2 | Define controller ownership, device selection and native/Steam interaction; prove SDK-free input policies before transport. | [README.md](planning/long-term/README.md) | -| Multi-source input composition | Deferred · phase 2 | Merge native and optional provider contributions before publishing one logical action snapshot, with precise edge and focus semantics. | [README.md](planning/long-term/README.md) | -| Multiple native input owners | Deferred · phase 2 | Generalize beyond one Gainput-initialized owner and coordinate native notification-window lifetime where demanded by consumers. | [README.md](planning/long-term/README.md) | -| Additional UI toolkit modules | Deferred · phase 2 | Add another toolkit only for a demonstrated consumer requirement; preserve neutral Engine contracts and toolkit-owned widget APIs. | [README.md](planning/long-term/README.md) | -| Expanded manifest families | Deferred · phase 2 | Broader declarative asset formats remain long-term until a specific application requires their semantics. | [README.md](planning/long-term/README.md) | +| Alternative graphics APIs | Deferred · phase 2 | No selected Vulkan, Metal or Direct3D renderer; future modules should test existing neutral graphics/resource contracts. | [plan](planning/long-term/README.md#additional-graphics-backends) | +| Adaptive timing adviser | Deferred · phase 2 | Potential profiling-driven timing recommendations without changing explicit simulation/input policy or silently retiming updates. | [plan](planning/mid-term/README.md#measured-optimization) | +| Steam Input | Deferred · phase 2 | Define controller ownership, device selection and native/Steam interaction; prove SDK-free input policies before transport. | [plan](planning/long-term/README.md#steam-api-and-input-integration) | +| Multi-source input composition | Deferred · phase 2 | Merge native and optional provider contributions before publishing one logical action snapshot, with precise edge and focus semantics. | [plan](planning/long-term/README.md#policy-before-sdk-transport) | +| Multiple native input owners | Deferred · phase 2 | Generalize beyond one Gainput-initialized owner and coordinate native notification-window lifetime where demanded by consumers. | [plan](planning/long-term/README.md#broader-native-input-ownership) | +| Additional UI toolkit modules | Deferred · phase 2 | Add another toolkit only for a demonstrated consumer requirement; preserve neutral Engine contracts and toolkit-owned widget APIs. | [plan](planning/long-term/README.md#ui-adapters) | +| Expanded manifest families | Deferred · phase 2 | Broader declarative asset formats remain long-term until a specific application requires their semantics. | [plan](planning/README.md#topics-awaiting-plans) | | Entity-component system | Deferred · phase 2 | No built-in entity registry, components/archetypes or prescribed ECS; game logic architecture belongs to the consumer. | [abstract-game.h](../projects/engine/include/cheryl/core/game-framework/abstract-game.h) | | Tilemap/world system | Deferred · phase 2 | Autotile selection is provided, but tilemap storage, regions, streaming and world-level map management are not. | [tile-selection.h](../projects/engine/include/cheryl/assets/selection/tile-selection.h) | | Physics system | Deferred · phase 2 | No physics world, rigid-body simulation or built-in solver; this simulation scope is separate from manual collision queries. | [cheryl/](../projects/engine/include/cheryl/) | | Save-game serialization | Deferred · phase 2 | Manifest JSON parsing does not constitute an application save-state or serialization subsystem. | [resources/](../projects/engine/include/cheryl/core/resources/) | -| Text shaping/glyph optimization | Deferred · phase 3 | Profile paragraph shaping, glyph preparation and upload before adding caches or accelerated line fitting. | [README.md](planning/mid-term/README.md) | -| GPU memory budgets/eviction | Deferred · phase 3 | Resource handles and caches are implemented, but automatic residency budgets, eviction and GPU-memory policy are absent. | [resource-provider.h](../projects/engine/include/cheryl/assets/resources/resource-provider.h) | -| Independent provider cache domains | Deferred · phase 3 | Global asset managers currently use one active provider/loading-owner domain; parallel independent cache domains are unsupported. | [asset-mgr.h](../projects/engine/include/cheryl/templates/asset-mgr.h) | -| Monitor hotplug event service | Deferred · phase 3 | Monitor enumeration exists; an engine-level dynamic monitor-added/removed subscription and lifecycle contract is absent. | [display-system-interface.h](../projects/engine/include/cheryl/core/display/display-system-interface.h) | +| Text shaping/glyph optimization | Deferred · phase 3 | Profile paragraph shaping, glyph preparation and upload before adding caches or accelerated line fitting. | [plan](planning/mid-term/README.md#measured-optimization) | +| GPU memory budgets/eviction | Deferred · phase 3 | Resource handles and caches are implemented, but automatic residency budgets, eviction and GPU-memory policy are absent. | [plan](planning/long-term/README.md#residency-accounting) | +| Independent provider cache domains | Deferred · phase 3 | Global asset managers currently use one active provider/loading-owner domain; parallel independent cache domains are unsupported. | [plan](planning/long-term/README.md#residency-accounting) | +| Monitor hotplug event service | Deferred · phase 3 | Monitor enumeration exists; an engine-level dynamic monitor-added/removed subscription and lifecycle contract is absent. | [plan](planning/long-term/README.md#monitor-hotplug) | | Windows worker CPU affinity | Deferred · phase 4 | Provide calling-thread CPU binding on Windows while preserving memory placement and inherited CPU eligibility. Preferred policies currently fall back; required CPU/cache locality is unavailable. | [Windows affinity scope](planning/long-term/README.md#windows-worker-affinity) | | macOS native acceptance | Deferred · phase 4 | Validate dependency configuration, windowing, input and graphics behavior on macOS. | [platform-acceptance.md](planning/long-term/platform-acceptance.md) | | Wayland native acceptance | Deferred · phase 4 | Verify GLFW Wayland windows, focus, input and OpenGL presentation; X11 is the active desktop acceptance path. | [platform-acceptance.md](planning/long-term/platform-acceptance.md) | -| Steam API services | Deferred · phase 4 | Define application-scoped service session, callback scheduling, SDK selection and integration requirements from a real consumer. | [README.md](planning/long-term/README.md) | +| Steam API services | Deferred · phase 4 | Define application-scoped service session, callback scheduling, SDK selection and integration requirements from a real consumer. | [plan](planning/long-term/README.md#steam-api-and-input-integration) | | Spatial/positional audio | Deferred · phase 4 | Audio handles expose gain, looping and playback, but no listener/source coordinates or spatial mixing contract. | [system.h](../projects/engine/include/cheryl/audio/system.h) | | OS clipboard services | Deferred · phase 4 | Both toolkit adapters reject clipboard API requests rather than reading or writing OS clipboard data. | [session.cpp](../projects/modules/ui/tgui/src/session.cpp) | | IME composition/preedit | Deferred · phase 4 | Committed Unicode text is delivered, but multi-stage input-method composition/preedit integration is missing. | [session.cpp](../projects/modules/ui/rmlui/src/session.cpp) | @@ -114,13 +131,14 @@ within each phase. | Feature / integration | Status | Description / scope | Investigate | | --- | --- | --- | --- | | **Runtime and concurrency** | | | | +| Engine and module event integration | Implemented | Typed observations, required view/service attachments, lifecycle and negotiable shutdown, optional UI clocks, asset catalogue/native provenance and bounded playback outcomes. Font catalogue and additional native capabilities remain deferred. | [current contracts](site/runtime/event-delivery.md#engine-and-module-notifications), [follow-up](planning/short-term/engine-events.md) | | EngineContext | Implemented | Owns the compatible display, input, rendering, presentation and resource adapters; controls the session domain. | [engine-context.h](../projects/engine/include/cheryl/core/engine/engine-context.h) | | AbstractGame lifecycle | Implemented | Consumer hooks for initialization, simulation update, frame preparation, quiescence and teardown. | [abstract-game.h](../projects/engine/include/cheryl/core/game-framework/abstract-game.h) | | GameRuntime execution modes | Implemented | Coordinates platform polling, simulation, rendering and shutdown in sequential or concurrent mode. | [game-runtime.cpp](../projects/engine/src/core/game-framework/game-runtime.cpp) | | SimulationScheduler | Implemented | Variable/fixed cadence with bounded recovery, skipped time and catch-up policy. | [simulation-scheduler.h](../projects/engine/include/cheryl/core/game-framework/simulation-scheduler.h) | | TickContext | Implemented | Provides simulation delta, aggregated input, framebuffer/logical dimensions and stop-request access. | [tick-context.h](../projects/engine/include/cheryl/core/game-framework/tick-context.h) | | `CE::Workers::Pool` | Implemented | Shared threads with futures, acceptance/closure, draining and exception delivery. | [worker-pool.h](../projects/engine/include/cheryl/core/engine/worker-pool.h) | -| Worker CPU placement | Implemented | Application-owned topology providers, optional runtime hwloc core/cache discovery and calling-thread binding where supported, automatic/reuse/spread policies and validated C++/JSON settings. Windows binding remains deferred to phase 4; NUMA memory placement remains deferred. Topology is not a speedup guarantee. | [worker contract](runtime/worker-execution.md) | +| Worker CPU placement | Implemented | Application-owned topology providers, optional runtime hwloc core/cache discovery and calling-thread binding where supported, automatic/reuse/spread policies and validated C++/JSON settings. Windows binding remains deferred to phase 4; NUMA memory placement remains deferred. Topology is not a speedup guarantee. | [worker contract](site/runtime/worker-execution.md) | | Weighted worker groups | Implemented | Isolated logical workloads with concurrency limits, priority, fair scheduling and statistics. | [worker-pool.cpp](../projects/engine/src/core/engine/worker-pool.cpp) | | PlatformDispatcher | Implemented | Submits captured work for execution on the native graphics/platform owner. | [platform-dispatcher.h](../projects/engine/include/cheryl/core/engine/platform-dispatcher.h) | | SimulationDispatcher | Implemented | Delivers queued callbacks on the simulation owner through future-bearing submissions. | [simulation-dispatcher.h](../projects/engine/include/cheryl/core/engine/simulation-dispatcher.h) | @@ -128,7 +146,7 @@ within each phase. | Typed EventChannel | Implemented | Compile-time payload typing in addition to event-name identity, using the persistent bus. | [event-channel.h](../projects/engine/include/cheryl/core/subsystems/event-channel.h) | | Runtime/worker diagnostics | Implemented | Domain IDs and counts for updates, input polls, backpressure, dispatch, work queues and failures. | [game-runtime.h](../projects/engine/include/cheryl/core/game-framework/game-runtime.h) | | Logging subsystem | Implemented | Structured spdlog-backed logging, sink management and synchronized logger open/close behavior. | [log.h](../projects/engine/include/cheryl/core/logging/log.h) | -| Failure/crash reporting | Implemented | Custom exception classes, best-effort failure reports, stack traces and optional native Debug handlers. | [signal-handlers/](../projects/engine/support/signal-handlers/) | +| Failure/crash reporting | Implemented | Custom exception classes, best-effort failure reports, stack traces and optional native Debug handlers. | [crash bootstrap](../projects/engine/private/diagnostics/src/main.cpp) | | **Input and display** | | | | | Neutral input system API | Implemented | Defines native adapter lifecycle, device IDs, capabilities and immutable published input views. | [input-interface.h](../projects/engine/include/cheryl/core/controls/input-interface.h) | | Action/chord binding | Implemented | Maps physical controls to game-defined buttons and axes; supports chords and binding changes. | [input-bindings.h](../projects/engine/include/cheryl/core/controls/input-bindings.h) | @@ -141,13 +159,13 @@ within each phase. | Capture leases | Implemented | RAII capture subscriptions for ordered events and committed text. | [input-capture.h](../projects/engine/include/cheryl/core/controls/input-capture.h) | | Keyboard focus routing | Implemented | Focus IDs, exclusive routing and epoch-aware leases for UI/gameplay consumers. | [input-routing.h](../projects/engine/include/cheryl/core/controls/input-routing.h) | | GLFW keyboard/mouse adapter | Implemented | Collects GLFW events and maps Gainput/native observations to Cheryl records. | [input-system.cpp](../projects/modules/platform/native-glfw/src/core/controls/input-system.cpp) | -| Native controller input (non-HID) | Implemented | Gainput-driven gamepad collection, retained-state and disconnect reconciliation exist; the Linux joystick path has an accepted HID-disabled baseline. | [native module](../projects/modules/platform/native-glfw/README.md#controller-backend-limits) | +| Native controller input (non-HID) | Implemented | Gainput-driven gamepad collection, retained-state and disconnect reconciliation exist; the Linux joystick path has an accepted HID-disabled baseline. | [native module](site/modules/native-glfw.md#controller-backend-limits) | | Display/monitor abstraction | Implemented | Monitor enumeration, primary selection, logical and framebuffer sizes and content scale. | [display-system-interface.h](../projects/engine/include/cheryl/core/display/display-system-interface.h) | | GLFW window modes | Implemented | Native resizing, normal/borderless/fullscreen transitions, close detection and cursor visibility. | [window.cpp](../projects/modules/platform/native-glfw/src/core/display/window.cpp) | | **Rendering** | | | | -| Positioned text runs | Implemented | Opt-in prepared glyph placement geometry and contiguous atlas-page draws preserve authored order, clipping, sampling and retained generations; arbitrary shader and exact boundary-pixel semantics need the per-glyph path. | [text contract](assets/text-layout.md#positioned-text-runs) | -| TGUI texture sampling | Implemented | Texture/font smoothing is copied into retained draws; immutable nearest/linear samplers share image generations without mutation. Toolkit policies disable mipmaps/anisotropy while ordinary engine defaults request anisotropy. Selected Linux sampling/UI checks are accepted; native nearest-font appearance remains separate. | [TGUI contract](../projects/modules/ui/tgui/README.md#recording-and-publication), [sampling](rendering/pipelines-and-materials.md#immutable-sampling) | -| Shader manifest schema and indexed material loading | Implemented · demo QA pending | Graphics 2.0 indexes select CPU-only program/material recipes, owned source bytes and backend-owned construction. Main text/image recipes migrated; textures and shaders stay independent, with optional asset selections and explicit incremental replacement. Linux CPU/backend checks are accepted; shader-pair failure/recovery QA remains pending. | [manifest guide](assets/asset-manifests.md#shadermaterial-definitions), [loading](assets/asset-loading.md) | +| Positioned text runs | Implemented | Opt-in prepared glyph placement geometry and contiguous atlas-page draws preserve authored order, clipping, sampling and retained generations; arbitrary shader and exact boundary-pixel semantics need the per-glyph path. | [text contract](site/assets/text-layout.md#positioned-text-runs) | +| TGUI texture sampling | Implemented | Texture/font smoothing is copied into retained draws; immutable nearest/linear samplers share image generations without mutation. Toolkit policies disable mipmaps/anisotropy while ordinary engine defaults request anisotropy. Selected Linux sampling/UI checks are accepted; native nearest-font appearance remains separate. | [TGUI contract](site/modules/tgui.md#recording-and-publication), [sampling](site/rendering/pipelines-and-materials.md#immutable-sampling) | +| Shader manifest schema and indexed material loading | Implemented · demo QA pending | Graphics 2.0 indexes select CPU-only program/material recipes, owned source bytes and backend-owned construction. Main text/image recipes migrated; textures and shaders stay independent, with optional asset selections and explicit incremental replacement. Linux CPU/backend checks are accepted; shader-pair failure/recovery QA remains pending. | [manifest guide](site/assets/asset-manifests.md#shadermaterial-definitions), [loading](site/assets/asset-loading.md) | | Renderer abstraction | Implemented | Backend-neutral lifecycle, frame rendering, viewport, clear and camera matrix operations. | [renderer.h](../projects/engine/include/cheryl/core/rendering/renderer.h) | | OpenGL backend | Implemented | GL-based pass playback, context validation, native rendering and resource maintenance. | [renderer.cpp](../projects/modules/graphics/opengl/src/backends/opengl/renderer.cpp) | | GLFW OpenGL context | Implemented | Context creation, activation and proc loading through the GLFW graphics module. | [glfw-context.h](../projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-context.h) | @@ -171,7 +189,7 @@ within each phase. | Asset manifest definitions | Implemented | Schema-backed CPU definitions of sprites, tilesets, pivots, grids, animations and texture references. | [manifest.h](../projects/engine/include/cheryl/assets/definitions/manifest.h) | | Manifest parser | Implemented | Parses sprite/tileset and shader documents selected by graphics indexes 2.0; validates exact registered paths and CPU references before construction. | [manifest-loader.cpp](../projects/engine/src/core/resources/asset-management/manifest-loader.cpp) | | Asset preparation/upload | Implemented | Separates CPU manifest scan/decode/validation from serialized provider-side GPU upload. | [asset-loader.cpp](../projects/engine/src/core/resources/asset-management/asset-loader.cpp) | -| Shared asset cache contracts | Implemented | Global asset managers retain immutable resource generations with synchronized publication. | [asset-mgr.h](../projects/engine/include/cheryl/templates/asset-mgr.h) | +| Shared asset cache contracts | Implemented | Global asset managers retain immutable generations with synchronized publication, exact-key catalogue revisions, seeded watches and separate graphics-domain provenance. | [asset-mgr.h](../projects/engine/include/cheryl/templates/asset-mgr.h) | | Texture manager | Implemented | Normalized-path image cache, exact-key lookup and provider-owner loading checks. | [texture-mgr.h](../projects/engine/include/cheryl/core/resources/asset-management/texture-mgr.h) | | Sprite definitions/assets | Implemented | Shared immutable texture/grid resources, pivot, orientations, views and clip definitions. | [sprite.h](../projects/engine/include/cheryl/assets/types/2d/sprite.h) | | Sprite animation cursors | Implemented | Named animation selection, facing filters, independent clock advancement and loop/clamp. | [sprite.cpp](../projects/engine/src/assets/types/2d/sprite.cpp) | @@ -184,10 +202,10 @@ within each phase. | Legacy bitmap/font paths | Implemented | `CE::Text::Legacy::AsciiFont` and deprecated `BitmapFont` remain available; recovered binary widths/atlas restore bitmap-font inputs, while JSON widths are metadata only. | [2d/](../projects/engine/include/cheryl/assets/types/2d/) | | System font discovery | Implemented | Searches font roots and selects installed font-file candidates. | [fonts-system.h](../projects/engine/include/cheryl/core/resources/fileio/fonts-system.h) | | **Ui and audio** | | | | -| TGUI UI bridge | Implemented | Owns toolkit session, widget APIs, input translation, CPU scene recording and scene upload. | [tgui/](../projects/modules/ui/tgui/) | -| RmlUi UI bridge | Implemented | Owns native documents/fonts, keyboard/text/pointer input, rectangular clipping and retained scene uploads. Core integration is sufficient to defer further extensions. | [rmlui/](../projects/modules/ui/rmlui/) | +| TGUI UI bridge | Implemented | Owns toolkit sessions, widgets/input, CPU scene recording/upload and opt-in automatic simulation view/time following. | [tgui/](../projects/modules/ui/tgui/) | +| RmlUi UI bridge | Implemented | Owns native documents/fonts, input, clipping, retained scene uploads and opt-in automatic simulation view/time following. Core integration is sufficient to defer further extensions. | [rmlui/](../projects/modules/ui/rmlui/) | | Nonblocking UI scene adoption | Implemented | Uploads recorded UI resources on graphics owner and adopts completed scene futures without blocking update. | [scene.h](../projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h) | -| Audio Clip/Voice/System API | Implemented | Backend-neutral immutable PCM clips, synchronized voice control and independent audio lifecycle. | [system.h](../projects/engine/include/cheryl/audio/system.h) | +| Audio Clip/Voice/System API | Implemented | Backend-neutral immutable PCM clips, synchronized control, independent output lifetime and optional generation outcomes with bounded owner pumping. | [system.h](../projects/engine/include/cheryl/audio/system.h) | | WAV/FLAC/MP3 decoding | Implemented | miniaudio-based CPU decode with supported-format checks and bounded output size. | [decode.cpp](../projects/modules/audio/miniaudio/src/decode.cpp) | | Offline audio mixer | Implemented | Device-free audio output rendered to caller-provided floating-point buffers. | [miniaudio.h](../projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h) | | **Utilities and memory** | | | | @@ -203,57 +221,95 @@ within each phase. | Pivot/anchor geometry | Implemented | Pivot presets, anchor conversion, quad/strip generation and Y-up geometry. | [anchor.h](../projects/engine/include/cheryl/math/anchor.h) | | General math utilities | Implemented | Byte, pointer, binary, fitting, numeric and time helper functions. | [math/](../projects/engine/include/cheryl/math/) | | File manager and path I/O | Implemented | Core file-manager interface and path-oriented file operations, distinct from installed font discovery. | [file-mgr.h](../projects/engine/include/cheryl/core/resources/fileio/file-mgr.h) | -| External Debug terminal | Implemented · Linux desktop acceptance pending | Optional Linux module owns process capture/viewer and test-report routing. The configuration matrix is accepted; AUTO links actual Debug only and non-Debug inclusion needs a runtime enable option. Engine supplies an optional startup callback attachment. | [module guide](../projects/modules/platform/debug-terminal-linux/README.md), [desktop QA](testing-requests.md#tr18-qa-linux-debug-terminal-on-a-real-desktop) | +| External Debug terminal | Implemented · Linux desktop acceptance pending | Optional Linux module owns process capture/viewer and test-report routing. Selection defaults on for supported single-configuration Debug builds; explicit ON includes every configuration, with non-Debug startup requiring a runtime enable option. Engine supplies an optional startup callback attachment. | [module guide](site/modules/debug-terminal-linux.md), [desktop QA](testing-requests.md#tr18-qa-linux-debug-terminal-on-a-real-desktop) | | **Build and verification** | | | | -| Installed CMake package | Implemented | Opt-in CherylSDK component exports prebuilt selected libraries, headers, dependency targets and licenses; selected hosted Linux and Windows installed consumers are accepted. Windows bundles native dependencies. | [SDK consumption](development/consuming-engine.md#prebuilt-development-sdks) | -| GitHub CI and SDK artifacts | Implemented | Linux GCC/Clang and Windows MSVC focused builds/tests plus relocated SDK consumers; successful main validation collects compiled development SDK artifacts without applications or release publication. Hosted builds and relocated consumers are accepted with explicit Windows skipped-case limits; first main-push artifact collection remains pending. | [CI matrix](development/building.md#github-ci-and-sdk-artifacts), [TR20](testing-requests.md#tr20-automated-main-push-sdk-collection) | +| Installed CMake package | Implemented | CherylSDK component exports prebuilt selected libraries, headers, dependency targets and licenses; selected hosted Linux and Windows installed consumers are accepted. Windows bundles native dependencies. | [SDK consumption](site/development/consuming-engine.md#prebuilt-development-sdks) | +| GitHub CI and SDK artifacts | Implemented | Linux GCC/Clang and Windows MSVC focused builds/tests plus relocated SDK consumers; successful main validation collects compiled development SDK artifacts without applications or release publication. Hosted builds and relocated consumers are accepted with explicit Windows skipped-case limits; first main-push artifact collection remains pending. | [CI matrix](site/development/ci.md), [TR20](testing-requests.md#tr20-automated-main-push-sdk-collection) | | Modular CMake root | Implemented | C++23 build splits Engine and native graphics/platform/UI/audio owners. | [CMakeLists.txt](../CMakeLists.txt) | | CMake option selection | Implemented | Root switches for GLFW, OpenGL, TGUI, RmlUi, miniaudio, native input, demo and test modes. | [CherylOptions.cmake](../cmake/CherylOptions.cmake) | | Named library target aliases | Implemented | Consistent identities such as Cheryl::Engine, Cheryl::NativeGLFW and Cheryl::Audio::Miniaudio. | [CherylTargets.cmake](../cmake/CherylTargets.cmake) | | Standalone module composition | Implemented | Module entry points assemble their local dependency graph without requiring every other module. | [CherylModules.cmake](../cmake/CherylModules.cmake) | -| Build-tree consumers | Implemented | Embeddable source consumers can use add_subdirectory() and target-linked Engine/module libraries. | [consuming-engine.md](development/consuming-engine.md) | +| Build-tree consumers | Implemented | Embeddable source consumers can use add_subdirectory() and target-linked Engine/module libraries. | [consuming-engine.md](site/development/consuming-engine.md) | | GoogleTest suites | Implemented | Engine/module unit, suite, aggregate and optional acceptance runners are declared and have test source; subject guides own configuration-specific acceptance. | [CherylTests.cmake](../cmake/CherylTests.cmake) | | CTest discovery | Implemented | Selected GoogleTest runners are registered with CTest using test discovery. | [CherylTests.cmake](../cmake/CherylTests.cmake) | | OpenGL native acceptance sources | Implemented | Existing tests exercise clipping, resource lifetime, input recovery, renderer shutdown and failure paths; source presence does not establish every native path. | [native-opengl.cpp](../projects/modules/graphics/opengl/tests/acceptance/src/native-opengl.cpp) | -| Cross-UI integration test | Implemented | Source/target for a consumer linking both TGUI and RmlUi modules; the UI guide records selected root-composition acceptance. | [UI validation](development/ui-adapters.md#repeating-linux-root-validation) | +| Cross-UI integration test | Implemented | Source/target for a consumer linking both TGUI and RmlUi modules; the UI guide records selected root-composition acceptance. | [UI validation](site/development/ui-adapters.md#repeating-linux-root-validation) | | Consumer header probes | Implemented | Checks selected public API headers and detects accidental native GL/GLFW leakage into neutral includes. | [CherylConsumers.cmake](../cmake/CherylConsumers.cmake) | | Native demo application | Implemented | Runnable composition example for input, camera, asset drawing, text and optional UI toolkits. | [main.cpp](../projects/apps/demo/src/main.cpp) | | Debug sanitizers | Implemented | Non-MSVC Debug configuration enables address/undefined-behavior sanitizers. | [CMakeLists.txt](../CMakeLists.txt) | | Third-party dependency wiring | Implemented | Pinned submodules plus FreeType, HarfBuzz, ICU, GLM, STB, JSON, spdlog, backward-cpp, CTTI and CLI11. | [CherylDependencies.cmake](../cmake/CherylDependencies.cmake) | -*100 entries.* +*101 entries.* ## Assessment and direction -The review's qualitative estimate of foundational architecture maturity is -approximately **85%**. It concerns established engine foundations, not completion -of the roadmap, and is not calculated from feature counts, coverage or test results. +The original review's qualitative estimate of foundational architecture maturity +was approximately **85%**. It concerned established engine foundations and was not +calculated from feature counts, coverage or test results. The implemented facilities support a desktop 2D consumer; higher-level game services remain consumer-selected. Cache/core locality, positioned text submission and CI/SDK source work are implemented. Selected Linux controlled/native worker, text-run, sampling/UI and relocated SDK checks are accepted within their -[subject-guide limits](README.md). Exact hosted Linux/Windows execution and the main -SDK collection gate remain pending; local Linux results do not establish Windows -runtime support. Terminal configuration/desktop acceptance, independent Engine-only -Release graph verification and targeted shader-reload/artwork QA remain in the -[testing queue](testing-requests.md). NUMA remains deferred indefinitely; general -packet merging and sorting require separate scope and workload evidence. +[subject-guide limits](README.md). Hosted Linux/Windows builds and relocated SDK +consumers, the independent Engine-only Release graph and the terminal configuration +matrix are accepted within their recorded limits. Desktop/device acceptance and +the first main-push SDK collection remain separate below. NUMA remains deferred +indefinitely; general packet merging and sorting require separate scope and workload +evidence. Recorded Linux acceptance covers the initial -[Unicode scope](assets/text-layout.md#native-acceptance-scope), -[audio baseline](../projects/modules/audio/miniaudio/README.md#acceptance-boundaries) +[Unicode scope](site/assets/text-layout.md#native-acceptance-scope), +[audio baseline](site/modules/miniaudio.md#acceptance-boundaries) and [sprite/tile samples](../projects/apps/demo/README.md#tile-and-sprite-samples), as well as the selected startup/UI composition. Their guides retain meaningful coverage limits, including bidi observations, compressed streaming, shader-failure recovery and simultaneous close/resize. Dedicated new regressions and supplied-SDK composition gaps remain in their owning plans; they are not blanket pending QA. -The [runtime architecture](runtime/runtime-architecture.md) explains system composition; +The [integration guide](site/development/consuming-engine.md#context-assembly) explains system composition; [subject guides](README.md) own detailed contracts. The -[roadmap](planning/develop-review-and-development-plan.md) owns sequencing, +[planning catalogue](planning/README.md) routes to subject plans, [long-term plans](planning/long-term/README.md) retain platform/consumer prerequisites, and the [testing queue](testing-requests.md) owns pending executable acceptance. +## Remaining acceptance + +Linux/GLFW/X11/OpenGL is the active native platform. Deferred platform coverage +does not gate Linux work. These remaining checks have independent prerequisites: + +| Work | Next action and owner | +| --- | --- | +| Main-push SDK collection | Verify the first successful main-push collection gate in [TR20](testing-requests.md#tr20-automated-main-push-sdk-collection). Hosted builds and relocated SDK consumers are accepted; PR/develop/manual runs intentionally skip collection. | +| Hosted Windows skipped cases | Supply a CFF font fixture and a temporary directory on the checkout drive, then accept the affected font-allocation and logging cases. The [CI coverage limits](site/development/ci.md#hosted-windows-coverage-limits) retain the exact scope. | +| Linux Debug terminal | Complete real-desktop [TR18](testing-requests.md#tr18-qa-linux-debug-terminal-on-a-real-desktop) with matching probe binaries, including emulator windows and desktop/IDE launch. | +| Demo material/artwork QA | Complete shader-pair failure/recovery and available artwork observations in [TR19](testing-requests.md#tr19-qa-indexed-demo-materials-and-recovered-graphics). Missing images and showcase fixtures remain blockers. | + +The [short-term plan](planning/short-term/README.md) owns unimplemented +font-selection and startup/UI regressions, supplied RmlUi composition acceptance +and the upstream placeholder follow-up. Existing Linux acceptance does not close +those dedicated gaps. The [demo guide](../projects/apps/demo/README.md#repeating-native-qa) +owns reusable native procedures. + +## Supporting plans + +These plans cover follow-up work, asset content and deferred investigations outside +the feature classification counts above. Each has an owning document or section: + +| Work | Readiness | Plan | +| --- | --- | --- | +| Further event capabilities | Deferred; select a consumer before font catalogue, independent native pumping or additional device/OS lifecycle work. | [Event follow-up](planning/short-term/engine-events.md) | +| Font and startup/UI regression coverage | Existing baseline accepted; dedicated boundary, result-handoff, callback and placeholder cases remain. | [Font selection](planning/short-term/README.md#font-selection-regressions), [Startup/UI](planning/short-term/README.md#startup-and-ui-follow-up) | +| Supplied RmlUi composition | Needs a corrected dependency and independent consuming host. | [Supplied dependency acceptance](planning/short-term/README.md#supplied-rmlui-acceptance) | +| Upstream placeholder correction | Needs an unmodified reproduction and matching regression before submission. | [Upstream follow-up](planning/short-term/README.md#upstream-placeholder-follow-up) | +| Artwork metadata | Needs source evidence or artwork-owner decisions for remaining manifest semantics. | [Unresolved metadata](planning/long-term/asset-manifest-metadata.md) | +| Additional dungeon manifests | Deferred; acquired packages still need separate image inventories and manifests. | [Dungeon packages](planning/long-term/README.md#additional-dungeon-asset-manifests) | +| Atlas padding/repacking | Deferred; scope not selected. | [Atlas scope](planning/long-term/README.md#atlas-padding-and-repacking) | +| NUMA discovery/memory placement | Indefinitely deferred; needs a consumer and multi-node hardware justification. | [NUMA prerequisites](planning/long-term/README.md#numa-locality) | + +Some deferred feature entries have only a description or consumer-required deferral, +not a concrete development plan. The [planning gaps](planning/README.md#topics-awaiting-plans) +identify that remaining scoping work; they are not ready implementation plans. + ## Status maintenance Update an entry when its implementation or acceptance changes, checking current diff --git a/docs/assets/asset-loading.md b/docs/assets/asset-loading.md deleted file mode 100644 index c8d82fcc..00000000 --- a/docs/assets/asset-loading.md +++ /dev/null @@ -1,155 +0,0 @@ -# Asset loading - -`CE::Assets::Manifests::Reader` parses graphics manifests 2.0 into CPU-only -sprite/tileset definitions and shader/material recipes. An owned -`CE::Assets::Loading::Loader` keeps one immutable root: -preparation selects indexed documents, decodes owned RGBA pixels and snapshots -shader source bytes without a provider; `upload()` constructs resources and -publishes retained metadata on the provider's loading thread. - -## Runtime entry point - -After initializing the selected backend, load a root with its provider: - -```cpp -CE::Assets::Loading::Loader loader("assets"); -loader.load_assets(engine.resources()); -auto actor = CE::Assets::Sprites::Manager::get().get_asset("miniworld:soldier_swordsman_cyan"); -auto documents = loader.manifests(); // shared_ptr> -``` - -Each asset family owns its definitions, retained values and cache API. Sprite and -tileset managers expose `load()` for definitions; the image manager exposes -`load()` for paths and `upload()` for decoded pixels. Their inherited `get_asset()` -and `clear_assets()` retain the shared cache contract. The loader's -`load_assets()` remains the complete-root preparation/upload convenience path. -The [value guide](asset-values-and-playback.md#api-domains) maps the family names. - -Each preparation scans again. Only exact `graphics-manifests.json` indexes select -documents; schemas and unlisted JSON are not parsed. Index, texture and shader -references resolve from the enclosing `graphics/` directory, and asset keys use -`namespace:name`. `prepare()` selects every discovered index and decodes referenced -images plus standalone PNGs below the root, deduplicated in sorted order. -`prepare_graphics(indexes)` selects explicit indexes relative to the Loader root -(or supplied absolute paths), decoding only their referenced images. A shader-only -selection decodes no images. Cross-document -IDs and every grid's bounds are checked against those exact decoded dimensions -before any upload. Upload consumes the owned pixels without reopening image files. -Pixels are four-channel RGBA in the decoder's default top-to-bottom row order. -OpenGL reverses those rows in transient upload storage to match the atlas -geometry's upward-positive UV coordinates; the supplied pixels remain unchanged. -The separate stb alpha-atlas path preserves its baked row/UV convention. - -Shader sources resolve through exact registered paths, including when basenames -repeat. Preparation owns the bytes, so upload does not reopen shader files either. -Program/material kinds, references and asset selections are checked within the -selected batch; an unrelated cached material cannot satisfy a missing selection. -`register_files()` performs discovery/publication of unmanaged paths only, without -opening JSON, decoding images or requiring a provider. - -Preparation can use a tracked context worker group. Own both the loader and its -`CE::Assets::Loading::Prepared` value through the platform handoff; simulation only retrieves ready -futures, and never blocks on CPU preparation or GPU upload: - -```cpp -auto workers = engine.make_worker_group(); -auto platform = engine.platform_dispatcher().submission(); -auto loader = std::make_shared("assets"); -auto preparing = workers.submit([loader, platform] { - auto prepared = loader->prepare(); - return platform.submit( - [loader, prepared = std::move(prepared)](CE::Runtime::Context& owner) mutable { - loader->upload(std::move(prepared), owner.resources()); - return loader->manifests(); - } - ); -}); -// A later update: only after preparing.wait_for(0s) reports ready, get the -// returned upload future. Check that future's readiness before reading metadata. -``` - -An owned root or injected shared root supplies physical capacity; this group -belongs to the context shutdown domain. Accepted CPU jobs finish while the -platform dispatcher remains available. Any final unexecuted upload is cancelled -before resource teardown, so its future reports failure instead of hanging. -Use platform submission endpoints rather than borrowed dispatcher pointers. - -## Publication and retry - -`CE::Assets::Loading::Prepared` owns decoded pixels, shader bytes and definitions, with no provider or native -handles. Preparation failure changes no caches or published metadata. Upload -consumes the supplied value on the provider's loading owner; moving it into a -request transfers that ownership. Retry needs a fresh preparation or a preserved -copy. Pending platform requests can cancel before execution under the -[dispatcher contract](../runtime/thread-dispatch.md); executing uploads have no -rollback or mid-batch cancellation contract. - -Each created cache entry can become visible before the next entry is created. -Upload failure preserves completed entries and the last successful metadata -snapshot. Allocation of that final snapshot can fail even after all entries exist. -Retry skips existing keys rather than replacing them, so published manifests -describe submitted definitions rather than an atomic view of current caches. -Changed files under the same keys do not provide hot reload. Readers retain old -metadata snapshots after another upload or loader destruction. - -Upload checks the optional provider-owned `shader_asset_builder()` capability, -then publishes programs/materials before images and sprite/tileset resources. -Providers without that capability reject selected shader recipes explicitly. -`CE::Assets::Shaders::Manager` retains each recipe together with its executable generation; -material construction uses the retained program generation even when an ordinary -load preserves an existing program. Asset selections retain their material handle. - -`upload(prepared, provider, true)` explicitly replaces shader generations only. -Each candidate is complete before publication; failure preserves that key's old -generation, while earlier successful replacements remain published. Existing -assets and frames keep their old handles rather than automatically rebinding. -`shader_manifests()` and `manifests()` describe the last successful submitted -definitions, not an atomic cache snapshot. `diagnostics()` reports the last attempt, -including partial publications/replacements. There is no watch-based reload or -whole-batch rollback. - -`load_assets(provider)` is the synchronous prepare/upload convenience path. -Construct separate loaders for separate roots. Legacy `Loader::get(root)` remains -available but rejects a different root after its first initialization; `get()` only -retrieves an already-initialized singleton. - -## Resources and application bootstrap - -The generic loader loads indexed shaders/materials, images, sprites and tilesets. -The application still chooses fonts explicitly; the -[file/font discovery guide](file-and-font-discovery.md) defines candidate selection, -index freshness and the legacy font cache's default/collection-face policy. The demo selects -`graphics/shaders/graphics-manifests.json` and retrieves `main:text` and `main:images` -from `CE::Assets::Shaders::Manager::find_material()`, including with `--full-assets`. F5 prepares that same index -and requests explicit replacement. The demo adopts its pair only after both -replacements succeed; failed reload preserves its previous application pair even -if the shared catalogue has published an earlier candidate. Fonts remain outside -the graphics manifest contract. Manual material builders remain supported; the -[rendering guide](../rendering/pipelines-and-materials.md#manual-material-construction) -shows the UI example that deliberately retains this path. - -`CE::Rendering::Resources::Provider::create_image()` accepts -`CE::Assets::Images::Decoded` RGBA pixels. -`upload_geometry(span, topology)` copies the transient view before -returning. The older shared-pointer/count overload keeps its CPU owner only through -that upload. Atlas cells use independent four-vertex strips; images and glyphs use -six-vertex triangle quads. Backend handles never retain these CPU buffers. OpenGL -checks dimension, byte-count, vertex-count, and draw-range limits before use. - -## Typed manifest data and consumers - -Engine definition types retain grid origins, frame sizes, spacing, pivots, views, -orientations, timed clips and facings, animated tile targets, Wang terrain metadata, -weighted variants, and neighbor-mask lookup tables. -`CE::Assets::Sprites::Asset::definition()` and -`CE::Assets::Tilesets::Asset::definition()` expose them after construction. Entity-owned -`CE::Assets::Sprites::Animation` already advances timed clips and publishes resolved cells; the -cached sprite has no mutable playback cursor. Tile-map neighbor selection and -application meanings for views/orientations remain gameplay work. -The [asset value and playback contract](asset-values-and-playback.md) defines direct -construction limits, borrowed metadata, lookup failure and submission ownership. - -Use `CE::Assets::Manifests::Reader::load(file)` or `parse(stream, source)` for document-only tools. -Use the owning focused runners or `all-tests` for regressions; -[architecture validation](../development/architecture-validation.md) describes -selection and coverage limits. diff --git a/docs/assets/text-layout.md b/docs/assets/text-layout.md deleted file mode 100644 index 5b293d81..00000000 --- a/docs/assets/text-layout.md +++ /dev/null @@ -1,206 +0,0 @@ -# Unicode text layout and font resources - -The initial rendering scope is accented Latin (including English, French and German), -Russian/Cyrillic, mixed paragraph direction and optional width-constrained wrapping. -Glyph coverage and shaping are font/script concerns rather than a separate renderer -for every language. Color emoji is a planned -[next text batch](../planning/mid-term/README.md#color-emoji); -wider CJK/script acceptance remains in the -[long-term plan](../planning/long-term/README.md#other-engine-extensions). -Display source maps do not establish caret movement, selection or IME/preedit. - -The service pairs face-qualified glyph placements with complete immutable page -generations. Its public stages have separate owners: - -| Namespace | Ownership | -| --- | --- | -| `CE::Text` | Immutable font collections, UTF-8/source records and shaped layouts; no uploaded resources. | -| `CE::Text::Rendering` | `Prepared` owns CPU layout/raster pages; `Generation` retains their uploaded geometry/atlas handles. | -| `CE::Rendering::Submission` | `resolve_text()` and `resolve_text_runs()` copy draw values into retained packets. | - -`CE::Text::Legacy::Font` exposes one atlas/geometry pair, which cannot represent -that fallback model. Its `AsciiFont`/`BitmapFont` implementations and toolkit-owned -text retain their separate roles. - -## Font selection - -`CE::Text::FontCollection::load(selection)` creates an immutable CPU snapshot of font -bytes. `FontSelection::preferred` interleaves application `CE::Text::FontFile` paths/collection -face indices and `CE::Text::SystemFontFamily` names in the application's desired order. Face -indices select ordinary collection faces, with a variable font's default coordinates; -named variation instances are not selected through that index. File -entries are strict: missing, unreadable, corrupt, nonscalable or non-Unicode faces -fail loading. An unavailable installed family is skipped. Nothing is published on -failure, and earlier font collections remain usable. Allocation failures propagate. - -System discovery uses the existing -[root traversal](file-and-font-discovery.md#system-font-candidates), but family -selection inspects font metadata rather than guessing from filenames. Family names -compare ASCII letters without case. Selection first prefers faces that are neither -bold-flagged nor heavier than Medium (weight 500), then upright faces, then the -declared OpenType weight closest to Regular (400). When numeric weight metadata -is absent, the bold flag supplies a 700/400 weight estimate. Sorted path/face order -breaks ties. Discovery is best effort. Set `system_directories` -to an explicit root list for application-controlled inventory, or an empty list for -no installed-font search; unset uses the system roots. Keep environment/files stable -during loading. A snapshot no longer needs those files afterward. - -With `automatic_system_fonts`, append available Arial, Segoe UI, Helvetica, Noto Sans, -DejaVu Sans and Liberation Sans families in that order, skipping a family if its -best available face is bold-flagged or heavier than 500. Explicit preferred file -and family sources remain unrestricted by this automatic-weight limit; explicit -families use the same ranking but can select a heavy face when no lighter one exists. -These preferences are not guaranteed to exist across operating systems or regions. -Repeated path/face entries are deduplicated. The terminal fallback is always an -[embedded, licensed DejaVu Sans](../../assets/fonts/README.md) face independent of the -application's fonts, UI modules and installed inventory. The fallback covers the -initial alphabets and contains a visible U+FFFD replacement; it cannot supply the -original glyph for every Unicode character. - -`faces()` returns metadata in selection order. Its indices identify faces only -within that retained collection. `covers(scalar)` checks whether any selected cmap -contains the scalar; it does not prove whole-grapheme coverage or successful shaping. -Each query opens local FreeType state, so immutable collections do not share mutable -face/size state between CPU workers. Library-native types stay out of public headers. - -## Shaping, direction and width - -`CE::Text::layout_text(fonts, utf8, options)` returns immutable `CE::Text::ShapedText`: original -[decoded scalar records](text-encoding.md), face-qualified glyph IDs/positions, -shaping-cluster byte/scalar ranges and line metrics. It retains its font collection -and copied options but no borrowed text, provider or uploaded resource. The font -size is an em size in local pixels, with Y-up baseline coordinates. It is independent -of camera/screen units and does not change the legacy AsciiFont/BitmapFont metric contract. - -[HarfBuzz](https://harfbuzz.github.io/what-is-harfbuzz.html) handles OpenType shaping, -kerning and ligatures. A precomposed accent and its combining sequence can produce -the same glyph while retaining different source ranges. Multiple glyphs may share a -cluster; a ligature can cover multiple scalars/graphemes. Cluster ranges describe -display associations, not selectable caret stops. Language is an optional BCP 47 -hint (default `und`); it does not filter scripts or translate text. Invalid tags fail. - -ICU resolves [paragraph bidi](https://unicode-org.github.io/icu/userguide/transforms/bidi.html) -before visual run shaping, and applies line direction after breaking. Direction is -automatic (first strong character, otherwise LTR), explicit LTR or explicit RTL. -Latin/digits retain their natural run direction in an RTL paragraph. Unicode overrides, -isolates and default-ignorable formatting participate in bidi without drawing visible -replacement boxes. ICU resolves bidi controls before shaping; those controls stay -in the owned scalar records and line ranges but are omitted from the shaping buffer -so their removal cannot replace a visible glyph's source index. Joiners and variation -selectors remain available to HarfBuzz. With a width, RTL paragraphs align to its -right edge; without a width, visual lines start at local X=0. CR/LF/CRLF and Unicode -paragraph separators start paragraphs; U+2028 forces a line within its existing paragraph. - -ICU [grapheme boundaries](https://unicode-org.github.io/icu/userguide/boundaryanalysis/) -define fallback units. Select the first face covering all visible scalars of each -grapheme, keeping combining marks with their base. An uncovered grapheme becomes -one U+FFFD from the embedded face, with `ShapedGlyph::missing` set and its original -source range retained. A notdef returned by shaping also becomes a visible embedded -replacement. Literal U+FFFD and malformed UTF-8 remain distinguishable through scalar -records; encoding replacement and missing font coverage are separate properties. -Embedded NUL is a scalar requiring fallback, rather than an end marker. - -An optional positive finite `maximum_width` enables greedy Unicode line breaking. -The UI/application derives that local width from its container and text scale; layout -does not query a window or UI object. Chosen lines are shaped with their actual line -boundaries and accepted by measured advance, preserving paragraph bidi across wraps. -Oversized words break at grapheme boundaries. A single too-wide grapheme is admitted -on its own line and marks `overflow`; clipping/scaling remains a caller decision. -Tabs expand to four spaces. Trailing ASCII spaces/tabs and line separators have no -visual advance, while line source ranges retain their consumed bytes. Other spaces -follow font/Unicode break rules. No automatic hyphenation, justification or vertical -writing is supplied. - -Text byte/UTF-16 and font size limits are checked before narrowing into library -indices/metrics. Invalid options fail before publication, allocation/library failures -propagate, and previous layouts are unchanged. Each layout uses local FreeType, -HarfBuzz and ICU state, allowing concurrent queries against one font collection. -Line fitting currently measures candidate prefixes; very long constrained paragraphs -can require repeated shaping. Optimize that only with representative measurements, -while retaining the actual accepted-line width and source/bidi contracts. - -## Preparation, upload and retained submission - -`CE::Text::Rendering::prepare(shaped, raster_options)` owns the layout and prepares -grayscale alpha pages and six-vertex glyph quads on the CPU. It rasterizes each -distinct face-qualified glyph once for that message, retaining glyph bearings and -shaped placement offsets. Spaces/empty outlines advance without drawing. Pages use -one transparent border pixel, are cropped to used padded bounds and preserve the -font provider's row/UV order. The configured maximum page extent is 4..4096 pixels -(default 1024); a glyph too large for one padded page fails before a bitmap allocation. -There is no shared glyph cache, in-place repack, eviction or residency budget. - -`CE::Text::Rendering::upload(prepared, provider)` copies CPU buffers into fresh immutable page -geometry/atlas handles on the provider's upload owner. Passing a copy preserves CPU -preparation for retry; moving it into an owned -[dispatcher request](../runtime/thread-dispatch.md) transfers that -value. A successful call returns one `CE::Text::Rendering::Generation` with its matching -placements and resources. A failure returns no generation and publishes no replacement. -Already created transient resources follow backend retirement; this is not an atomic -native-resource transaction. Callers publish a complete candidate only after success. - -`CE::Rendering::Submission::resolve_text(rendered, style, context)` performs CPU packet resolution alongside -the legacy font overload. Supply a nonempty `context.image` pipeline key/unit so -each glyph selects its own generation page; that key must be absent from draw -parameters. Glyph offsets are scaled and transformed by the supplied model exactly -as in legacy submission. Packets copy values and retain both page handles. Replacing -or releasing the text/font/preparation cannot pair old quad ranges with a newer atlas. -Old submitted frames retain their resources, subject to the original backend domain -remaining alive for native use. Submission performs no shaping, discovery, rasterization, -upload, binding or drawing. - -### Positioned text runs - -Set `CE::Text::Rendering::RasterOptions::positioned_runs = true` during `CE::Text::Rendering::prepare()` to retain -additional geometry with glyph placements baked into its vertices. Ordinary page -geometry remains available for per-glyph submission; opting in trades preparation, -upload and geometry storage for fewer per-frame packets. The extra geometry repeats -quads in authored glyph order and records each contiguous atlas-page range. A page -sequence A/B/A remains three runs rather than grouping both A ranges together. - -After upload, call `CE::Rendering::Submission::resolve_text_runs(rendered, style, context)` to emit one packet -per run. Preparation must have opted in; an ordinary generation rejects this call. -The image key/unit, immutable sampling, clipping, material and whole-text model/scale -remain those selected by the caller. Packets retain the positioned geometry and -matching atlas generation through replacement/release; empty text emits no draws. - -This overload requires a shader applying the common scale/model transform to the -positioned vertices, without per-glyph or draw-identity semantics. The API cannot -infer that contract from arbitrary shaders. Use `CE::Rendering::Submission::resolve_text()` for those shaders -or when the original per-glyph raster evaluation is required: regrouping floating-point -transforms can change coverage of exact boundary pixels. The default preparation -and submission remain per glyph. This facility does not merge arbitrary frame -packets, reorder draws or change either UI adapter's batching policy. - -The [demo](../../projects/apps/demo/README.md#builtin-unicode-text) submits immutable -HUD/camera text, prepares replacements on a CPU worker and uploads through the -platform dispatcher. Its preview supports builtin-only, automatic, explicit file/ -family and paragraph-direction choices. The demo guide preserves -[native preview procedures](../../projects/apps/demo/README.md#unicode-preview-validation). -Its `--text-runs` option selects positioned submission for builtin text and sample -labels; toolkit-owned text keeps its separate path. - -## Native acceptance scope - -Linux/GLFW/X11/OpenGL sequential/concurrent preview appearance, wrapping, paragraph -alignment and resource replacement/shutdown are accepted for the initial grayscale -scope. Similar output across runtime/font variants is compatible with the same -effective glyph faces and placements. Right-aligned Latin under explicit RTL and -unchanged Hebrew-only output between automatic/RTL modes match the direction -contract above; this native report does not independently confirm Hebrew glyph order. -The accepted [CPU bidi case](../../projects/engine/tests/unit/src/text-layout.cpp) -checks Hebrew/mixed visual source order and preserves Latin order under explicit RTL. -Alignment alone is not proof of every bidi sequence. - -Linux OpenGL run acceptance compares the test application's 64×64 backbuffers with -`glReadPixels`, drawing the same retained generation through per-glyph and run paths. -It covers nearest/linear sampling, clipping, signed scale/rotation, page transitions -and replacement lifetime. It does not capture the desktop or other applications. -These fixture comparisons and CPU submission measurements do not establish every -boundary-pixel identity, physical-GPU timing or other-platform appearance. - -Dedicated numeric font-weight/style fixtures remain in the -[Unicode plan](../planning/short-term/unicode-text.md#progress). Color/CJK rendering, -grapheme/bidi editing, IME and additional platforms retain separate scope. Reusable -[CPU checks](../development/architecture-validation.md#engine-asset-and-text-regressions) -and these Linux observations do not establish other-platform coverage. diff --git a/docs/development/building.md b/docs/development/building.md deleted file mode 100644 index 58a59023..00000000 --- a/docs/development/building.md +++ /dev/null @@ -1,298 +0,0 @@ -# Configuring and building Cheryl - -Use CMake 3.28 or newer, Ninja and a C++23 compiler/standard library with -`std::format`. Initialize the pinned submodules as described in -[setup](../../README.md#setup). Build-tree consumers use the same dependencies; -link Cheryl targets instead of copying include and link settings. Prebuilt consumers -use the [SDK import targets](consuming-engine.md#prebuilt-development-sdks). - -## Configuration options - -Pass settings as `-DNAME=value`. These defaults apply to a fresh root configuration; -existing build directories and IDE profiles retain their cache. Native options -are declared only when Native GLFW is selected; the RmlUi verification option is -declared only when RmlUi is selected. - -| Option | Default | Effect | -| --- | --- | --- | -| `CHERYL_BUILD_NATIVE_GLFW` | `ON` | Selects GLFW display/windows and optional input. | -| `CHERYL_BUILD_DEBUG_TERMINAL_LINUX` | Linux GNU/Clang: `ON`; elsewhere: `OFF` | Selects the optional Linux output-terminal module for root applications/test runners. | -| `CHERYL_DEBUG_TERMINAL` | `AUTO` | Links the selected terminal only in Debug; `ON` includes other configurations, where display requires `--debug-terminal`; `OFF` omits it. | -| `CHERYL_DEBUG_TERMINAL_EMULATOR` | `auto` | Linux module launcher selection: `auto`, `konsole` or `xterm`. | -| `CHERYL_BUILD_OPENGL` | `ON` | Selects the complete OpenGL backend; requires Native GLFW. | -| `CHERYL_BUILD_UI_TGUI` | `ON` | Selects TGUI independently of platform/graphics. | -| `CHERYL_BUILD_UI_RMLUI` | `OFF` | Selects RmlUi independently; can coexist with TGUI. | -| `CHERYL_RMLUI_PLACEHOLDER_FIX_VERIFIED` | `OFF` | Attests that a supplied RmlUi library includes the placeholder hit-testing correction. Set only after verification; Cheryl-owned source builds declare their correction automatically. | -| `CHERYL_BUILD_AUDIO_MINIAUDIO` | `OFF` | Selects miniaudio decoding, output and streaming. | -| `CHERYL_BUILD_DEMO` | `ON` | Adds `demo` with Native GLFW, OpenGL and native input selected. | -| `CHERYL_BUILD_FIRST_GAME` | `OFF` | Adds the maintained `first-game` walkthrough application with Native GLFW, OpenGL and native input selected. | -| `CHERYL_BUILD_DOCS` | `OFF` | Adds the optional `docs-tools` and `docs` targets; tools are selected when these targets run. | -| `CHERYL_BUILD_TESTS` | `ON` | Adds focused tests and explicitly buildable acceptance/aggregate runners. | -| `CHERYL_BUILD_ALL_TESTS` | `OFF` | Includes aggregates in the default build and CTest; requires tests. | -| `CHERYL_BUILD_ACCEPTANCE_TESTS` | `OFF` | Includes acceptance runners in the default build; requires tests. | -| `CHERYL_BUILD_CONSUMER_TESTS` | `OFF` | Adds selected owners' consumers and first-include probes independently of GoogleTest. | -| `CHERYL_BUILD_WORKER_TOPOLOGY_HWLOC` | `OFF` | Selects optional runtime topology discovery and thread binding through hwloc. | -| `CHERYL_BUILD_WORKER_TOPOLOGY_JSON` | `OFF` | Selects the versioned worker settings loader independently of hwloc. | -| `CHERYL_BUILD_SDK` | `OFF` | Adds the `cheryl-sdk` build target and `CherylSDK` install/export component for selected libraries, headers and dependencies. | -| `CHERYL_NATIVE_INPUT` | `ON` | Includes Gainput-backed input in Native GLFW. | -| `CHERYL_NATIVE_NULL_PLATFORM` | `OFF` | Configures an owned GLFW without X11/Wayland. Disable native input to omit Gainput/X11. | -| `CHERYL_SANDBOX_BUILD` | `OFF` | Deprecated shorthand defaulting native input off and the null platform on. Prefer explicit options. | -| `CHERYL_LOG_PROFILE` | `auto` | Selects `developer`, `support` or `release`; `auto` follows the build configuration. | -| `CHERYL_LOG_COMPILED_MASK` | Empty | Overrides compiled severity bits (`0`–`63`); see [logging policy](../runtime/logging.md#compile-policy). | -| `WARN` | `OFF` | Adds `-Wall`. | - -A supplied GLFW target keeps its host's settings. Null-platform OpenGL still needs -OpenGL development/link libraries and does not supply the windowed demo. -Standalone modules default their local `CHERYL_BUILD_TESTS` to `OFF`; the -[module guide](modules.md#standalone-modules) covers their composition. - -### Build configurations - -Keep different configurations in separate directories. This block builds Debug; -substitute `RelWithDebInfo`/`build/support` or `Release`/`build/release` as needed: - -```sh -( - set -e - cd "$(git rev-parse --show-toplevel)" - cmake -S . -B build/debug -G Ninja -DCMAKE_BUILD_TYPE=Debug - cmake --build build/debug --target demo --parallel -) -``` - -Non-MSVC Debug enables AddressSanitizer and UndefinedBehaviorSanitizer. Automatic -logging selects developer in Debug, support in RelWithDebInfo and release otherwise. -All translation units sharing Cheryl headers must use the same compiled logging -definitions; runtime levels cannot restore stripped severities. - -Executables and archives use the configured build root. Windows adds `.exe` to -executables. Multi-configuration generators require `--config Release` for builds -and `-C Release` for CTest, with executables normally in `Release/`. -Target and artifact declarations are centralized in -[CherylTargets.cmake](../../cmake/CherylTargets.cmake) and -[CherylOutputs.cmake](../../cmake/CherylOutputs.cmake). - -### Engine-only configuration - -This omits GLFW, Gainput, OpenGL, GLAD, UI toolkits and miniaudio. Engine still needs -its text dependencies: - -```sh -( - set -e - cd "$(git rev-parse --show-toplevel)" - cmake -S . -B build/engine-only -G Ninja -DCMAKE_BUILD_TYPE=Release \ - -DCHERYL_BUILD_NATIVE_GLFW=OFF -DCHERYL_BUILD_OPENGL=OFF \ - -DCHERYL_BUILD_DEBUG_TERMINAL_LINUX=OFF \ - -DCHERYL_BUILD_UI_TGUI=OFF -DCHERYL_BUILD_UI_RMLUI=OFF \ - -DCHERYL_BUILD_AUDIO_MINIAUDIO=OFF \ - -DCHERYL_BUILD_WORKER_TOPOLOGY_HWLOC=OFF -DCHERYL_BUILD_WORKER_TOPOLOGY_JSON=OFF \ - -DCHERYL_BUILD_DEMO=OFF -DCHERYL_BUILD_TESTS=OFF - cmake --build build/engine-only --target cengine --parallel -) -``` - -## Dependency and toolchain settings - -| Setting | Purpose | -| --- | --- | -| `GLFW_BUILD_WAYLAND=OFF` | Uses bundled GLFW's X11 backend on Linux without Wayland development tools. | -| `GAINPUT_ENABLE_HID=OFF` | Omits Gainput's HID support and hidapi fetch. Use the [current Linux joystick path](../../projects/modules/platform/native-glfw/README.md#controller-backend-limits) while HID integration remains unresolved. | -| `Python_EXECUTABLE=/path/to/python` | Selects GLAD's interpreter; Jinja2 must be installed in it. | -| `CMAKE_POLICY_VERSION_MINIMUM=3.5` | Allows pinned legacy dependency files to configure with CMake 4 when needed. | -| `CHERYL_REPOSITORY_ROOT=/path/to/cheryl-engine` | Supplies shared helpers and Engine bootstrapping for standalone modules/consumers. Root configuration sets it automatically. | -| `CHERYL_NATIVE_GLFW_SOURCE=/path/to/native-glfw` | Supplies Native GLFW to standalone OpenGL when its target is absent. | -| `CHERYL_TGUI_SOURCE`, `TGUI_DIR` | Selects a TGUI 1.13.0 source tree or exact package with custom/FreeType support. | -| `CHERYL_RMLUI_SOURCE`, `RmlUi_DIR` | Selects a RmlUi 6.3 source tree or exact package with stock FreeType and the required [placeholder correction](../../projects/modules/ui/rmlui/README.md#placeholder-dependency-contract). | -| `CHERYL_MINIAUDIO_SOURCE` | Overrides pinned miniaudio source; a supplied miniaudio target takes precedence. | -| `CHERYL_RMLUI_TEST_FONT=/path/to/font.ttf` | Supplies a real font fixture when the selected SDK's sample font is unavailable. | -| `CHERYL_HWLOC_TARGET=existing_target` | Supplies a compatible hwloc 2.5+ (2.x) target when worker discovery/binding is selected; otherwise discovery checks supported config targets and pkg-config. | - -Modules reuse compatible supplied dependency targets where supported. Their owner -guides document selection order and feature requirements. Owned source dependencies -are pinned in [.gitmodules](../../.gitmodules); initialize the recorded revisions. -The first HID-enabled configuration fetches hidapi 0.15.0 and needs network access -or a prepared FetchContent cache. UI dependency selection performs no download. - -Engine configuration registers `Cheryl::Startup`, reusing `CLI11::CLI11` or adding -the pinned `extern/cli11` source. OpenGL registers `Cheryl::OpenGL::Startup`. -These support libraries are excluded from the default build unless a consumer -links them or names their build targets; the demo links OpenGL startup. Plain -Engine/OpenGL targets do not acquire CLI11 usage requirements. See -[application startup](consuming-engine.md#command-line-startup). - -The terminal module adds a separate optional startup target and linker entry hook; -plain Engine links neither. Debug display requires a desktop and supported emulator; -missing prerequisites fall back to inherited output. Headless Debug and test -discovery create no viewer. See the -[Linux terminal guide](../../projects/modules/platform/debug-terminal-linux/README.md) -for output/report separation and lifetime policy. `ON` is a build inclusion option, -not a request to display a Release window automatically. - -## System dependencies - -Install development headers and link libraries as well as runtime libraries. -Package names vary by operating system. - -| Requirement | Selection | Notes | -| --- | --- | --- | -| Threads | Engine | Discovered through `Threads::Threads`. | -| hwloc 2.5+ (2.x) | WorkerTopology::Hwloc only | Runtime CPU/core/cache discovery and calling-thread binding; omitted configurations do not discover/download/link it. | -| FreeType, HarfBuzz, ICU uc/i18n | Engine | Font inspection/rasterization, shaping, bidi and boundaries; required with UI disabled too. | -| X11 and extensions | GLFW X11 / native Linux input | GLFW checks XRandR, Xinerama, XKB, Xcursor, XInput and X Shape headers. Gainput input also needs X11. | -| Wayland, xkbcommon, `wayland-scanner` | Bundled GLFW Wayland | Linux GLFW enables X11 and Wayland by default. Disable Wayland for an X11-only build. | -| OpenGL | OpenGL module | Headers/link libraries; native execution also needs a usable driver/display. | -| Python and Jinja2 | GLAD generation | Jinja2 must be available in CMake's selected interpreter. | -| FreeType | TGUI / RmlUi | Each toolkit uses its own font service. | -| libudev / libusb | Linux hidapi | Requirements depend on selected HID backends. | -| Konsole or xterm | Linux output terminal at runtime | A desktop is needed for display; neither is an Engine build dependency. | -| libdw, libbfd, or libdwarf/libelf | Backward, optional | Improve trace source/symbol resolution. | - -Engine source dependencies include GLM, CTTI, spdlog, Backward, STB and JSON. -Integration SDKs belong to their modules; GoogleTest is test-only. See -[consumer usage requirements](consuming-engine.md#dependencies-and-headers) and the -[module index](../../projects/modules/README.md) for dependency ownership. - -## Troubleshooting - -| Symptom | Next step | -| --- | --- | -| Selected dependency directory is empty | Initialize the pinned submodules. | -| A selected target is missing in CLion | Change the cached option and reload CMake. | -| Python cannot import `jinja2` | Install it in the selected interpreter or set `Python_EXECUTABLE`. | -| GLFW cannot find Wayland tools/libraries | Install the requirements or set `GLFW_BUILD_WAYLAND=OFF`. | -| CMake 4 rejects an old dependency policy | Set `CMAKE_POLICY_VERSION_MINIMUM=3.5`. | -| Supplied RmlUi Core lacks the placeholder-correction declaration | Use Cheryl-owned source or provide verified target/configuration metadata according to the [dependency contract](../../projects/modules/ui/rmlui/README.md#placeholder-dependency-contract). | -| Standard C++ headers cannot find `CE::Math.h` or `stdlib.h` | Inspect stale compiler discovery before editing includes; see [recovery](consuming-engine.md#standard-headers-in-an-existing-build). | - -Successful configuration/build does not establish native platform coverage. The -[test guide](testing.md) and [architecture validation](architecture-validation.md) -describe the relevant checks and prerequisites. - -Documentation has a separate [generation entry point](documentation.md) that -does not configure or compile the engine. The docs-only CMake project exposes -the same targets without graphics or native development dependencies. - -## Optional worker support - -`CHERYL_BUILD_WORKER_TOPOLOGY_HWLOC=ON` adds `cengine_worker_topology_hwloc` -(`Cheryl::WorkerTopology::Hwloc`); JSON selection adds `cengine_worker_topology_json` -(`Cheryl::WorkerTopology::Json`). Both options default off. A plain Engine consumer -needs neither support library. Selecting both also registers the explicitly -buildable `example_worker_topology` shared-pool composition example. -The [worker guide](../runtime/worker-execution.md) owns provider lifetime, placement -policies, capacity and configuration semantics. - -hwloc selection reuses an explicit or compatible existing target, then an installed -config package or pkg-config SDK. Missing/incompatible selection fails clearly; -there is no implicit SDK download/install. Cross builds require a supplied target -SDK or target-rooted pkg-config settings (`PKG_CONFIG_LIBDIR` and -`PKG_CONFIG_SYSROOT_DIR`); do not resolve host libraries accidentally. Discovery -occurs when the application creates its provider, never through `lstopo` at configure -time. Builds do not bake host CPU/cache layout into artifacts or add `-march=native`. -CPU vendor is not a CMake selection. - -## Development SDK builds - -SDK export is opt-in and requires Cheryl-owned builds of the pinned source -dependencies; imported dependency targets are rejected by this packaging path. -Ordinary source composition can still reuse supplied targets. Use a clean checkout -with submodules at their recorded revisions for reproducible archives. The packager -records the source commit and verifies recursive submodule HEADs against its gitlinks. - -Use a single-configuration Ninja build and select the modules/helpers to distribute. -On Linux, this produces the default Native GLFW/OpenGL/TGUI libraries and common -Startup support without building the demo or tests: - -```sh -( - set -e - cd "$(git rev-parse --show-toplevel)" - cmake -S . -B build/sdk -G Ninja \ - -DCMAKE_BUILD_TYPE=Release -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ - -DCMAKE_INSTALL_LIBDIR=lib -DCHERYL_BUILD_SDK=ON \ - -DCHERYL_BUILD_DEMO=OFF -DCHERYL_BUILD_TESTS=OFF \ - -DCHERYL_BUILD_DEBUG_TERMINAL_LINUX=OFF \ - -DGAINPUT_ENABLE_HID=OFF -DGLFW_BUILD_WAYLAND=OFF - cmake --build build/sdk --target cheryl-sdk --parallel - python3 .github/ci/package.py --build-dir build/sdk --output build/packages -) -``` - -The packaging tools use Python 3.12 or newer. `package.py` installs only the -`CherylSDK` component, creates a Linux `.tar.gz` or Windows `.zip`, and writes -`SHA256SUMS` plus `SDK-MANIFEST.json`. A direct -`cmake --install build/sdk --component CherylSDK --prefix /absolute/sdk/prefix` -installs the library/header/CMake component; Windows archive production additionally -copies its native dependency prefix. Use `package.py` for the complete bundle. -Archive names include the Cheryl version from -[CherylVersions.cmake](../../cmake/CherylVersions.cmake), platform/distribution, -architecture, compiler/version, configuration and commit ID. - -Windows CI builds through the pinned vcpkg manifest in [.github/ci](../../.github/ci/vcpkg.json) -with the `x64-windows` triplet. Packaging copies release headers, import libraries, -DLLs, CMake files and licenses from `build/ci/vcpkg_installed/x64-windows` into -`dependencies/`; `--native-prefix` can select another prepared prefix. Debug -dependency trees, tools and applications are omitted. Linux retains system native -dependencies. Neither package contains the demo or C/C++ implementation translation -units; public and inline/template headers remain included. The selected libraries -are already compiled. See [SDK consumption](consuming-engine.md#prebuilt-development-sdks) -for ABI, logging and runtime-library requirements. - -## GitHub CI and SDK artifacts - -[ci.yml](../../.github/workflows/ci.yml) validates pushes to `develop`/`main`, pull -requests targeting either branch and manual dispatches. Each job checks out recorded -recursive submodules. Actions use commit pins; Windows uses the manifest's pinned -vcpkg baseline. Builds use Ninja and available CPU parallelism. - -| Job | Host/compiler/configuration | Selection | -| --- | --- | --- | -| `linux-gcc-hwloc` | Ubuntu 24.04 x64, GCC 14 Release | Headless Engine, hwloc and JSON. | -| `linux-gcc-neutral` | Ubuntu 24.04 x64, GCC 14 Release | Headless Engine and JSON, hwloc omitted. | -| `linux-clang-debug` | Ubuntu 24.04 x64, Clang 18 Debug | Headless Engine, hwloc/JSON, ASan/UBSan. | -| `windows-msvc-hwloc` | Windows 2025 x64, MSVC RelWithDebInfo | Headless Engine and hwloc/JSON with vcpkg native libraries. | -| `linux-modules` | Ubuntu 24.04 x64, GCC 14 Release | Native GLFW/input, OpenGL, TGUI, RmlUi, miniaudio, hwloc/JSON and SDK. | -| `windows-modules` | Windows 2025 x64, MSVC RelWithDebInfo | The same selected modules/helpers and SDK, with bundled native dependencies. | - -[run.py](../../.github/ci/run.py) configures explicit owners/options/build targets, -runs each focused owner prefix once and executes independent consumers. Modules -jobs compile the demo for composition coverage but never launch or package it. -Linux jobs provide TrueType/CFF allocation fixtures. Missing fixtures and -platform-specific skips are reported as unaccepted coverage. Native OpenGL, native -hwloc binding acceptance, actual audio devices, desktop/controller QA and the -Debug terminal remain separate from this focused CI selection. - -Modules jobs package and verify an extracted SDK on every validation run. -[verify_sdk.py](../../.github/ci/verify_sdk.py) checks the checksum and absence of -producer paths, then builds independent consumers/header probes using only installed -Cheryl libraries. Test fixture sources/fonts come from the checkout; Cheryl and -module implementation sources are not rebuilt. Use a fresh consumer build/extraction -directory for each verification. See -[repeating SDK checks](architecture-validation.md#installed-sdk-consumers). - -Successful main-push modules jobs retain SDK candidates for three days. After all -headless and modules jobs succeed, the collection job verifies checksums and uploads -`cheryl-development-sdks-` for 90 days. Test/configuration reports are retained -for 14 days. These are workflow artifacts; the workflow creates no GitHub release, -uploads no release assets and publishes no demo package. Release publication is a -separate future decision. - -The selected hosted Ubuntu GCC/Clang and Windows MSVC producer jobs and relocated -SDK consumers are accepted. The first main-push artifact collection remains -[TR20](../testing-requests.md#tr20-automated-main-push-sdk-collection); collection is -intentionally skipped on PR, `develop` and manual runs. - -### Hosted Windows coverage limits - -Both Windows profiles leave these focused cases unaccepted: - -| Cases | Missing prerequisite and next action | -| --- | --- | -| `font_bake.real_nested_allocation_scope`, `font_bake.real_cff_alloc_failure` | The workflow supplies the bundled TrueType face but no CFF OpenType fixture on Windows. Supply `CHERYL_STB_ALLOCATION_CFF` with a suitable face, retain the TrueType fixture for nested scopes, and rerun these cases on hosted Windows. | -| `logging.config_reopen` | The test needs a temporary directory on the checkout's filesystem root so it can use a relative log path. Configure the hosted Windows temporary directory on that drive, then rerun the case. | - -The [font fixture requirements](architecture-validation.md#native-and-fixture-checks) -define suitable faces. A green job does not accept these skipped cases; successful -local MSVC/Wine checks do not replace the missing hosted execution. These limits -are separate from SDK collection and desktop/GPU/device acceptance. diff --git a/docs/development/consuming-engine.md b/docs/development/consuming-engine.md deleted file mode 100644 index ad9d0091..00000000 --- a/docs/development/consuming-engine.md +++ /dev/null @@ -1,359 +0,0 @@ -# Consuming the engine - -Start with the [first-game walkthrough](../getting-started/first-game.md) for a -maintained application covering startup, controls, asset loading, and sprite/text -submission. This page describes the source and SDK consumption contracts. - -Applications can compose source targets from a checkout or link a prebuilt -[development SDK](#prebuilt-development-sdks). `projects/engine/` owns the neutral -`Cheryl::Engine` target, backed by the build -target `cengine` and archive output name `cheryl-engine`. Applications link selected -integration targets explicitly; [the module guide](modules.md) describes ownership, -standalone composition and tests. - -```cmake -set(CHERYL_BUILD_TESTS OFF) -set(CHERYL_BUILD_DEMO OFF) -set(CHERYL_BUILD_NATIVE_GLFW OFF) -set(CHERYL_BUILD_DEBUG_TERMINAL_LINUX OFF) -set(CHERYL_BUILD_OPENGL OFF) -set(CHERYL_BUILD_UI_TGUI OFF) -set(CHERYL_BUILD_UI_RMLUI OFF) -set(CHERYL_BUILD_AUDIO_MINIAUDIO OFF) -add_subdirectory(path/to/cheryl-engine cheryl) -add_executable(application main.cpp) -target_link_libraries(application PRIVATE Cheryl::Engine) -``` - -For native graphics, enable Native GLFW, OpenGL and native input, then link -`Cheryl::Engine`, `Cheryl::NativeGLFW` and `Cheryl::OpenGL`. Select and link -`Cheryl::UI::TGUI`, `Cheryl::UI::RmlUi` or `Cheryl::Audio::Miniaudio` when needed. -For command-line startup, link `Cheryl::OpenGL::Startup`; it supplies the common -startup target and the native/graphics targets transitively. -Consumers obtain C++23, includes, logging policy and SDK requirements through -targets rather than compiling Engine sources or repeating dependency lists. - -## Prebuilt development SDKs - -Extract an SDK and select its root through `CMAKE_PREFIX_PATH` or -`Cheryl_DIR=/lib/cmake/Cheryl`. No Cheryl implementation checkout is needed -to consume it. Link the same public aliases as source consumers: - -```cmake -cmake_minimum_required(VERSION 3.28) -project(my_game LANGUAGES CXX) -find_package(Cheryl 4 CONFIG REQUIRED) -add_executable(my_game main.cpp) -target_link_libraries(my_game PRIVATE Cheryl::Engine) -# Add Cheryl::OpenGL::Startup or the selected module/support targets as needed. -``` - -For a Linux Release SDK, configure the application's directory as follows: - -```sh -( - set -e - cd /path/to/my_game - cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_PREFIX_PATH=/absolute/path/to/extracted-sdk - cmake --build build --parallel -) -``` - -The SDK contains compiled Engine, Startup and selected module/topology libraries, -their dependency libraries or headers, the crash-bootstrap object, public headers, -CMake import/version files and licenses. `.h`/`.hpp` and required `.inl`/`.ipp`/`.tpp` -files remain included. C/C++ implementation translation units, tests, artwork, -applications and the demo are excluded. There is no separate demo package. -Header-based dependencies such as miniaudio still require their public header. -The [build guide](building.md#development-sdk-builds) describes production; -`SDK-MANIFEST.json` records selected features, source/submodule revisions, OS, -architecture, compiler/version, configuration, logging profile and native versions. - -Use a package compatible with the target OS/distribution, architecture, compiler, -standard-library ABI and configuration. Windows CI SDKs use MSVC x64 RelWithDebInfo -and the DLL CRT; Linux CI SDKs use Ubuntu 24.04 x64 GCC 14 Release. A locally built -SDK's identity describes that local toolchain, not those hosted configurations. -Imported logging definitions are fixed to the producer's compiled profile/mask; -the application must not select contradictory definitions. Match MSVC runtime and -configuration rather than mixing incompatible Debug/Release C++ binaries. - -| Native dependencies | Consumer requirements | -| --- | --- | -| Linux | Install compatible FreeType, HarfBuzz and ICU development/runtime packages; hwloc when selected, the producer's Backward resolver libraries, and X11/OpenGL when selected. Pinned C++ and selected integration dependencies are already supplied. | -| Windows | The `dependencies/` prefix supplies pinned vcpkg headers, import libraries, CMake files, release DLLs and licenses. Keep it with the SDK; add its `bin/` directory to the application's DLL search path. The MSVC runtime remains a host requirement. | - -The package config discovers native libraries, checks the producer's ICU major -version and requires the hwloc 2.5+ (2.x) API when that provider is included. -Windows config prepends the bundled dependency prefix and exposes -`Cheryl_RUNTIME_DIRECTORIES` for runtime-path setup; it does not deploy DLLs beside -an application automatically. OpenGL still needs the host's graphics runtime. -SDK relocation does not establish compatibility with another ABI or distribution. - -Installed-target consumers and first-include probes cover the selected SDK -composition, including both Startup support headers, on the selected hosted Linux -and Windows toolchains. The [CI guide](building.md#github-ci-and-sdk-artifacts) -retains hosted skipped-case limits and the pending main-push artifact collection. -Desktop, GPU and physical-device coverage remain separate. The SDK does not export -the Linux Debug terminal; that module retains its source-composition path. - -## Command-line startup - -[`CE::Application::Startup`](../../projects/engine/support/startup/include/cheryl/core/engine/startup.h) -owns CLI11 parsing and validates execution, input polling and simulation timing -before invoking a selected backend factory. Link `Cheryl::Startup` for a custom -backend or `Cheryl::OpenGL::Startup` for the -[GLFW/OpenGL factory and options](../../projects/modules/graphics/opengl/README.md#command-line-startup). -The support targets expose CLI11 in their public headers; linking Engine or OpenGL -alone retains their direct context/runtime construction APIs without that public -dependency. - -Create startup on the platform owner, register application options with -`add_application_options`, then call `initialize(argc, argv)` once. Option-definition -callbacks run immediately; any values bound to the parser must remain at a stable -address through initialization. Startup itself cannot be copied or moved because -its callbacks bind its configuration. - -Help and parse/configuration errors produce a `CE::Application::StartupResult` with no context. -Return its `exit_code` when `should_start()` is false; help succeeds and invalid -arguments fail before native context construction. A successful result owns the -`CE::Runtime::Context` and the parsed `CE::Application::RuntimeOptions`. Construct the game from that -context, then call `result.make_runtime(game)` to apply its run mode, polling and -timing together. Keep the result and game alive through runtime destruction; -`make_runtime` requires an lvalue result. Backend construction failures propagate, -and `CE::Runtime::GameRuntime::run()` retains the runtime's existing initialization/cleanup contract. - -`CE::Application::StartupOptions` supplies application defaults through `execution` and -`runtime`. The common Engine options override those defaults: - -| Argument | Effect | -| --- | --- | -| `--concurrent` | Selects a separate simulation owner thread. | -| `--fixed` | Selects fixed-step simulation. | -| `--fixed-step-ms=N` | Selects fixed simulation and sets its positive step in milliseconds. | -| `--variable-interval-ms=N` | Sets variable-update spacing; zero permits unpaced updates. | -| `--max-fixed-updates=N` | Sets a positive limit on ordinary fixed updates per scheduler turn. | -| `--variable-catch-up` | Selects fixed simulation with bounded variable catch-up recovery. | -| `--recovery-prefix=N` | Sets the fixed-update prefix before recovery, at most `max_fixed_updates`. | -| `--recovery-cap-ms=N` | Caps the recovery duration; zero removes the cap. | -| `--input-unlimited` | Selects unlimited completed polls between simulation updates. | -| `--input-capacity=N` | Selects finite input polling with a positive completed-poll capacity. | -| `--input-spacing-ms=N` | Sets minimum spacing between completed polls; zero removes the delay. | -| `--worker-count=N` | Sets capacity for the lazily created Engine CPU pool, separate from the simulation thread. Owned capacity must be positive; an injected shared pool retains its own capacity. | - -Numeric values are nonnegative and must satisfy the linked -[timing](../runtime/simulation-timing.md) and -[polling](../runtime/input-state-model.md#polling-backlog-and-scheduling) contracts. -Option callbacks apply in parse order: `--input-unlimited --input-capacity=4` -selects finite polling; reversing them selects unlimited polling. Without overrides, -the default configuration is sequential variable simulation, lockstep polling and -one lazy CPU worker. - -A custom `CE::Application::StartupBackend` provides a required `create(const CE::Runtime::ExecutionOptions&)` factory, -optional Backend argument definitions and optional validation. The factory must -return an owned context; a null result fails startup. Backend selection is currently -programmatic. CLI backend selection, application-preference/build-default resolution -and runtime-loaded discovery remain unimplemented at the selection point after -parsing. [The demo option helper](../../projects/apps/demo/src/demo-options.cpp) -shows application-specific registration without duplicating the engine parser. - -## Game hooks and ownership - -Implement `CE::Game::AbstractGame` and lend it, with an `CE::Runtime::Context`, to -`CE::Runtime::GameRuntime`. Keep both alive through `CE::Runtime::GameRuntime::run()` returning or throwing. The selected -factory assembles display, window, input, presentation, renderer and resources; -`CE::Runtime::GameRuntime::run()` initializes those adapters before calling the game. - -| Hook | Owner | Responsibility | -| --- | --- | --- | -| `init()` | Platform/graphics | Bind actions and create initial resources after adapter startup. | -| `update(tick)` | Simulation | Advance game state using `delta_seconds` and the tick's input/copy of window sizes. | -| `prepare_render_frame(writer) const` | Simulation | Resolve complete ordered passes/packets; publish no borrowed game state. | -| `quiesce()` | Platform after simulation joins | Stop external producers and invalidate borrowed listeners; keep their dependencies alive. | -| `deinit()` | Platform/graphics | Release application resources after accepted work settles and frames recycle. | - -Sequential mode runs all hooks on the calling thread. Concurrent mode gives -update/frame preparation to one simulation worker; platform polling, uploads and -presentation remain on the caller. Cleanup pairs with attempted initialization, -so hooks must tolerate partial startup. The [frame boundary](../runtime/runtime-frame-boundary.md) -defines shutdown and failure ordering. - -### Minimal native application - -With Native GLFW, OpenGL and native input enabled, this opens a window and stops -when Escape is pressed. It submits no draws yet: - -```cpp -#include -#include -#include -#include -#include - -class Game final : public CE::Game::AbstractGame { - CE::Runtime::Context& engine_; - static constexpr CE::Input::Actions::Id quit_{1}; - -public: - explicit Game(CE::Runtime::Context& engine) : engine_(engine) {} - void init() override { - auto& input = engine_.input(); - static_cast(input.bindings().bind_button({input.keyboard_id(), gainput::KeyEscape}, quit_)); - } - void update(const CE::Game::TickContext& tick) override { - if (tick.input.button(quit_).pressed()) - tick.request_stop(); - } - void prepare_render_frame(CE::Rendering::FrameWriter&) const override {} - void deinit() override {} -}; - -int main(int argc, char** argv) { - auto startup = CE::Application::GLFWOpenGL::make_startup("My game", {.title = "My game"}); - auto result = startup.initialize(argc, argv); - if (!result.should_start()) - return result.exit_code; - Game game(*result.context); - auto runtime = result.make_runtime(game); - runtime.run(); - return result.exit_code; -} -``` - -Link this example to `Cheryl::OpenGL::Startup`. Pass `--concurrent` to select the -simulation worker, or use the common timing/polling options above. Applications -that construct `CE::Runtime::GameRuntime` directly can still pass `RunMode::Concurrent` as its -third argument. -Keep native binding IDs inside bootstrap; gameplay reads semantic `CE::Input::Actions::Id`s. -Never change bindings or query a live window from concurrent `update()`. - -To add presentation, follow [asset loading](../assets/asset-loading.md) and -[pipeline/material construction](../rendering/pipelines-and-materials.md#native-bootstrap-and-binding) -in `init()`, then use CPU submission helpers in frame preparation. For later -replacements, prepare owned data on [workers](../runtime/worker-execution.md), -[submit upload](../runtime/thread-dispatch.md) to platform and adopt only a ready -complete result during update. Keep the prior generation while work is pending. -The [demo implementation](../../projects/apps/demo/src/main.cpp) shows that flow. - -Use [input actions and focus](../runtime/input-state-model.md) for controls, -[simulation timing](../runtime/simulation-timing.md) for fixed/variable policies, -and [asset playback](../assets/asset-values-and-playback.md) for per-entity cursors. -The [Unicode service](../assets/text-layout.md) prepares retained text; UI adapters -keep their toolkit authoring APIs. [Audio](../runtime/audio.md) is separately -application-owned and needs its producers settled before closure. World storage, -entities, collision and mechanics belong to the game or its selected modules. - -## Dependencies and headers - -| Requirement | Target scope and reason | -| --- | --- | -| C++23, Threads, logging configuration | Engine public requirements used by templates/runtime consumers. | -| spdlog, CTTI, GLM | Engine public requirements; legacy `glm.hpp` spelling remains supported. | -| Backward::Interface | Engine public trace/resolver requirements; its own global signal-handler object is not linked. | -| STB and JSON | Engine private implementation include directories. | -| FreeType, HarfBuzz, ICU uc/i18n | Engine private font/layout implementation and link requirements; no library-native types appear in public headers. | -| CLI11 | Startup support's public parsing API; reuse `CLI11::CLI11` or initialize the pinned `extern/cli11` source. Plain Engine/OpenGL consumers do not inherit CLI11 headers or linkage. | -| GLFW | Native GLFW private implementation requirement, also used privately by the OpenGL context binding. | -| Gainput | Native GLFW public requirement when `CHERYL_NATIVE_INPUT` is enabled; its types occur in that owner's headers. | -| X11 | Native input's Linux dependency; OpenGL acceptance also uses it for explicitly selected X11 scenarios. | -| GLAD and OpenGL::GL | OpenGL public generated types and private system link requirement. | -| GoogleTest | Test-only; disabled owners do not discover their integration SDKs. | -| miniaudio | Optional audio module's private implementation/link requirement; public Engine and module audio headers expose no SDK types. | -| hwloc 2.5+ (2.x) | Optional WorkerTopology::Hwloc private SDK/final link requirement; Engine alone does not discover or link it. | - -The Engine umbrellas and granular contracts are neutral. Concrete window/input -headers belong to Native GLFW; `backends/opengl` headers belong to OpenGL. Existing -`cheryl/...` and legacy `core/...`/`assets/...` include spellings are preserved through -each target's public roots, including in the installed SDK. - -`` and `` expose no Glad types; concrete OpenGL resources are -opt-in through ``. Portable asset values and primitives live in -`assets/types/`, with shared 2D bases under `assets/types/2d/base/`. CPU geometry -builders live in `assets/geometry/`, parsed grids/clips/manifests in -`assets/definitions/`, and neutral upload/resource interfaces in `assets/resources/`. -`core/rendering/` owns renderer contracts, frame submissions and resolved packets. -Common submission uses public parameter keys; the -[backend mapping](../rendering/pipelines-and-materials.md#native-bootstrap-and-binding) -owns native uniform names and graphics types. - -## Crash and exception traces - -`Cheryl::Engine` delivers the automatic crash bootstrap. -Final consumers receive its object directly, so a static linker cannot omit an -unreferenced archive initializer. Installation follows the bootstrap object's -`NDEBUG` scope in the engine build: Debug-style builds install -`backward::SignalHandling`; `NDEBUG` builds do not. -Exception and explicit stack capture remain available through the existing bounded -capture/fallback implementation in all builds. The legacy global trace resolver and -`Cheryl::SignalHandlers` target remain available; the demo needs only the selected -engine/module targets. `NDEBUG` controls signal bootstrap installation, not explicit -trace capture. The legacy `ST_ON_SIGNALS` definition exposes the `sh` declaration -through `core/logging.h`; it does not change installation scope. See the -[signal procedure](architecture-validation.md#manual-acceptance-drivers). - -## Independent consumers and header probes - -[Engine consumer](../../projects/engine/tests/consumer/CMakeLists.txt) links only -Engine and exercises multiple translation units, events, workers and CPU resources. -Its first-include probes include the neutral umbrellas and clipping contract and reject GL/GLFW -header leakage. Standalone bootstrapping selects Engine only in a local scope. - -[Native GLFW consumer](../../projects/modules/platform/native-glfw/tests/consumer/CMakeLists.txt) -and [OpenGL consumer](../../projects/modules/graphics/opengl/tests/consumer/CMakeLists.txt) -link their actual module with first-include probes. They -reference real implementation symbols without requiring a display at execution. -`CHERYL_BUILD_CONSUMER_TESTS=ON` adds consumers for the selected root assembly. - -The Engine-only consumer entry point is: - -```sh -( - set -e - cd "$(git rev-parse --show-toplevel)" - cmake -S projects/engine/tests/consumer -B build-consumer-engine -G Ninja \ - -DCMAKE_BUILD_TYPE=Release -DCHERYL_REPOSITORY_ROOT="$PWD" - cmake --build build-consumer-engine --target consumer-cengine --parallel - ./build-consumer-engine/cheryl-consumer -) -``` - -Standalone consumer entry points require -`CHERYL_REPOSITORY_ROOT=/path/to/cheryl-engine` for shared helpers and bootstrapping. -Engine, Native GLFW and OpenGL consumer bootstraps suppress both UI selections in -a local variable scope, including inherited `ON` choices, without changing the -enclosing configuration's cache. -Normal Native GLFW consumption selects no Cheryl OpenGL/GLAD dependency; the -OpenGL consumer selects both integration owners. CMake 4 hosts may need -`CMAKE_POLICY_VERSION_MINIMUM=3.5` for pinned legacy dependency projects. Standalone -module entry points and explicit null-platform selection are documented separately -in the module guide. [Architecture validation](architecture-validation.md) describes -independent graph, consumer and runner checks and their coverage limits. - -## Optional output terminal - -For Linux GNU/Clang consumers, explicitly link `Cheryl::DebugTerminal::Linux` (or -the root's selected `Cheryl::DebugTerminal`) to choose the process entry attachment. -Plain Engine and command-line Startup do not select it. `AUTO` includes actual -Debug only; `ON` permits a non-Debug consumer to display it with `--debug-terminal`. -See the [module guide](../../projects/modules/platform/debug-terminal-linux/README.md) -for argument consumption, fallback and independent viewer lifetime. - -The small `Cheryl::DebugTerminal::Startup` support target accepts an implementation -callback; with no callback it calls the application unchanged. Native capture, -viewer launch and process-wide shutdown live entirely in the selected module. - -## Standard headers in an existing build - -An existing CMake/CLion profile can retain failed C++ compiler ABI discovery. Check -whether generated `CMakeCXXCompiler.cmake` records -`CMAKE_CXX_ABI_COMPILED FALSE` and empty implicit include directories. This can turn -the X11 dependency's `/usr/include` into an explicit `-isystem` argument; -GCC can then fail to resolve `CE::Math.h` or `stdlib.h` through `#include_next` in -C++ standard headers. - -Refresh compiler discovery and regenerate the build files while preserving the -profile/toolchain settings. CMake normally filters its -[detected implicit include directories](https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_IMPLICIT_INCLUDE_DIRECTORIES.html) -from explicit compiler arguments. Reload CMake in CLion after repairing an existing -build externally. Preserve intended profile/toolchain/cache settings when resetting -generated discovery metadata or creating a fresh build directory. diff --git a/docs/development/documentation.md b/docs/development/documentation.md deleted file mode 100644 index 3f15c78f..00000000 --- a/docs/development/documentation.md +++ /dev/null @@ -1,302 +0,0 @@ -# Generating and publishing documentation - -The manual and C++ reference share one generation command. Doxygen extracts the -selected public headers to XML; Breathe and Exhale turn that XML into Sphinx pages, -and MyST includes the Markdown guides. Documentation generation does not configure -or compile the engine. - -## Generate locally - -Use a Git checkout and Python 3.11 or newer with the standard `venv` module. You -do not need to install Sphinx, Doxygen or pip manually. The first fallback setup -needs network access to PyPI and the official Doxygen release assets, and a writable -user cache. Use `python3` in the commands below if that is your interpreter's name. -Run the shell blocks from any directory inside the checkout; the subshell locates -the repository root and preserves your starting directory. - -```sh -( - set -e - cd "$(git rev-parse --show-toplevel)" - python docs/generation/build.py -) -``` - -Open `docs/generation/generated/html/index.html`. The output directory also -contains Doxygen XML, Sphinx doctrees and `sphinx-warnings.log`. -`--output /path/to/output` selects another -output root outside the authored documentation, or another directory beneath -`docs/generation/generated/`; its `api/` and `pycache/` directories are reserved. -The generated tree is ignored by Git. Edit the declaring -headers or authored guides, then regenerate. Generation is serialized within a -checkout because all output profiles share the generated API source directory. - -The generation scripts, Sphinx configuration, extensions, styles, templates and -dependency manifests live under `docs/generation/`. Sphinx reads the authored -pages from `docs/` using that separate configuration directory. Generated API -sources live under `docs/generation/generated/api/`; Python bytecode defaults to -`docs/generation/generated/pycache/`. The other generation files are excluded from -the published manual's source discovery. - -The generator treats Sphinx warnings and unresolved references as errors, then -checks the public-header inventory, private-member exclusion, local links and -fragments, case-insensitive filename collisions, and source objects at the checked -revision. Successful extraction does not establish that every declaration has -useful prose, examples or a complete contract. Review public comments and the -[API index](../api/index.md) separately. - -Ordinary inline code in the manual and API prose links automatically when its name -identifies one published declaration. This includes qualified names, unique short -names, template types, calls such as `CE::Text::Rendering::prepare(shaped, raster_options)`, -and qualified member assignments. The displayed code stays unchanged. Call -arguments are expressions, so they do not select an overload: ambiguous names, -unknown names and private implementation details stay plain code. Code blocks, -declaration signatures and existing links are left alone. Explicit C++ roles remain -available when a specific overload is needed. The linker builds one lookup from -Sphinx's declaration inventory, then checks inline literals without reparsing -headers or XML for each reference. -The build log reports its indexing and linking time for serial Sphinx builds. - -Derived pages link to the declaring base for inherited interfaces. Public nested -types have their own pages rather than repeated declarations in the outer class. -The reference adds Doxygen's `final`, `[[nodiscard]]` and template constraints as -declaration notes, and includes concept definitions. Each declaration page links -to its header at the recorded source revision. Consult that header for the full C++ -declaration. Existing Mermaid fences are displayed as plain text until a diagram -renderer is selected; they are not rendered diagrams. - -## Tool selection and reuse - -[bootstrap.py](../generation/bootstrap.py) prefers a compatible installed -Doxygen and the Python packages in the invoking interpreter. Newer versions are -allowed for local work. Python selection checks the complete installed dependency -closure, version constraints, Python requirements and imports, rather than only -the five direct package versions. - -| Tool | Minimum installed version | -| --- | --- | -| Doxygen | 1.18.0 | -| Sphinx | 8.2.3 | -| Breathe | 4.36.0 | -| Exhale | 0.3.7 | -| MyST Parser | 5.1.0 | -| Sphinx RTD Theme | 3.1.0 | - -If installed tools do not qualify, bootstrap selects the exact versions in -[requirements.txt](../generation/requirements.txt) and -[native-tools.json](../generation/native-tools.json). Python fallback uses an -isolated virtual environment, a pinned pip wheel and hash-checked binary wheels; -it does not install into the invoking interpreter. Doxygen fallback uses a -hash-checked official archive. A healthy cached environment is validated and -reused without running an installer. - -The default shared cache is `cheryl-engine/docs-tools` beneath the platform's -user cache: `$XDG_CACHE_HOME` or `~/.cache` on Linux, `~/Library/Caches` on macOS, -and `%LOCALAPPDATA%` on Windows. Python cache identity includes the interpreter, -platform and requirements hash; native cache identity includes the archive hash. -Locks and readiness markers prevent concurrent setup from reusing a partial -installation. An interrupted setup is repaired on the next invocation. - -Pinned Doxygen archives are provided for Linux x86_64, Windows x86_64, and macOS -x86_64/arm64. Other hosts need a compatible installed Doxygen; Python fallback -also requires matching binary wheels. The pinned Python lock has wheel metadata -coverage for CPython 3.11–3.14 on these platforms. Linux fallback has been exercised; -Windows and macOS native setup still require execution acceptance. - -`status` reports tool readiness without installing anything. `setup` prepares tools -without generating documentation. Use `exec --force-pinned` for reproducible -generation, including CI and Read the Docs: - -```sh -( - set -e - cd "$(git rev-parse --show-toplevel)" - python docs/generation/bootstrap.py exec --force-pinned -- python docs/generation/build.py -) -``` - -Set `CHERYL_DOCS_CACHE_DIR` or pass bootstrap `--cache-dir /path/to/cache` to choose -another shared cache. To select another interpreter, invoke bootstrap with that -interpreter. `build.py --doxygen /path/to/doxygen` supplies an explicit executable, -which must meet the minimum and takes precedence over automatic native tool -selection. Keep local overrides out of publication configuration. - -## CMake entry points - -The docs-only project needs CMake 3.28 or newer and Ninja in addition to Python. -It enables no compiler languages and does not discover engine dependencies: - -```sh -( - set -e - cd "$(git rev-parse --show-toplevel)" - cmake -S docs/generation -B docs/generation/generated/cmake -G Ninja - cmake --build docs/generation/generated/cmake --target docs -) -``` - -This writes `docs/generation/generated/html/index.html`. The optional `docs-tools` -target prepares tools; the `docs` target also selects them, so running `docs-tools` -first is unnecessary. Neither target belongs to the default build. - -For an engine build, configure `CHERYL_BUILD_DOCS=ON` and explicitly build `docs`. -The root project still has its usual compiler and dependency requirements; use the -[building guide](building.md) for that configuration. Both CMake entry points expose -`CHERYL_DOCS_PYTHON`, `CHERYL_DOCS_CACHE_DIR` and `CHERYL_DOCS_FORCE_PINNED`: - -```sh -( - set -e - cd "$(git rev-parse --show-toplevel)" - cmake -S docs/generation -B docs/generation/generated/cmake -G Ninja \ - -DCHERYL_DOCS_PYTHON=/path/to/python \ - -DCHERYL_DOCS_CACHE_DIR=/path/to/shared-cache \ - -DCHERYL_DOCS_FORCE_PINNED=ON - cmake --build docs/generation/generated/cmake --target docs -) -``` - -## Refreshing the pinned tools - -Treat a tool upgrade as a coherent change to the fallback environment, rather than -changing one direct version and accepting whatever an installer resolves: - -1. Resolve the full dependency closure from official PyPI release metadata, - including platform and Python markers. Pin every required package and the - installer; include SHA-256 hashes for the accepted wheel variants. Keep binary - wheels required, and verify Python requirements and wheel availability across - the supported Python/platform combinations. -2. Update `native-tools.json` when changing Doxygen or the pinned pip wheel. Obtain - archive URLs and digests from the [official Doxygen releases](https://github.com/doxygen/doxygen/releases) - and [PyPI](https://pypi.org/), and keep platform mappings explicit. Update the - bootstrap minima only when the supported local baseline changes; fallback pins - and minimum installed versions serve different purposes. -3. With execution authorized, generate using `--force-pinned`, check the output, - and confirm warm-cache reuse. Review API signatures and public/private scope - when changing Doxygen, Breathe or Exhale. A new requirements hash selects a new - cache identity; old caches do not need to be overwritten. - -## Publishing contract - -The publishing configuration is -[.readthedocs.yaml](../../.readthedocs.yaml), -[documentation.yml](../../.github/workflows/documentation.yml) and -[sync-docs-stable.py](../../.github/scripts/sync-docs-stable.py). Hosted publication -still requires the account and repository setup below. Both `develop` and `main` -must contain the engine headers and documentation implementation before their -respective sites can build. - -GitHub Actions generates and checks HTML for pull requests, pushes to `develop` -and `main`, `v*` tags, and manual runs. It retains checked HTML and build diagnostics -as downloadable artifacts for 30 days. Read the Docs independently clones the -selected Git ref, runs the same pinned generator, and hosts the resulting HTML. -Actions artifacts are not the website's source and do not need an upload token for -Read the Docs. Engine compilation belongs to the existing code CI; the documentation -job does not compile examples. - -The Read the Docs configuration uses Ubuntu 24.04 and Python 3.13, with one custom -`build.jobs.build.html` command: - -```yaml -version: 2 -build: - os: ubuntu-24.04 - tools: - python: "3.13" - jobs: - build: - html: - - python docs/generation/bootstrap.py exec --force-pinned -- python docs/generation/build.py --output "$READTHEDOCS_OUTPUT" -``` - -Keep `sphinx`, `mkdocs` and a separate Python installation step out of this custom -configuration: bootstrap owns setup and generation. Read the Docs publishes the -`html/` directory beneath `$READTHEDOCS_OUTPUT`. Its -[custom build documentation](https://docs.readthedocs.com/platform/stable/build-customization.html) -and [configuration reference](https://docs.readthedocs.com/platform/stable/config-file/v2.html#build-jobs) -describe this contract. PDF and other builders are outside the current configuration. - -### Version policy - -| Website version | Git source | Policy | -| --- | --- | --- | -| `latest` | `develop` | Current development documentation. | -| `stable` | Explicit `stable` branch | Fast-forward mirror of a checked commit on `main`. | -| Release version, such as `vX.Y.Z` | Matching release tag | Historical documentation, activated individually or by an agreed tag rule. | - -Set the Read the Docs default branch to `develop`. An explicit Git branch named -`stable` supplies the stable version instead of Read the Docs automatically choosing -the greatest stable semantic version tag. Do not create a conflicting `stable` tag. -Activate desired historical tags and leave them visible for the version selector; -tags predating the documentation tooling cannot generate a site without a separate -backport policy. See [Read the Docs versions](https://docs.readthedocs.com/platform/stable/versions.html). - -The stable sync job is disabled unless the repository variable -`CHERYL_DOCS_PUBLISH_STABLE` equals the literal string `true`. Enable it only after -approval, the documentation merge into `main`, and account setup. The job must run -only for canonical-repository pushes to `main`, after documentation checks succeed; -it must never check out pull-request code with write credentials. Serialize stable -updates and compare the checked revision with the live `main` ref before updating. -Create `stable` if absent, otherwise fast-forward it without force. Reject divergence -and respect repository rules; do not delete, reset or force-update the branch to -bypass a rejection. GitHub's [ref API](https://docs.github.com/en/rest/git/refs#update-a-reference) -supports the required non-forced update. - -Read the Docs builds `latest` directly from `develop`; that build is independent of -the Actions result. The stable mirror advances only after the documentation job -succeeds. Branch protection and release policy determine which engine checks must -pass before a change reaches `main`. - -### First account and site setup - -1. Merge the documentation configuration into `develop`. Create an account at - [Read the Docs Community](https://app.readthedocs.org/), install its GitHub App - with access to `cppcooper/cheryl-engine`, and import that repository. Importing - requires repository administration access. The App subscribes to the required - events, so no additional manual webhook is needed. If the project was imported - manually, connect the repository in project settings. See the - [Git integration instructions](https://docs.readthedocs.com/platform/stable/reference/git-integration.html#adding-a-project-from-a-repository). -2. Record the actual project slug and public URL returned by Read the Docs; do not - assume the slug from the repository name. Set its default branch to `develop`, - activate `latest`, and check that version using the publication checks below. -3. Merge the approved release into `main`. Enable `CHERYL_DOCS_PUBLISH_STABLE=true` - only when stable publication is approved. Enabling the variable does not - trigger a workflow, and manual dispatch runs only the documentation checks. - The next qualifying `main` push creates or advances the `stable` branch; let - Read the Docs sync versions, then activate `stable`. Trigger a build to refresh - versions if it has not appeared yet. -4. After a successful stable build and revision check, choose `stable` as the - project's default website version. Activate existing release tags manually; - a future-tag activation rule does not replace that initial selection. - -### Publication verification and recovery - -Every full build writes `html/revision.json`. Its -`source_revision` is the full checked Git commit, `source_ref` identifies the build -ref, and `dirty` records whether the source checkout differs from that commit. -The marker also records selected tool versions and lock/manifest hashes, without -machine-specific executable or cache paths. Source links must use the same full -commit; an overridden revision must match Git `HEAD`. - -For the first site and each publication investigation, fetch `revision.json` from -the actual version URL, for example `/revision.json`. Compare -`source_revision` exactly with the expected commit and the Read the Docs build's -reported commit; a successful build status or plausible page content is insufficient. -For `stable`, also compare the Git `stable` and intended checked `main` revisions. -Publication requires a clean source marker, successful internal links and fragments, -and Git source links resolving to objects at that commit. - -First-site acceptance also includes opening the manual and API entry points, -following an inherited base link and a declaring-header link, checking navigation -and search, and switching between `latest` and `stable` once both are active. -Check historical-version selection when a release tag containing the documentation -tooling is available; do not count that coverage before such a tag exists. Confirm -the GitHub App triggers the expected branch rebuilds. Local HTML checks do not -establish that the hosted version selector, search or webhook delivery works. - -To pause stable updates, set `CHERYL_DOCS_PUBLISH_STABLE` to `false`. Correct a bad -publication with a new reviewed commit on `main`, including a revert when restoring -earlier content, then let the normal fast-forward path rebuild `stable`. Do not move -the stable branch backward. If a ref diverges or repository rules reject an update, -inspect the refs and resolve the cause before retrying; do not force the mirror. -After recovery, repeat the exact revision and page checks against the public URL. diff --git a/docs/development/modules.md b/docs/development/modules.md deleted file mode 100644 index c89425ce..00000000 --- a/docs/development/modules.md +++ /dev/null @@ -1,182 +0,0 @@ -# Engine and integration modules - -Cheryl uses one shared `Cheryl::Engine` and independently selected integration -libraries. Each owner declares its sources, public includes, dependencies and tests. -The root selects the assembly. The [module index](../../projects/modules/README.md) -maps owners to Engine contracts and links their APIs; [build options](building.md#configuration-options) -control selection. - -Engine has no reverse dependency on an integration. Logging, memory, workers, -events, assets and utilities remain within the one Engine library. Platform owners -implement display/window/input contracts, graphics owners implement rendering and -resources, UI owners consume neutral contracts, and audio owners implement playback. -The optional Linux Debug terminal owns process output/viewer behavior outside the -runtime adapter graph. Its Engine support attachment accepts a module callback; -an absent implementation leaves application startup unchanged. - -Command-line bootstrap uses the separate `Cheryl::Startup` and -`Cheryl::OpenGL::Startup` support targets. They add CLI11 and backend argument -definitions to applications that select them; Engine and OpenGL do not depend -back on those targets. See [startup composition](consuming-engine.md#command-line-startup). - -Optional Engine helpers can use `projects/engine/support/` without becoming runtime -platform modules. Preserve the same dependency direction: selected support targets -consume Engine; Engine does not acquire their SDKs or initialization as a reverse -dependency. `Cheryl::WorkerTopology::Hwloc` and `Cheryl::WorkerTopology::Json` -use that boundary for independently selected discovery/binding and configuration -helpers. [Provider composition](../runtime/worker-execution.md#topology-providers-and-capacity) -is application-owned; neutral workers and scheduling remain in the one Engine library. - -```mermaid -flowchart LR - Application --> Engine - Application --> NativeGLFW - Application --> OpenGL - OpenGL --> NativeGLFW - OpenGL --> Engine - NativeGLFW --> Engine - Application --> UI[Selected UI adapter] - UI --> Engine - Application --> Audio[Selected audio backend] - Audio --> Engine -``` - -## Module boundary criteria - -A separate module is justified when it gives an application a concrete choice or -isolation benefit: omitting a dependency, selecting an alternative implementation, -or testing an external integration independently. A directory, internal interface -or small caller count does not by itself justify a library boundary. Cheryl keeps -one shared `Cheryl::Engine`. - -Keep the selected Native GLFW, whole OpenGL and independent TGUI/RmlUi owners -cohesive unless a consumer demonstrates a benefit from another boundary. Native -input depends on a live window; dividing display and input splits that coupled -lifetime. OpenGL's context interface permits another window integration within its -owner and does not itself justify context/bridge libraries. - -Before introducing another library target, record: - -1. the concrete omit/replace/test benefit; -2. the engine contract the owner implements; -3. dependencies that become optional or isolated; -4. lifetime/dependency direction and how cycles are avoided; -5. test ownership and the smallest independent consumer proving the boundary; -6. compatibility consequences for current consumers. - -If those points do not identify a real benefit, keep the code inside the existing -owner. Logging, memory, workers, events, general utilities and similar internal -facilities remain engine organization unless a consumer demonstrates otherwise. -Evaluate architectural interfaces by their contract rather than deleting them -because they have few callers. - -## Select an assembly - -The root defaults to Native GLFW, OpenGL, TGUI and the demo. RmlUi and miniaudio -are optional and default to `OFF`. UI and audio selection is independent of -platform/graphics selection. OpenGL requires Native GLFW; the demo also requires -native input. Disable demo and every module option for Engine alone. -On Linux GNU/Clang this includes `CHERYL_BUILD_DEBUG_TERMINAL_LINUX=OFF`. -The root automatically links the selected terminal interface to its demo/shared -test entry point; external applications select it explicitly. Its configuration -policy and standalone probes are in the -[owner guide](../../projects/modules/platform/debug-terminal-linux/README.md). - -Applications link public targets, for example: - -```cmake -target_link_libraries(game PRIVATE Cheryl::Engine Cheryl::NativeGLFW Cheryl::OpenGL) -``` - -Linking Engine alone supplies neutral contracts, not concrete native/graphics -implementations. Targets propagate public include roots, C++23, logging policy and -dependency requirements; raw archive consumers must supply their complete link -closure. Engine umbrellas remain neutral. Include concrete classes explicitly -through their owner's headers, such as `cheryl/backends/native-glfw.h`. - -`CHERYL_NATIVE_INPUT=OFF` omits Gainput and the default-input factory overload. -`CHERYL_NATIVE_NULL_PLATFORM=ON` configures an owned GLFW without X11/Wayland; -a supplied GLFW target keeps its host settings. Prefer these explicit options to -the deprecated sandbox shorthand. The [build guide](building.md) owns their -configuration and dependency details. - -Selection preserves owner lifetimes: input detaches while its window is live, -resources retire before context destruction, and runtime shutdown settles workers -and recycles frames before game/adapter cleanup. - -## Standalone modules - -Standalone entry points require an absolute `CHERYL_REPOSITORY_ROOT` for shared -helpers and Engine bootstrapping. Each owner reuses a supplied `Cheryl::Engine` -or adds Engine from that root. OpenGL also reuses `Cheryl::NativeGLFW` or requires -`CHERYL_NATIVE_GLFW_SOURCE`. Bootstrapping suppresses other owners, demo and Engine -tests in a local variable scope, without rewriting a host's cache. - -This builds only the OpenGL owner and its dependencies: - -```sh -( - set -e - cd "$(git rev-parse --show-toplevel)" - cmake -S projects/modules/graphics/opengl -B build/opengl-module -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCHERYL_REPOSITORY_ROOT="$PWD" \ - -DCHERYL_NATIVE_GLFW_SOURCE="$PWD/projects/modules/platform/native-glfw" - cmake --build build/opengl-module --target module_opengl --parallel -) -``` - -Other module directories are standalone source entry points too. Module-local -checks use `CHERYL_BUILD_TESTS=ON` without enabling Engine's broader suites. -Hosts own their root `enable_testing()` policy. Independent consumers use the -owner's `tests/consumer/` entry point. Prebuilt selected owners are also available -through the [installed SDK](consuming-engine.md#prebuilt-development-sdks), which -uses the same public aliases and its producer's compiled configuration. - -## Add a module - -Resolve the boundary criteria above before adding a target. Then: - -1. Create an owner under `projects/modules///` with `include/`, `src/` - and `tests/`. Keep public contracts under `include/cheryl/`; implementation - helpers and SDK details stay private where the API permits it. -2. Copy [Module.cmake.in](../../cmake/templates/Module.cmake.in) into the owner's - `CMakeLists.txt`. Replace `MODULE_NAME`, the uppercase `MODULE_ID` and public - `MODULE_ALIAS`. `cheryl_prepare_module()` reuses or bootstraps Engine; pass - `REQUIRES_NATIVE` only when Native GLFW is a real prerequisite. -3. Declare shared metadata using the same ID in the files below. Add SDK discovery - inside the selected owner, reusing compatible supplied targets before owned - sources. Publish required header dependencies with `PUBLIC`; keep private - implementation dependencies `PRIVATE`. Do not rewrite supplied targets/cache. -4. Declare a root selection with `cheryl_option(name description default_value)` - in [CherylOptions.cmake](../../cmake/CherylOptions.cmake), and conditionally add - the owner in the root [CMakeLists.txt](../../CMakeLists.txt). No other module - should select this owner implicitly. -5. Add owner-local behavior suites and an independent consumer with first-include - probes. Keep cross-owner cases in `projects/tests/`. Document the public alias, - dependency selection, lifetime/thread requirements and capability limits in the - owner's README, and add it to the module index. - -| Metadata file | Module declaration | -| --- | --- | -| [CherylVersions.cmake](../../cmake/CherylVersions.cmake) | `VERSION_PROJECT_MODULE_` | -| [CherylTargets.cmake](../../cmake/CherylTargets.cmake) | `TARGET_LIB_MODULE_` and test target identities | -| [CherylOutputs.cmake](../../cmake/CherylOutputs.cmake) | `OUTPUT_LIB_MODULE_` and runner artifact names | -| [CherylLinkage.cmake](../../cmake/CherylLinkage.cmake) | Scoped `LINKAGE_PUBLIC_*` / `LINKAGE_PRIVATE_*` requirements | - -Declaration files reload metadata into the caller's scope; helper definitions are -guarded. Load helpers before declaring a standalone project so versions and target -names are available. The template collects only its owner's `src/*.cpp` with -`CONFIGURE_DEPENDS`, making source additions/removals trigger regeneration. Keep -conditional implementations, generated sources and tests in their own selections. - -[CherylTests.cmake](../../cmake/CherylTests.cmake) creates an owner aggregate with -`cheryl_add_test_aggregate(... OWNER)`; set `CHERYL_TEST_AGGREGATE` before adding -focused suites through `cheryl_add_test_suite`. Suites compile cases once into -object targets reused by focused, owner and assembly runners. Keep test hooks on -case targets so applications do not inherit them. [Test selection](testing.md) -covers runner options; [architecture validation](architecture-validation.md) -covers isolated graphs, supplied-target reuse and native acceptance. - -UI-specific font, alpha, input and publication requirements are in -[Writing a UI adapter](ui-adapters.md). diff --git a/docs/external-work/rmlui-placeholder-issue.md b/docs/external-work/rmlui-placeholder-issue.md deleted file mode 100644 index 8449fa71..00000000 --- a/docs/external-work/rmlui-placeholder-issue.md +++ /dev/null @@ -1,249 +0,0 @@ -# RmlUi placeholder hit-testing: issue draft and investigation guide - -This is a private, AI-authored study document, checked against the source and RmlUi's AI policy on 2026-10-08. The issue-shaped section below preserves the report and diagnosis; it is **not a body to paste into RmlUi's tracker**. Their [AI policy](https://github.com/mikke89/RmlUi/blob/master/AI_POLICY.md) requires human-authored GitHub communication. The guide explains how to investigate this yourself and write your own submission. - -No upstream issue or PR has been opened. No build, test, debugger session, or independent crash reproduction was run for this analysis. The proposed fix and testing instructions have not been validated by execution. - -## Issue-style study draft - -### Title: Text input placeholder hit-testing uses display lengths with an empty editable value - -Clicking an empty text input while its placeholder is visible crashes my application's demo. This started after replacing an editable initial `value` with the native `placeholder` attribute: - -```xml - -``` - -The dependency is RmlUi 6.3, pinned to [`ba95ffe8bfb6370efb2cdcca927eaad4710c5413`](https://github.com/mikke89/RmlUi/commit/ba95ffe8bfb6370efb2cdcca927eaad4710c5413). The application loads its font, shows the document, and updates the context before forwarding input. The placeholder is displayed as expected. The application does not implement a custom text-input widget or manually set `placeholder-shown`. - -Expected behavior: clicking focuses the field, the editable value remains empty, and typing replaces the help text with the entered value. - -Observed behavior: the application crashes when the field is clicked with the placeholder showing. There is no captured backtrace or sanitizer report yet, and this has not been reproduced in a standalone upstream sample. The following diagnosis comes from source inspection; attribution of the reported crash to this path still needs runtime confirmation. - -### Suspected failure path - -The widget distinguishes the displayed text from the editable value, but its character hit-test mixes those two representations. - -1. [`InputTypeText::OnAttributeChange`](https://github.com/mikke89/RmlUi/blob/ba95ffe8bfb6370efb2cdcca927eaad4710c5413/Source/Core/Elements/InputTypeText.cpp#L41-L64) forwards changes to the native widget. [`SetValueOrPlaceholder`](https://github.com/mikke89/RmlUi/blob/ba95ffe8bfb6370efb2cdcca927eaad4710c5413/Source/Core/Elements/WidgetTextInput.cpp#L1017-L1030) sets `placeholder-shown` and stores the placeholder in `text_element` when the value is empty. -2. [`WidgetTextInput::GetValue`](https://github.com/mikke89/RmlUi/blob/ba95ffe8bfb6370efb2cdcca927eaad4710c5413/Source/Core/Elements/WidgetTextInput.cpp#L1008-L1015) returns a static empty string while `placeholder-shown` is set. This keeps the hint out of the editable value. -3. [`FormatText`](https://github.com/mikke89/RmlUi/blob/ba95ffe8bfb6370efb2cdcca927eaad4710c5413/Source/Core/Elements/WidgetTextInput.cpp#L1285-L1296) generates line content from `text_element` and sets `line.editable_length` from that displayed content. It [retains those lines](https://github.com/mikke89/RmlUi/blob/ba95ffe8bfb6370efb2cdcca927eaad4710c5413/Source/Core/Elements/WidgetTextInput.cpp#L1403-L1409) and clamps the absolute cursor index, without converting their lengths to the empty editable value. -4. [The mousedown/drag handler](https://github.com/mikke89/RmlUi/blob/ba95ffe8bfb6370efb2cdcca927eaad4710c5413/Source/Core/Elements/WidgetTextInput.cpp#L693-L723) calls `CalculateCharacterIndex` before clamping the resulting cursor index. -5. [`CalculateCharacterIndex`](https://github.com/mikke89/RmlUi/blob/ba95ffe8bfb6370efb2cdcca927eaad4710c5413/Source/Core/Elements/WidgetTextInput.cpp#L1120-L1156) takes its starting pointer from `GetValue()` but its offset and length from the displayed line: - -```cpp -const Line& line = lines[line_index]; -const char* p_begin = GetValue().data() + line.value_offset; -const char* p_end = p_begin + line.editable_length; - -for (auto it = StringIteratorU8(p_begin, p_begin, p_end); it;) -{ - ++it; - // Width is then measured from this same range. -} -``` - -For the first line of the original ASCII hint, the mismatch is: - -| Quantity | Value while the placeholder is shown | -| --- | --- | -| Displayed text | `"Click here and type"`, 19 bytes | -| Editable value from `GetValue()` | Empty, 0 bytes | -| First line offset | 0 | -| First line editable length | 19 | -| Supplied hit-test range | Empty value's `data()` through `data() + 19` | -| Valid editable insertion position | 0 | - -That range does not describe the empty string. The [iterator constructor and increment](https://github.com/mikke89/RmlUi/blob/ba95ffe8bfb6370efb2cdcca927eaad4710c5413/Source/Core/StringUtilities.cpp#L608-L619) trust the supplied range. [Its validity check](https://github.com/mikke89/RmlUi/blob/ba95ffe8bfb6370efb2cdcca927eaad4710c5413/Include/RmlUi/Core/StringUtilities.h#L183-L184) checks pointer boundaries, and [UTF-8 seeking](https://github.com/mikke89/RmlUi/blob/ba95ffe8bfb6370efb2cdcca927eaad4710c5413/Include/RmlUi/Core/StringUtilities.h#L99-L105) does not stop at a NUL terminator. Incrementing can therefore read outside the empty value's valid character range and, depending on storage and how far iteration proceeds, outside its allocation/object. - -This establishes a range mismatch in upstream widget code. It does not establish the exact instruction that crashed the application. Small-string storage, pointer position, and the measured widths can affect whether a particular run crashes or produces a sanitizer diagnostic. - -### Possible fix - -An empty editable value has only one valid insertion position. A candidate fix is to return zero from `CalculateCharacterIndex` before forming pointers or indexing the display lines: - -```cpp -if (GetValue().empty()) - return 0; -``` - -This preserves the distinction between visible hint text and editable content. It also covers the [vertical cursor movement caller](https://github.com/mikke89/RmlUi/blob/ba95ffe8bfb6370efb2cdcca927eaad4710c5413/Source/Core/Elements/WidgetTextInput.cpp#L892-L924), rather than guarding only a mouse event. This is a proposed correction, not an executed or upstream-accepted fix. - -The exact placement needs review alongside `ideal_cursor_position_to_the_right_of_cursor` and subsequent cursor/selection updates. Placeholder alignment already has [separate cursor handling](https://github.com/mikke89/RmlUi/blob/ba95ffe8bfb6370efb2cdcca927eaad4710c5413/Source/Core/Elements/WidgetTextInput.cpp#L1451-L1495). A regression should cover those behaviors and the shared textarea widget before claiming the guard is sufficient. - -### Related reports and outstanding evidence - -[Issue #868](https://github.com/mikke89/RmlUi/issues/868) concerns adding placeholder support. [Issue #1008](https://github.com/mikke89/RmlUi/issues/1008) concerns missing-font cursor failures. Both were closed when checked; neither establishes this placeholder/value range mismatch. Searches did not find a matching open issue or PR on 2026-10-08. That is not proof there is none; repeat the search before filing. - -A complete report still needs the exact failing runtime configuration, a small unmodified-upstream reproduction, and a backtrace or sanitizer diagnostic linking the click to this range. It should also say whether the current upstream branch reproduces the problem. A fresh source check of [`master`'s hit-test method](https://github.com/mikke89/RmlUi/blob/master/Source/Core/Elements/WidgetTextInput.cpp#L1120-L1156) on 2026-10-08 found the same pointer/length construction; that is a source observation, not a runtime test. - -## Understanding the issue yourself - -### Trace the two strings before changing anything - -Read the linked methods in this order: `InputTypeText::OnAttributeChange`, `OnPlaceholderAttributeChanged`, `SetValueOrPlaceholder`, `GetValue`, `FormatText`, then the mousedown handler and `CalculateCharacterIndex`. The [attribute callbacks](https://github.com/mikke89/RmlUi/blob/ba95ffe8bfb6370efb2cdcca927eaad4710c5413/Source/Core/Elements/WidgetTextInput.cpp#L250-L281) connect the markup to the internal state. Finish with `StringIteratorU8::operator++` and `SeekForwardUTF8`, so you can explain the actual read rather than assuming a NUL ends iteration. - -The invariant required by the hit-test is: - -```text -0 <= line.value_offset -0 <= line.editable_length -line.value_offset + line.editable_length <= GetValue().size() -``` - -When the placeholder is showing, line geometry describes the hint while `GetValue().size()` is zero. Displaying a nonempty hint is correct; treating its length as an editable range into the empty string is the problem. - -The original field is a single-line example with offset zero. Wrapped textarea hints can have later lines with nonzero offsets, making the mismatch affect both pointer expressions. Check that case separately rather than assuming a successful input test proves every shared-widget path. - -### Confirm the diagnosis in an isolated upstream checkout - -Use a separate RmlUi checkout and build it as the root project, outside Cheryl's dependency setup. Start with the pinned 6.3 revision, then repeat on the current upstream revision and record its full commit ID. Preserve the shared `extern/rmlui` checkout. - -There is already a Cheryl source-build workaround described below. A demo binary compiled with that workaround cannot demonstrate the unmodified upstream failure. - -For a small upstream reproduction, load a valid font and use a document such as this. It is a suggested fixture, not a reproduction already run: - -```xml - - - Placeholder hit-test - - - - - - -``` - -RmlUi's test shell [loads the bundled LatoLatin fonts](https://github.com/mikke89/RmlUi/blob/ba95ffe8bfb6370efb2cdcca927eaad4710c5413/Samples/shell/src/Shell.cpp#L31-L50). In a standalone sample, explicitly load the font and verify success before using this family. Show the document and call `Context::Update()` before sending input. Confirm that the field has a valid font face, positive content width, and at least one formatted line. This avoids confusing the suspected bug with a missing font or an unformatted field. - -Click at the left, middle, and right of the field. Forward `ProcessMouseMove` before `ProcessMouseButtonDown(0, 0)`, then release with `ProcessMouseButtonUp(0, 0)`. Keep individual trials separate in time unless double-click behavior is the case under investigation. - -With a debugger, break in `CalculateCharacterIndex` and inspect: - -- `parent->IsPseudoClassSet("placeholder-shown")`. -- `GetValue().size()` and `text_element->GetText().size()`. -- `line_index`, `lines.size()`, `line.value_offset`, and `line.editable_length`. -- `p_begin`, `p_end`, and the iterator position as `++it` enters `SeekForwardUTF8`. - -The predicted state is a visible nonempty hint, an empty logical value, and a nonempty range built into that empty value. Capture the stack and these values at the invalid access. Do not manually dereference the bogus range just to print it. - -[The later cursor clamp](https://github.com/mikke89/RmlUi/blob/ba95ffe8bfb6370efb2cdcca927eaad4710c5413/Source/Core/Elements/WidgetTextInput.cpp#L926-L943) can prevent an invalid final insertion index; it cannot undo an earlier invalid read. If your captured failure is instead an empty `lines` access or a missing font, record that difference and revise the diagnosis. - -For AddressSanitizer, also try a much longer ASCII placeholder, for example 256 repeated characters, and click toward the right side of the field. Check the actual formatted line length. A longer range makes crossing the empty string's physical storage more likely; a short hint can read invalid logical content without crossing a sanitizer red zone. No fixed small-string capacity or guaranteed crash should be assumed. - -### Choose and review the C++ change - -Before implementing the guard, explain why zero is the right result for an empty editable value regardless of the hint's width, alignment, or mouse position. The visible hint remains available to layout and rendering. - -Then follow the return value through `SetCursorFromRelativeIndices`, `MoveCursorToCharacterBoundaries`, `UpdateCursorPosition`, and selection updates. For a later textarea display line, a relative zero can still acquire a nonzero absolute offset before clamping. Verify that the final caret and selection remain zero. - -Review whether the guard should precede or follow the reset of `ideal_cursor_position_to_the_right_of_cursor`. Cheryl's provisional guard returns at method entry, skipping that reset. Test transitions from an edited value back to the placeholder and double-click selection instead of assuming that retained state is harmless. - -Simply substituting `text_element->GetText()` for `GetValue()` would make the range describe displayed text, but would also make hit-testing operate on the hint's character positions. Explain how the chosen fix preserves the empty editable value contract. Likewise, changing line lengths during formatting may affect display alignment and other consumers; do not change their meaning without following those uses. - -Keep the upstream correction in C++ and its regression in the upstream test suite. The Cheryl CMake mechanism is downstream packaging, not the underlying fix. - -## Regression and acceptance guidance - -A useful regression exercises the public input path with a real font and completed layout. An assertion that an empty field remains empty is necessary but can pass despite the invalid read; pair behavioral checks with a pre-fix failing memory diagnostic or another demonstrable regression failure. - -The existing [`ElementFormControlInput.cpp` unit-test file](https://github.com/mikke89/RmlUi/blob/ba95ffe8bfb6370efb2cdcca927eaad4710c5413/Tests/Source/UnitTests/ElementFormControlInput.cpp) is a natural place for a focused input regression. Its `form.input.text.no_font` case tests a different condition. The [text-input benchmark](https://github.com/mikke89/RmlUi/blob/ba95ffe8bfb6370efb2cdcca927eaad4710c5413/Tests/Source/Benchmarks/WidgetTextInput.cpp) shows public mouse-event submission, selection inspection, and manual time advancement. - -Write the regression yourself using those fixtures: - -1. Obtain the context from `TestsShell::GetContext()`, load the small document, and find its native `ElementFormControlInput`. Assert successful document/input creation, show the document, and update. Then assert a valid font face, an empty value, and `placeholder-shown` before sending events. -2. Derive pointer coordinates from the field's laid-out content box. Click and release through the context API, update, and inspect `GetValue()` and `GetSelection()`. Both selection endpoints should be zero and selected text empty. -3. Submit text through `ProcessTextInput`. Assert that only the typed text becomes the value and the hint disappears. Clear the value, update, assert the hint returns, and repeat the click. -4. Include a long-placeholder case that demonstrably fails against the unchanged upstream source. Run the same case against the proposed fix. Keep the regression when it passes with the fix. -5. Close the document and use the fixture's shutdown conventions. Advance the test system's manual time between separate clicks, as the benchmark does, to avoid accidentally testing double-clicks. - -Use these cases to assess the shared helper: - -| Case | Required observation | -| --- | --- | -| Original short ASCII hint; left/middle/right clicks | Focus works; value and selection stay empty; no memory diagnostic | -| Long ASCII hint | Demonstrable failure before the change, success after it | -| Drag, Shift-click, and double-click on hint | Hint never becomes selectable or editable content | -| Type, clear, then click again | Typed value is exact; clearing restores safe placeholder behavior | -| Nonempty value and an empty field without a hint | Existing caret placement and selection behavior remain correct | -| Multibyte UTF-8 hint and entered text | Hint stays outside the value; editable text preserves character boundaries | -| Left, center, and right alignment | Caret uses the empty value's aligned insertion position | -| Textarea with wrapped/newline hints; clicks on later lines and vertical movement | No invalid range; final caret/selection stay within the empty value | - -The [test shell](https://github.com/mikke89/RmlUi/blob/ba95ffe8bfb6370efb2cdcca927eaad4710c5413/Tests/Source/Common/TestsShell.cpp#L16-L95) uses its dummy renderer by default while still loading fonts and performing layout. `RMLUI_TESTS_USE_SHELL` enables the native backend window. Start with the default test mode to isolate the widget logic, then perform native mouse QA. A headless pass does not replace checking actual focus, visible caret placement, and placeholder appearance. - -### Suggested Linux sanitizer run - -These commands are for a separate upstream checkout **after you have added the regression**. They are based on RmlUi's [Linux sanitizer workflow](https://github.com/mikke89/RmlUi/blob/ba95ffe8bfb6370efb2cdcca927eaad4710c5413/.github/workflows/build.yml#L53-L84) and [unit-test target](https://github.com/mikke89/RmlUi/blob/ba95ffe8bfb6370efb2cdcca927eaad4710c5413/Tests/Source/UnitTests/CMakeLists.txt). They have not been run here. Prerequisites include Clang, CMake with preset support, Ninja, FreeType, SDL2, SDL2_image, and OpenGL development dependencies. - -Replace the absolute checkout path. Name your new cases with the `form.input.text.placeholder` prefix, or adjust the selector. The subshell preserves the caller's working directory. - -```bash -( - set -eu - rmlui_checkout="/absolute/path/to/separate/RmlUi" - cd -- "$rmlui_checkout" - cmake -S . -B Build-placeholder-asan -G Ninja --preset dev \ - -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ - -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=OFF \ - -DRMLUI_FONT_ENGINE=freetype -DRMLUI_BACKEND=SDL_GL2 \ - -DRMLUI_SDL_VERSION_MAJOR=2 \ - -DCMAKE_C_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ - -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" \ - -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address,undefined" - cmake --build Build-placeholder-asan --target rmlui_unit_tests --parallel - env -u RMLUI_TESTS_USE_SHELL \ - ASAN_OPTIONS=detect_leaks=1:abort_on_error=1 \ - UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 \ - LSAN_OPTIONS="suppressions=$rmlui_checkout/Utilities/lsan.supp:print_suppressions=1" \ - ./Build-placeholder-asan/rmlui_unit_tests \ - --test-case="form.input.text.placeholder*" -) -``` - -First run with the upstream C++ unchanged and the new regression present. Save the failure and its stack. After applying your correction, repeat the same configuration/run; a matching existing build directory can be reused. Check that the selector actually executed your cases: an empty selection is not a passing regression. - -Then run the complete `rmlui_unit_tests` executable with the same sanitizer environment and without the `--test-case` filter. For visual QA, use an upstream sample or the test shell's native mode with a display available. Record which revision, compiler/standard library, font engine, backend, and runtime mode were exercised. - -Use the supported [upstream CI configurations](https://github.com/mikke89/RmlUi/blob/ba95ffe8bfb6370efb2cdcca927eaad4710c5413/.github/workflows/build.yml) to choose additional relevant environment coverage. Different standard libraries can expose this storage bug differently. Distinguish observed results from portability reasoning; unrun CI jobs are not evidence that the change works elsewhere. - -## Cheryl context and the provisional workaround - -The local report followed Cheryl commit `9fa1b75f8ad10602bd8861bbbb1058f4569aec3b`, which replaced an initial value with the native placeholder. The [demo field](../../assets/graphics/ui/demo.rml#L48) and [placeholder style](../../assets/graphics/ui/demo.rml#L25) show the resulting markup; the style only changes its color. - -The [demo setup and update](../../projects/apps/demo/src/rmlui-demo.cpp) load a font, obtain a native `ElementFormControlInput`, show the document, and call `update_time` before handling input. The [session](../../projects/modules/ui/rmlui/src/session.cpp) updates the native context and forwards mouse movement and button events through the normal RmlUi API. Each complete tick batch is now handled once, with application controls before individual records. These observations make a stock-widget failure plausible; they do not rule out every integration fault without a reproduction and stack. - -Cheryl commit `f0a25b2203fcf809601ed8009c8964d8b3bedfef` adds the [source-build workaround](../../projects/modules/ui/rmlui/cmake/RmlUi.cmake). The [module dependency contract](../../projects/modules/ui/rmlui/README.md#placeholder-dependency-contract) owns generated-source behavior and correction declarations. The guard changes C++ behavior; CMake arranges which source is compiled while leaving the dependency checkout unchanged. - -The helper applies only when Cheryl creates the RmlUi source target. Existing `RmlUi::Core` targets and installed packages bypass it, but the [module](../../projects/modules/ui/rmlui/CMakeLists.txt) now rejects them unless they declare `CHERYL_RMLUI_PLACEHOLDER_FIX=TRUE` or the consumer attests through `CHERYL_RMLUI_PLACEHOLDER_FIX_VERIFIED=ON`. This declaration must describe a correction already present in the supplied library; it does not install or execute the guard. Neither dependency path has been runtime-validated for this report. Do not describe the local guard as a confirmed fix or use a guarded Cheryl build as the pre-fix upstream baseline. - -## Preparing a submission under RmlUi's AI policy - -The [policy](https://github.com/mikke89/RmlUi/blob/master/AI_POLICY.md) says: - -> All *communication* on the RmlUi GitHub repository must be written by human authors. - -This includes issues, PR descriptions, and comments. Its exceptions are faithful translation and limited, attributed AI quotations with human-written context and predominantly human-authored content. Disclosure does not make an AI-generated issue body acceptable. - -For code, the policy permits smaller AI-assisted PRs subject to disclosure of extent, model, and AI-written parts. The author must understand, review, and test the code, take responsibility for it, and be certain it works in other environments. Large PRs mostly or entirely authored by AI are not accepted. Re-read the live policy before submitting. - -The following is practical preparation, rather than additional rules imposed by that policy: - -1. **Establish firsthand evidence.** Reproduce the failure yourself, capture the relevant state and stack, and determine whether it persists on current upstream. Record actual environment details and results. If attribution remains uncertain, say so. -2. **Explain the defect unaided.** Be able to identify the two strings, explain where the line length originates, show why the iterator's supplied end is wrong, and explain why clamping afterward is insufficient. Also explain why a short hint can appear safe in one environment. -3. **Review the correction yourself.** Understand every changed line and relevant caller, including the state skipped by an early return. Demonstrate pre-fix failure and post-fix success, check the neighboring text-editing behavior, and justify the environment coverage. -4. **Write your own GitHub communication.** Use your observations and understanding to author the issue, PR description, and replies. Do not paste this draft, ask AI to polish it for submission, or treat superficial edits as human authorship. If you quote a small portion, follow the policy's attribution and context requirements. -5. **Disclose AI involvement in your own words if submitting assisted code.** This analysis and the provisional Cheryl guard were AI-authored. If you use the suggested guard, identify that contribution, along with any AI-written tests or later changes. Record the actual model identity from the Codex session/settings rather than guessing; describe what you personally reviewed and tested. -6. **Keep unsupported claims out of the submission.** Include only runtime results you obtained. If you cannot yet meet the PR's understanding, testing, or environment requirements, continue investigating or open a human-authored issue with the limits stated instead of presenting the correction as accepted evidence. - -The useful handoff to maintainers is a small reproducible failure, the violated range invariant, and independently checked behavior after a narrow change. This file preserves the investigation needed to reach that point; that firsthand verification and human-authored submission remain to be done. diff --git a/docs/for-agents/README.md b/docs/for-agents/README.md new file mode 100644 index 00000000..ed8004a5 --- /dev/null +++ b/docs/for-agents/README.md @@ -0,0 +1,50 @@ +# Agent instruction library + +This directory holds focused instructions for working on Cheryl. The root +[AGENTS.md](../../AGENTS.md#instruction-index) is the canonical index: it routes +shared work, investigation, architecture, implementation, testing, Git and +documentation tasks. Start there and read the guides for the current objective. + +The [shared workflow](workflow.md) applies to every task. Focused guides own their +specific methods and rules. Reading a guide does not authorize another work mode +or an operation requiring permission. Existing user authorization persists within +its scope; use it instead of asking again. + +## Documentation and source owners + +[Documentation workflow](documentation.md) owns writing, auditing, usefulness +review and maintenance policy. [API documentation](api-documentation.md) owns +source-comment coverage, compact class declarations and extraction/render checks. +[Documentation generation](documentation-building.md) owns the agent's preparation +and artifact-verification sequence; the linked published tooling guide owns exact +commands, cache behavior and hosting procedures. + +The [documentation directory](../README.md) maps published and repository-only +material. [Published navigation](../site/index.md) owns the manual hierarchy; +subject guides and declaring source own engine contracts. Preserve the agreed +navigation and useful URLs/anchors while improving content and task handoffs. + +These Markdown guides are loaded through the root index. Their directory does +not give them automatic per-directory instruction scope. `docs/AGENTS.md` routes +actual documentation work back to the appropriate guides, including documentation +of the instruction library itself. + +## Maintain this library + +- Keep essential entry safeguards and the task-to-guide index in root `AGENTS.md`. + Put detailed rules and procedures in the focused owner. Summaries link that + owner; they do not introduce competing policy. +- Before moving a rule, account for every requirement, exception, scope boundary + and rationale that still applies. Update references to the old owner and check + that a task entering through the root index reaches the new rule. +- Prefer extending an existing guide. Add a guide when it answers a distinct + recurring task and reduces irrelevant context or repeated searches. Choose a + clear trigger, required inputs, working method, boundaries and completion evidence. +- Record durable user decisions, such as the + [fixes-develop consolidation strategy](git.md#consolidate-branches-into-fixes-develop), + in their subject owner. Keep temporary progress and transcripts out of the library. +- Verify links, headings and policy preservation. Walk representative tasks through + the index: investigation must not silently become implementation; a build must + retain its operation boundary; a merge must reach the recorded history policy. +- Expand from observed needs and recheck the benefit on later tasks. Do not + duplicate the human manual, actual tool configuration or a source inventory. diff --git a/docs/for-agents/api-documentation.md b/docs/for-agents/api-documentation.md new file mode 100644 index 00000000..8d6bdb70 --- /dev/null +++ b/docs/for-agents/api-documentation.md @@ -0,0 +1,254 @@ +# Document the generated API + +Use this guide when creating, repairing or maintaining API source comments and +their generated reference. Read the shared [documentation workflow](documentation.md) +first. Explain the public contract from the responsible declaration, implementation +and relevant tests; inspect what Doxygen, Breathe and Sphinx actually publish. + +## Define the full coverage + +Inventory the current public-header inputs, extracted entities and rendered +articles before a broad pass. The [generator](../site/generation/build.py), +[Doxyfile](../site/generation/Doxyfile.in) and +[Sphinx configuration](../site/generation/conf.py) establish publication scope. +Audit inventories provide the earlier baseline and priorities, not a permanent +list of current declarations. + +Cover every meaningful exposed entity, including members that appear only inside +a type's article: classes, structs, unions, public/protected fields and methods, +constructors, destructors, operators, overloads, enum values, aliases, templates, +concepts, constants, public macros, callbacks and authored header/namespace content. +Check inherited members and specializations. A standalone page count does not +measure this coverage. + +Review each entity's meaning, even when it needs little prose. A transparent alias +or self-contained constant may be complete with a brief explanation or an accurate +declaration and context. A policy field or lifecycle method usually needs much +more. Thorough documentation supplies all applicable meanings; repeating a name +or adding identical boilerplate to every symbol does not satisfy the request. + +Prioritize common lifecycle, resource, input, text and other heavily used APIs, +then complete the remaining exposed scope. Prioritization changes work order, +not the final denominator. Preserve the declared private/internal boundary. +Mechanical namespace/file indexes need useful navigation, not invented contracts. +Accidentally published include guards and private visibility helpers are filtering +defects; address publication instead of explaining them as consumer features. + +## Establish the contract before editing + +Read the declaration, implementation, callers and relevant tests as needed to +answer the applicable questions below. Check virtual implementations and failure +paths before promising behavior on behalf of an interface. Existing ordinary +comments can hold valuable contracts: verify and preserve their meaning when +converting them into extracted documentation. + +| Entity | Required meanings to consider | +| --- | --- | +| Type/interface | Purpose, supported use, invariants, default/empty state, ownership, copy/move behavior, synchronization and availability. Unions need active-member rules. | +| Function/method/operator | Inputs and defaults, units/ranges, result and ownership, side effects, publication timing, valid state/thread/context, failure and preserved state, and differences between overloads. | +| Constructor/destructor/reset | Acquired and borrowed resources, construction failure, empty/moved-from state, teardown prerequisites, idempotence where promised, and final release versus deferred native retirement. | +| Field | Meaning, units, coordinate/index domain, valid values, sentinel/default behavior, ownership/mutability and relationships to other fields. | +| Enum/value/flags | Purpose of the choice, each value's effect, valid combinations and unsupported/reserved values where defined. | +| Template/alias/concept | Parameter meanings, constraints, additional semantics beyond the spelling, specialization differences and relevant related types. | +| Constant/public macro | Meaning, units/domain, sentinel behavior, feature/build availability, and intended consumer or module-author use. | +| Callback/callable argument | Invocation phase/thread, synchronous or deferred execution, argument/capture lifetime, retained ownership, reentrancy, exceptions and unsubscription. | +| Header/namespace | Include purpose, required link targets/features and route to the owning guide. Document actual choices; avoid filler for a mechanical index. | + +For operations, check these details explicitly when relevant: + +- Null, empty, zero, negative, out-of-range and unknown values; whether input is + copied, moved, borrowed or retained, and how long it must remain valid. +- Return values, references, views, handles, futures and optional results; what + they own or borrow, when they become usable, and what invalidates them. +- Which thread/owner/context may call, what is synchronized, and when accepted + requests become visible. Distinguish request acceptance from applied state. +- Allocation, validation and backend failures; exception types or error results + that callers need, partial effects, retained prior output, and retry behavior. +- Mutations, callback invocation, resource-domain requirements, teardown ordering + and behavior after move/reset/shutdown. State guarantees only when supported. + +Use `@brief`, `@param`, `@tparam`, `@return`/`@retval`, `@throws`, `@pre`, `@post`, +`@note` and `@see` when they make the verified contract easier to retrieve. Omit +inapplicable sections. A parameter entry explains its meaning and restrictions, +not just its C++ type. Document `void` operations through effects and state. + +Do not invent a stronger guarantee than source or an established interface +supports. An implementation defect, ambiguous contract or unsupported expectation +is discovered work requiring resolution, not an invitation to write plausible +prose. Keep internal algorithm commentary separate from public obligations. + +## Keep class definitions compact + +The repository preference is a scannable class body with detailed contracts outside +it. Follow the existing declaration order and keep data members above methods. +Do not move definitions, change signatures/access, or refactor a class to fit docs. + +| Content | Preferred location | +| --- | --- | +| Type overview | Before the class/struct/union, outside its body. | +| Short field or enumerator meaning | After its declaration, using `///<` or `/**< ... */`. | +| Detailed member or method contract | After the complete class, in the same extracted header, using an explicit qualified Doxygen target. | +| Definition already outside its class in an extracted header | Before that definition when it attaches to the intended entity without a repeated target. | +| `.cpp` definition documentation | Only when that source is included in the extraction and cache inputs; the current pipeline excludes it. | + +Doxygen supports trailing member comments and detached structural blocks. Keep +structural commands out of trailing blocks. See the official +[comment placement guide](https://www.doxygen.nl/manual/docblocks.html). + +### Example placement + +This fictional example illustrates locations, not a Cheryl API or a complete +implementation. Adapt contracts to actual behavior; the declaration list remains +short even when the method needs a detailed explanation. + +```cpp +namespace Example { + /** Owns bytes that callers can copy into their own output storage. */ + class Buffer { + public: + std::size_t max_copy = 0; ///< Maximum bytes per copy; zero permits no copying. + + [[nodiscard]] std::size_t copy_to(std::span destination) const; + }; +} + +/** + * @fn std::size_t Example::Buffer::copy_to(std::span destination) const + * @brief Copies buffered bytes, bounded by destination size and max_copy. + * @param[out] destination Writable storage that must not overlap the buffered bytes. + * @return Number of bytes copied; zero when no bytes can be copied. + * + * The caller retains ownership of destination; this operation does not retain it. + * Bytes beyond the returned count remain unchanged. Concurrent mutation of this + * buffer requires caller synchronization. + */ +``` + +If a field needs more than a short trailing explanation, keep that explanation +short and place its additional contract outside the class, for example: + +```cpp +/** + * @var std::size_t Example::Buffer::max_copy + * @details Changing the limit affects later calls, without resizing buffered storage. + */ +``` + +Maintain one detailed contract for an entity. Avoid splitting its rules among +several detached blocks or duplicating the same prose beside the declaration, +definition and manual. A useful short brief and one detailed owner can coexist. + +### Associate detached blocks precisely + +Use `@fn` with a qualified function declaration on one line, and `@var` with the +qualified variable declaration. Match argument types, template scope and relevant +qualifiers, including leading `::` inside template arguments; update targets +with signature changes. Prefer direct attachment when a definition already lives +outside its class. The official +[command reference](https://www.doxygen.nl/manual/commands.html#cmdfn) explains +these targets and their duplication risk. + +Review constructors, conversions, overloaded operators, cv/ref-qualified methods, +constrained templates, partial specializations and inherited methods individually. +Do not create a phantom declaration or attach several overloads' contracts to one +member. Preserve real defaults, qualifiers, attributes and base types in output. + +Describe each overload's actual differences with explicit links. Avoid order-based +"the overload above" wording and automatic `@overload` prose: current extraction +sorts member documentation. Use `@copydoc`, `@copybrief` or `@copydetails` only for +the same contract; identify overloaded targets by argument types. Add actual +override differences instead of copying incompatible lifetime or failure promises. +The current parser can fail on nested template return types; a verified +`@fn auto Qualified::method(...) -> ReturnType` target can attach where the +prefix-return form does not. Inspect XML even when extraction emits no warning. +For the two ref-qualified `StartupResult::make_runtime` declarations, detached +`@fn` matching loses the ref qualifier. Their concise trailing briefs and the +complete type-level lifetime contract provide the tested compact fallback. + +See the [overload](https://www.doxygen.nl/manual/commands.html#cmdoverload) and +[copy commands](https://www.doxygen.nl/manual/commands.html#cmdcopydoc). + +### Respect extraction inputs + +Current `header_inputs()` selects public `.h`, `.hpp`, `.inl`, `.ipp` and `.tpp` +files under the configured include roots, plus the explicitly selected public +exception owner at `projects/engine/include/internals/exceptions.h`. Its historical +path does not authorize extracting other implementation headers. +`SEARCH_INCLUDES` is off and the extraction fingerprint hashes those headers. A comment only in a `.cpp` file will +neither enter the reference nor invalidate its extraction cache. + +Doxygen can associate comments at an out-of-class source definition when that +file is an input. If implementation work adopts `.cpp` placement, treat selecting +the source files, cache invalidation, public/private filtering and source inventory +as one necessary tooling change. Check cold and incremental output and prevent +internal source entities from becoming accidental public articles. Moving comments +alone is insufficient. Keep the header form until the supported path is established. + +Do not add filler file comments just to trigger extraction: the current Doxyfile +uses `EXTRACT_ALL=YES`. Write header documentation when it explains real include, +feature, linking or migration choices. Missing-documentation warnings are disabled; +an undocumented declaration can pass generation. + +## Connect symbols to use + +Give significant APIs a nearby route to the canonical manual section or maintained +example that shows their use. Link precise lifecycle/ownership rules when the +operation depends on them; retain essential preconditions locally. Link related +declarations explicitly with Doxygen references when they clarify a choice. + +Before adding guide links throughout comments, verify one complete path through +Doxygen XML, Breathe and the generated HTML. A Markdown link relative to a header +is not necessarily resolved relative to that header in the final API article. +Use a supported, checked mechanism; keep links usable in the generated reference +and versioned documentation. Avoid guessed generated filenames or a pinned web +URL that disconnects the example from the displayed source revision. + +Use the checked manual-link form, with the document name relative to `docs/site/`: + +```cpp +/** @see [Font selection](cheryl-guide:assets/text-layout#font-selection) */ +``` + +The repository-link extension resolves `cheryl-guide:` after Doxygen/Breathe to +a relative HTML link within the current documentation version. The document must +exist; normal generated-site checks verify its fragment. Use actual headings, +not guessed anchors. This form has a Doxygen-to-HTML pilot; ordinary source-relative +Markdown paths do not gain that interpretation automatically. + +## Verify a batch and finish the scope + +Before bulk comment placement changes, check representative cases: a trailing +field and enum value, a detached method and longer field contract, an overload, +a template/specialization and a virtual/inherited member. Use the actual repository +toolchain and permitted verification. Official Doxygen syntax support alone does +not prove correct association or rendering through Cheryl's full pipeline. + +For each coherent subject batch: + +1. Compare the current extracted/published entities with the source declarations. + Account for every exposed member, including nested and inherited content. +2. Verify comments against implementation, then inspect XML association, parameter + names, overload targets, qualifiers and cross references. Unmatched blocks, + phantom declarations, accidental internal exposure and lost existing prose + are failures even when some output looks correct. +3. Inspect the rendered type/member articles. Confirm that prose, parameter/return + information, specialization bases, signatures, examples and guide links are + accurate and findable. Do not compensate for a wrong generated declaration + by adding a prose disclaimer; fix the owning extraction/rendering mechanism. +4. Walk a starting-reader use question and an expert contract question from the + symbol. Require an explicit answer or useful handoff, not engine-source reading + to fill a documentation gap. +5. Run the applicable permitted checks and generation, using the regular output + described in the [tooling guide](../site/development/documentation.md). Report + exactly what was source-inspected, extracted, rendered and executed. + +Track coverage in the working context against the current entity inventory. +Account explicitly for an intentional exclusion or mechanically sufficient +declaration; do not count it as a fully documented complex contract. Keep the +historical usefulness report intact. A successful build, comment count or average +score cannot establish that every requested API entity has been covered. + +API work is complete only when the full requested exposed scope is accounted for, +applicable contracts are supported and visible, declaration identity is preserved, +and relevant links/presentation checks pass. Unresolved semantic or rendering +defects remain outstanding work at handoff. diff --git a/docs/for-agents/architecture.md b/docs/for-agents/architecture.md new file mode 100644 index 00000000..03b2ae74 --- /dev/null +++ b/docs/for-agents/architecture.md @@ -0,0 +1,127 @@ +# Architecture and design + +Use this guide to decide how a requested capability or correction should fit the +existing system. Produce a source-backed recommendation that an implementation +can follow, including unresolved decisions and acceptance limits. + +Read [shared workflow](workflow.md) for work modes, source authority, scope and +shared-tree boundaries. Use [implementation](implementation.md) after a design +has been selected, and [testing](testing.md) when defining its acceptance. +The engine's subject guides and declaring sources own its actual contracts; +this page supplies a design method rather than another engine architecture. + +## Frame the problem + +State the result the caller needs before choosing an API, class or module: + +- Who performs the operation, and what useful result should they obtain? +- What application work, failure or dependency makes the current path inadequate? +- Which requirements are fixed, and which examples merely illustrate them? +- What must remain compatible, and what is outside this decision? + +Separate the requested outcome from a proposed mechanism. An example involving a +book, HUD or loader does not by itself require an engine-owned book, HUD or loader +policy. Identify the reusable responsibility and the application choices before +deciding where either belongs. + +## Establish the current and intended contracts + +Trace the relevant public entry point through its implementation and cooperating +owners. Read the configuration and relevant tests where they establish selection, +lifetime or failure behavior. Expand investigation to resolve a consequential +uncertainty; avoid collecting an unrelated source inventory. + +Keep these distinctions explicit: + +| Evidence | Record | +| --- | --- | +| Observed implementation | What the current source and configuration actually do, including rejection and cleanup. | +| Documented intent | The requirements or rationale in the owning guide or plan; identify any mismatch with source. | +| Verified behavior | The cases actually observed, with their configuration and limits. | +| Open question | The assumption, missing evidence and consequence if it proves false. | + +Look for an existing operation that already supplies the requested responsibility. +Check its convenience layers and lifetime authority before proposing another +registry, dispatcher, fallback, ownership wrapper or application workaround. +Distinguish a confirmed bug, an absent integration and an optional convenience; +they can require different decisions. + +## Compare viable approaches + +Consider keeping the current contract, extending an existing owner, and adding a +new boundary where those are meaningful alternatives. Explain the consequences +that affect the caller and maintainer, rather than presenting an inventory of +possible abstractions. + +Compare the applicable tradeoffs: + +- Application setup and the ordinary path, including sensible existing defaults. +- Ownership, dependency direction and the ability to use a component independently. +- Lifetime and shutdown obligations, retained data and borrowed access. +- Execution owners, synchronization, publication and progress during waits. +- Failure visibility, recovery, cancellation and preservation of useful state. +- Compatibility, migration effort and the cost of maintaining multiple paths. +- Evidence and fixtures needed to establish the claimed behavior. + +Evaluate an interface by its architectural purpose and contract. Do not remove it +because it has no callers or few callers. Determine which extension, replacement, +independent consumer or supported responsibility it exists to serve. + +Recommend the approach that addresses the actual requirements, with the material +tradeoff and rationale. Do not imply that a broader redesign is necessary merely +because it would simplify the proposed local implementation. + +## Specify the boundary precisely + +Describe a concrete caller sequence and enough API shape to expose the important +decisions. A short example can reveal missing initialization, result adoption or +cleanup that a type diagram hides. Identify proposed APIs as proposed. + +| Contract question | Resolve when applicable | +| --- | --- | +| Inputs and outputs | Valid values, units, defaults, identifiers and returned ownership. | +| Ownership and lifetime | Owners, borrowed targets, retained snapshots, invalidation and destruction order. | +| Execution | Allowed callers, owner threads, synchronization and whether an operation queues, blocks or runs inline. | +| State and publication | Acceptance versus completion, visibility boundaries, ordering and replacement/coalescing behavior. | +| Failures | Synchronous exceptions, asynchronous results, cancellation, partial effects, retry and preserved state. | +| Capability limits | Observable unsupported cases and the guarantees supplied by optional integrations. | + +Trace shutdown as carefully as ordinary success. Determine which producers stop, +which accepted users must finish, who continues servicing dependencies, and when +borrowed objects or native domains can disappear. Retaining a handle does not +automatically retain every resource needed to use it. + +## Check compatibility and acceptance + +Identify affected public declarations, headers, link targets, saved values and +application sequences. Separate source compatibility, binary compatibility and +runtime behavior where they differ. Explain a required migration without silently +changing an established contract to make a new example convenient. + +For build, tooling, SDK or CI boundaries, follow +[producer-to-consumer analysis](implementation.md#trace-build-tooling-sdk-and-ci-changes). +Check the affected configurations rather than assuming one source build answers +installed, relocated or packaged consumption. + +Define acceptance in terms of observable behavior: the normal caller outcome, +material rejection/failure cases, lifecycle boundaries and relevant configurations. +Identify existing coverage that can be reused and the specific missing cases or +fixtures. Use the [testing guide](testing.md) to distinguish proposed coverage +from checks that actually ran. + +## Hand off a decision + +Give the next phase a concise account of: + +- The selected contract and why it addresses the caller's problem. +- The responsible owner, concrete call sequence and affected boundaries. +- Material alternatives rejected and the tradeoff that decided between them. +- Source evidence, assumptions and decisions that remain unresolved. +- A coherent implementation sequence and its dependency checkpoints. +- Required acceptance, observed evidence and outstanding limits. + +Keep open decisions visible. A design recommendation does not establish that the +implementation exists or that its behavior has been accepted. Use the +[shared workflow](workflow.md) for progression and handoff boundaries, and the +[documentation workflow](documentation.md) when maintaining durable design or +subject documentation is part of the task. diff --git a/docs/for-agents/documentation-building.md b/docs/for-agents/documentation-building.md new file mode 100644 index 00000000..d1beb204 --- /dev/null +++ b/docs/for-agents/documentation-building.md @@ -0,0 +1,67 @@ +# Generate and verify documentation + +Use this guide when generation, generated-page inspection or stale-output diagnosis +is part of the authorized task. Read the [shared workflow](workflow.md) and exact +[operation permissions](testing.md#verification-and-resource-use). Full generation +requires authorization; reuse authorization already given for the relevant work. + +The [published tooling guide](../site/development/documentation.md) owns commands, +cache invalidation, tool versions, CMake entry points and output selection. Use +[API documentation](api-documentation.md) for declaration association and +[documentation workflow](documentation.md) for content correctness. Generation +success alone establishes neither. + +## Locate the current inputs and output + +1. Check the current checkout, branch, Git status and affected diffs. Distinguish + authored source, extraction inputs, generated API sources and HTML. +2. Use `docs/site/` as the manual source and `docs/site/generation/` for the + generator/configuration. API inputs come from the generator's explicit inventory. +3. For normal local delivery, generate into + `docs/site/generation/generated/html/`. The entry is `index.html`. Use another + output profile only when it serves the requested verification; it does not + replace the normal local site promised to the user. +4. When diagnosing stale content, inspect the browser path and that output's + `revision.json`. Old folders, other checkouts and cached tabs can show a valid + older site. A file's existence does not establish current generation. + +## Run the relevant generation + +Use the existing pinned/cached tools through the repository wrapper. Inspect +readiness before installing or downloading anything; reuse compatible tools and +caches. Preserve active user invocations and bound agent-run parallelism. +Follow the tooling guide's normal generation, extraction-only and clean-rebuild +choices based on the changed input or observed failure. Do not bypass the owning +wrapper or change unrelated build configurations to make output appear current. + +For authorized normal local generation, from a Git working tree: + +```sh +( + set -e + cd "$(git rev-parse --show-toplevel)" + ./generate-docs.sh --jobs 4 +) +``` + +Agent invocations may use reduced process priority; user-run commands retain +normal priority and appropriate available parallelism. The `--jobs` argument +bounds Sphinx workers, not an Engine build. + +## Verify the delivered artifact + +- Require successful generation and its local link/source checks. Inspect warnings + and failures; do not present an older HTML file left behind as a successful rerun. +- Inspect representative changed page bodies. For API work, follow comments from + source through XML into visible type/member prose, correct signatures and guide + links. The presence of an identifier or anchor alone does not prove prose rendered. +- Compare `revision.json` with the intended source revision and dirty status. For a + final committed delivery, generate/check that revision and confirm the normal + output path. A dirty preview remains a preview and must be described accordingly. +- Check old bookmarks and duplicate generated roots when the user's observation + disagrees with the inspected output. Link the exact current page and explain any + identified stale copy. Preserve user outputs; remove or replace them only within + an authorized cleanup task. +- Report the actual generated entry, revision, inspected content and checks. + Keep source review, example execution and hosted publication acceptance distinct. + Local generation does not publish or verify the hosted site. diff --git a/docs/for-agents/documentation.md b/docs/for-agents/documentation.md new file mode 100644 index 00000000..0c367014 --- /dev/null +++ b/docs/for-agents/documentation.md @@ -0,0 +1,417 @@ +# Write and review useful documentation + +Help the intended reader finish a task or answer a question without guessing. +Start with a plain explanation, then provide the exact rules needed to use the +system correctly. A concise entry and detailed contracts can belong on the same +page when headings and links make each easy to find. + +Use the [shared work rules](workflow.md) for authorization and phase boundaries. +This guide owns documentation audit, authoring and maintenance policy. The +[documentation directory](../README.md) identifies existing owners; the +[tooling guide](../site/development/documentation.md) owns generation commands, +outputs, caches and tool selection. + +## Establish the task and its owner + +Before editing, inspect Git status and relevant existing changes, then identify +the reader's task, the content being changed and its authoritative owner. Choose +the appropriate operation: + +| Operation | Starting action | Required result | +| --- | --- | --- | +| Create | Search existing pages and public declarations for the answer. | Fill a distinct unanswered need in its existing owner, or justify a new page. | +| Improve | Read the whole affected unit, its entry route, relevant source and connected examples. | Repair a specific reader problem while preserving correct contracts and useful detail. | +| Audit | Define the corpus, questions and evidence before judging content. | Report supported findings and limits; follow the repository's audit/maintenance boundary. | +| Maintain | Trace the changed contract or completed work through its owners and dependents. | Reconcile current instructions, examples, links, plans and acceptance scope. | + +Keep the working scope concise: audience, intended outcome, starting scaffold, +affected owners, evidence and completion checks. Record unresolved implementation +questions separately from prose defects. A documentation task must not silently +change an API or invent behavior to make an example easier. + +### Place the answer + +- Keep published prose under `docs/site/`. Edit module references there; neighboring + module READMEs are aliases. Keep agent guidance and repository plans outside the + published source root. See the [source layout](../README.md#source-layout). +- Update the existing owner before adding another explanation of the same rule. + An example may summarize the rule at the point of use and link its full contract. +- Preserve the established navigation, category order and tutorial progression + during content repair. Add useful local headings and contextual routes. A new + page needs a distinct reader task and one navigation parent in the existing + hierarchy; secondary routes use ordinary links. +- Match the page's title, introduction and incoming labels to the question it + answers. Preserve useful paths and section anchors when consolidating content. +- Source comments own declaration-specific API contracts; subject guides own + cross-system explanations and procedures. Follow the + [API instructions](api-documentation.md) for generated reference work. + +### Establish the evidence + +Read the responsible declaration, implementation and relevant tests/configuration +before writing a current contract. Follow a convenience operation far enough to +verify the dependencies on which its promise relies. Tests provide evidence of +specific cases; source and test presence alone do not establish executable acceptance. + +Prefer the supported engine path and defaults when teaching application setup. +If an example requires application-owned plumbing, confirm that no existing engine +service supplies it and explain the actual application responsibility. A missing +integration is discovered engineering work; do not spread an unexplained workaround +across tutorials as though it were the intended architecture. + +## Layer first use and precise reference + +Start with the operation's purpose and normal supported path. Introduce a concept +when the reader first needs it. Follow with meaningful choices/customization and +the exact contracts, limits and failure rules. Give experts headings that lead +directly to those rules without rereading a lesson. + +Keep ownership, lifetime, thread and failure constraints beside the step that can +violate them. Additional depth can live in the linked reference. A plain opening +must not hide a prerequisite, and a precise reference must not require a beginner +to infer the common sequence from a list of types. + +## Choose the page's job + +Before drafting, answer: + +- Who is reading, and what should they accomplish or understand? +- What knowledge, files, configuration or working example do they start with? +- Which existing page owns the answer, and which source establishes the behavior? +- What result would show success, and where should the reader go next? + +Use the role that matches that job. The outlines below are prompts; keep the +parts relevant to the page rather than adding empty sections. + +| Role | Useful outline | +| --- | --- | +| Index | Purpose and reader choice → meaningful destination table or list → deeper routes. Explain differences between nearby choices. | +| Tutorial | Goal and expected result → prerequisites and starting scaffold → staged changes with concepts introduced at use → observe success → cleanup and next lesson. | +| How-to | Named starting point → exact changes and insertion points → expected result → relevant failures and limits → full contract. | +| System overview | Responsibility and type choices → relationships, ownership and execution → small integration route → detailed owners. | +| Conceptual explanation | Reader question → mental model and necessary terms → relationships and consequences → relevant choices → concrete task/reference. | +| Reference | Valid inputs, outputs and units → applicable ownership, lifetime, thread and state rules → failures and limits → examples and authoritative links. | +| Troubleshooting | Observable symptom → evidence that distinguishes likely causes → corrective action → expected observation and unresolved limits. | +| Plan or acceptance procedure | Current readiness → prerequisites and decisions → next action → observable acceptance. Follow [maintenance policy](#maintenance-policy). | +| API comment | Purpose → meaningful parameter/return semantics → relevant preconditions, ownership, execution and failures → owning guide or related declaration. | + +An index succeeds through its choices and destinations. A simple alias or constant +may answer its lookup question through an accurate declaration and one contextual +link. A resource or lifecycle reference usually needs substantially more contract +detail. Examples should solve an actual reader problem. + +### Task or tutorial starting outline + +Adapt this to the size of the operation. A tutorial may repeat the change-and-observe +steps as it introduces concepts; a focused task may need only one such step. + +```markdown +# Perform the operation + +State the result and when the reader needs it. +Name the working example or starting files and required setup. + +## Make the change + +Identify the file, class/member, hook or configuration to change. +Introduce necessary concepts before using them. +Show the change and explain why it produces the result. + +## Observe the result + +Describe an observable success condition and relevant failure behavior. +Include cleanup when the operation creates a lifetime to manage. + +## Continue + +Link the next task and the authoritative detailed contract. +``` + +An intermediate lesson still needs a checkpoint: a defined return value, state +change or other inspectable result. Say which later lesson makes it visible in +the application. + +### Reference starting outline + +```markdown +# Name the interface or contract + +Explain its purpose and when to choose it. Link the first-use sequence. + +## Inputs and results + +Define valid values, units, defaults and returned ownership. + +## Ownership and execution + +State relevant lifetimes, allowed callers/threads and publication boundaries. + +## State, failures and limits + +Describe transitions, rejection, preserved state and unsupported cases. + +## Examples and sources + +Give a focused example where it helps; link deeper usage and declaring sources. +``` + +For generated reference pages, use the [API instructions](api-documentation.md) +for comment placement, complete entity coverage and source-to-render checks. + +## Start plainly, then give the exact contract + +- Put the reader's answer in the opening paragraph. State what the operation + accomplishes before describing its internal machinery. +- Define necessary terms at first use. Keep names consistent across prose, + snippets, assets and linked examples. +- Give each paragraph one main point. Prefer concrete subjects and verbs over + vague claims such as "handles everything" or "works automatically." +- State units, defaults and important boundaries explicitly. For example, identify + whether a width uses logical coordinates or framebuffer pixels. +- Give snippets their insertion context: file, member declarations, hook, required + includes, configuration and asset prerequisites as needed. Identify placeholders + and distinguish a complete example from a fragment. +- Make the simple path easy to follow, then expose exceptions and exact rules + under descriptive headings. Keep a failure or lifetime rule beside the step + that needs it, even when the full explanation belongs deeper. +- Describe the expected observation. "It builds" and "the requested behavior + works" are different outcomes. +- Preserve useful expert detail. Improve its introduction, ordering or retrieval + when readers struggle to find it; remove repetition after preserving its unique + requirements in the authoritative owner. + +Use short, concrete headings and task labels. Tables help readers choose between +paths or compare rules; use columns that support that decision. Avoid repeated +promises such as "safely" without stating the actual constraint. Use a diagram +when it clarifies a relationship, ownership boundary or sequence; accompany it with +the essential rule in text and confirm the generator can render its format. +Use descriptive link text and language-tagged code fences. Explain meaningful +images/diagrams in text and label units and relationships; color alone must not +carry a required distinction. Check wide tables and long signatures in the +rendered page instead of assuming they remain readable. + +## Make examples and procedures complete + +- Name the initial working scaffold, source/SDK selection and relevant options. + Identify exact files, declarations, members, hooks, includes, link targets and + asset prerequisites. Show where a fragment belongs and what existing code it + replaces or retains. Do not present a fragment as a complete application. +- Follow the full relevant lifecycle: setup, use, observation and release. A lesson + ending in prepared state must identify how that state is inspected and which + lesson makes it visible. +- Prefer maintained example code or includes when they serve the lesson. Check + the lesson's conceptual explanation against that code; inclusion alone does not + establish that the reader understands the missing integration. +- Keep identifiers, font-group names, assets, actions, options and defaults + consistent across linked lessons and references. Check an example's requested + identity as well as its plausible output when fallback can hide a mismatch. +- Explain whether paths are relative to the checkout, asset root, executable or + current directory. Command blocks establish their working directory and preserve + the caller's starting directory. Follow existing repository command conventions. +- Separate ordinary success from expected rejection/failure examples. Explain + what the reader observes, what remains valid and how to recover. Diagnostics + should lead to an action rather than repeat internal implementation names. +- State supported configurations and necessary fixtures at the point of use. + Validate selected platform, static/shared linkage, packaging and runtime claims + along their affected producer-to-consumer path. Do not infer all configurations + from one successful example. + +## Support the claims + +Add technical detail when it helps the reader choose, use or verify the system. +Check that precision against the responsible source. Unsupported specificity +creates an expectation the documentation cannot justify. + +For each consequential statement, identify the source that establishes it: + +| Claim | Evidence to inspect | +| --- | --- | +| Current behavior | Declaring interface and relevant implementation, including failure paths. | +| Selection, linking, installation or deployment | The configuration owner and the affected producer-to-consumer path. | +| Intended change | The owning design or plan, clearly distinguished from current behavior. | +| Accepted behavior | Results for the stated revision, configuration and coverage, with unresolved limits retained. | + +Use precise scope: who owns a value, what a view borrows, when a change becomes +visible, which thread may perform an operation, and what failure leaves intact. +Follow a convenience API through its dependencies before promising behavior on +their behalf. + +If evidence is incomplete, state what is known and what remains uncertain. Label +an inference and identify the missing check. A declaration's presence establishes +that it was extracted; inspect the rendered signature and meaning before claiming +accurate or complete API documentation. + +Keep one authority per fact. Other pages can summarize enough to make a decision +and link the exact contract. Check linked examples together: a name mismatch can +silently select a valid fallback and produce a plausible result. + +For punctuation in headings, distinguish the Markdown heading slug from the +final HTML ID. MyST can resolve `#shadermaterial-definitions` to HTML +`#shader-material-definitions`; copying the latter into a Markdown fragment can +produce an unresolved reference. Check the source form through Sphinx. API +`cheryl-guide:` links enter after that resolution and need the final HTML +fragment. Explicit labels preserve old HTML bookmarks, but a Markdown +`page.md#fragment` still needs that page's recognized heading slug; use the +current heading or a label reference. + +## Review and audit + +For a documentation audit, inspect the relevant implementation and documentation +for inaccurate, obsolete, missing, duplicated or unnecessary information. Report +findings and recommended changes without modifying documents. Distinguish intended +behavior from a mistaken claim about current implementation. + +### Apply existing findings + +The [usefulness report](../../reports/documentation-usefulness/report.md) and its +per-page inventories describe a recorded baseline. Recheck each finding against +the current source and content before using it as a task. Do not overwrite the +historical scores with assumed improvements or treat an old gap as proof that +the corresponding source/test is still absent. + +Prioritize reader harm: false instructions and declaration errors; blocked task +handoffs and inconsistent examples; missing API contracts and guide routes; then +entry explanations, retrieval and scaffolds. Use the requested implementation +sequence when work is divided into stages. A high-scoring page can still contain +a consequential defect. Work order does not narrow a request to cover all content. + +Repair a coherent subject or reader route, including its affected examples and +links, then review it before expanding the next batch. State the problem repaired +and its evidence; a higher score, longer comment or larger page is not the outcome. + +### Bound a new audit + +- Establish the requested audience and corpus before scoring. For a published-site + audit, use the generator's source root, exclusions and include relationships to + identify the rendered content. A repository link does not make its target a + published page. +- Count included canonical content once. Keep manual pages and generated + declaration articles in separate summaries, and put any repository-only review + outside the primary results and priorities. +- Record the source revision and distinguish current observations from a previous + report's findings. Use the existing navigation to find each page's job, entry + context and deeper owners. +- Identify the expected improvement for the reader. A higher score should reflect + a repaired instruction, supported contract or clearer route to an answer. +- Read every substantive unit claimed as reviewed, including relevant member + documentation, rather than extrapolating from a sample. Distinguish complete + reading, selected claim checks and automated measurements. Identify uncertainty + and evidence limits for claims that were not checked. + +### Measure usefulness + +Assess the page for its role and intended reader: + +| Dimension | Question | +| --- | --- | +| Purpose and place | Does the page answer the expected question from its stated prerequisites? | +| Beginner entry | Are the concepts and common path understandable to its intended first reader? | +| Practical usefulness | Can the reader finish the operation or answer the lookup question? | +| Contract depth | Can an expert verify the relevant inputs, ownership, execution, state and failures? | +| Currency and evidence | Do important claims match current source and distinguish implementation from acceptance? | +| Retrieval | Can readers scan to the next step or exact rule? | +| Connections | Can readers find the page, continue the task and reach its deeper owners? | + +Use the report's [rubric and weights](../../reports/documentation-usefulness/report.md#rubric) +when numeric comparisons are requested. Preserve its role-sensitive interpretation +and cap for materially inaccurate content. Keep manual, generated API and +repository-only results separate. Comment counts, link counts and automated +readability measures are evidence screens; they do not establish semantic quality +or replace a reader walkthrough. + +## Review the reader's path + +Use the [usefulness rubric](../../reports/documentation-usefulness/report.md#rubric) +and [assessment method](../../reports/documentation-usefulness/report.md#2-how-the-assessment-was-made) +to structure judgment. The report's findings describe its recorded baseline; +check current source before carrying them into new work. Word counts, comment +presence and fine score differences do not establish usefulness. + +Walk through two questions appropriate to the page: + +1. **Starting reader:** Begin at the stated prerequisites. Follow the links and + instructions to the result. Record missing setup, inferred steps, inconsistent + names and unnecessary detours. Can the reader recognize success? If a fallback + can hide a mistake, check the selected identity or state as well as appearance. +2. **Experienced reader:** Find an exact answer about a relevant input, owner, + lifetime, thread, transition or failure. Can the reader cite the rule directly + or reach its authoritative owner through a clear link? + +Distinguish a static walkthrough from an executed example or observed user task. +Report what was inspected or run, the evidence it provides and remaining limits. +Use the existing verification rules to choose permitted checks. An unexecuted +example needs an honest status, not an implied successful result. + +### Check discovery + +Start from likely task words, the relevant overview and an API symbol reached +through search. Can each route reach the capability and its complete procedure? +Check whether the documentation answers what the engine supplies, what the +application chooses and which integration point to use. Follow the actual route; +a destination's existence is not evidence that a reader can find it. + +For a gap, identify the reader's question, entry point, route attempted, missing +answer and smallest useful correction. Prefer a clear label, contextual link or +missing section in the existing owner over a duplicate page or navigation redesign. + +## Maintain, consolidate and retire + +After a contract changes, follow its impact through API comments, the owning guide, +summaries, examples, procedures and inbound links. Update the affected meanings +together; retain valid detail outside the change. Distinguish observed behavior +from intended changes when source and documented architecture disagree. + +Before removing a document or section, identify its unique requirements, rationale, +procedures and unresolved acceptance. Preserve still-useful material in an existing +owner, then repair incoming links and useful anchors. Completed implementation +history belongs in Git; current contracts belong in references and unfinished work +belongs in its owning plan. Follow the [maintenance policy](#maintenance-policy) below. + +Keep `STATUS.md` as the overview of capabilities and planned work. `planning/` +holds actual plans and an index of those plans. Reconcile both directions: each +actual plan needs a STATUS route; STATUS topics without a concrete plan remain +explicit scoping gaps. Separate ready acceptance in the testing queue from +unimplemented regressions and deferred fixture/device prerequisites. + +### Maintenance policy + +Documentation should help a reader understand the current system, its intended direction, or the work required to reach that direction. + +Prefer updating and consolidating existing authoritative documents over creating new ones. Remove obsolete, duplicate, or superseded documentation when its useful information has been preserved. Do not expand the document collection merely to record intermediate work, reasoning, or implementation progress. + +Apply these rules to affected documents across `docs/` during authorized maintenance: + +- Describe current contracts in the present tense. Keep plans focused on the target state, unresolved work, prerequisites, ownership/dependency constraints, ordered steps, discovery boundaries and acceptance criteria. +- Keep one concise progress checklist for each active macro task in the plan that owns its details. Reconcile accumulated subtask progress during maintenance, retaining checked subtasks while the macro task remains open. Keep future macro tasks at roadmap detail until they become active; link to the owning checklist instead of maintaining duplicate progress lists. +- Remove completed macro-task narratives and superseded alternatives. Document resulting durable behavior in its authoritative subject document, and link to it instead of duplicating contracts or source inventories. +- Preserve unique requirements, decisions and rationale that still guide implementation. Before shortening or deleting a plan, verify that each remains available in the surviving text or a linked authoritative document. Do not replace relevant detail with a vague summary or a pointer to Git alone. +- Git commits and diffs hold implementation history. A brief decision/history summary with specific Git references is useful when it explains the current direction or saves a future reader substantial reconstruction; do not reproduce the task-by-task journal. +- Do not append routine successful build/test/check reports, pass counts, transcripts or toolchain snapshots unless explicitly requested. Retain reusable validation procedures and meaningful coverage limits. Distinguish source completion from executable acceptance; a successful exit with skipped or unselected checks does not establish the missing coverage. +- Record unresolved failures, unavailable prerequisites and environment/compatibility limits only to the extent they affect future work, with the required next action. Remove resolved blockers unless they describe an enduring constraint. +- Reconcile affected plans and current-state documentation against accumulated completed work. When a macro task completes, remove its task section and checklist after preserving any reusable procedure, durable contract or still-relevant decision rationale and repairing inbound links. Delete a one-purpose plan when no unresolved work remains. Do not move its execution log elsewhere merely to empty the plan. + +Keep a paragraph when it explains how something works, how it should work, how to get there, or a decision/constraint needed to do so correctly. Concision must preserve that information. + +Historical audit data retains its reviewed revision and findings. Repair live +report links when owners move; use revision links for retired evidence. A later +audit records its new scope and evidence rather than silently rewriting history. + +## Complete a documentation unit + +Before handing off, establish the following for the affected scope: + +- The intended reader can find the answer, follow the normal path and recognize + success; experts can retrieve precise rules and their source. +- Consequential claims, names, examples and configuration boundaries agree with + the responsible source and connected documents. +- The change preserves useful contracts, established navigation and relevant + unresolved work; it introduces no competing authority or unexplained workaround. +- Applicable links, targets and fragments resolve. Inspect the rendered result + for presentation-sensitive changes and API work; source Markdown or XML alone + does not prove usable HTML. +- When generation is part of the authorized task, use the regular output at + `docs/site/generation/generated/html/`, following the tooling guide. Record the + actual output and revision; do not substitute a build-directory copy. +- Report changed content, checks actually performed and outstanding evidence or + decisions. Generation/link success does not prove that prose is accurate or + that the documented application procedure ran successfully. diff --git a/docs/for-agents/git.md b/docs/for-agents/git.md new file mode 100644 index 00000000..91eb636e --- /dev/null +++ b/docs/for-agents/git.md @@ -0,0 +1,109 @@ +# Git work and branch consolidation + +Use this guide when creating commits or integrating branches. The shared +[workflow](workflow.md) owns task authorization, progression and shared-tree +safety. A review of branches remains read-only until integration is requested. + +## Create coherent commits + +Complete and commit coherent development units independently after the relevant +permitted checks. Commit only task-related changes that can be separated cleanly +from pre-existing work. Inspect status, affected diffs and the staged diff before +committing; do not stage unrelated or unfinished user changes. + +If task work and pre-existing work cannot be separated safely, leave the overlap +uncommitted and explain the boundary. Do not rewrite the user's work to manufacture +a clean commit. A commit records the completed source change; state any outstanding +executable acceptance separately. + +For agent-created commits, supply this identity on the Git command: + +```sh +git -c user.name=cppcooper \ + -c user.email=cppcooper@users.noreply.github.com \ + commit --file /path/to/reviewed-commit-message +``` + +Do not change global or repository Git configuration. The message begins with a +third-person singular present-tense verb, such as `Adds`, `Updates`, `Revises`, +`Deletes`, `Fixes` or `Requires`. Use a detailed body appropriate to the development +unit: the resulting behavior, affected contracts and meaningful verification +limits. Preserve actual newlines when passing a message through a file or a +structured tool argument. + +Preserve individual completed commits normally. Do not amend, squash or otherwise +rewrite them without an explicit request. Do not generate mailbox patches or +push commits/branches to a remote unless explicitly requested. A request to merge +locally does not authorize a push or branch deletion. + +## Consolidate branches into fixes-develop + +The established policy for a user request to merge completed branches down into +`fixes-develop` is a **squash consolidation**, with every integrated original +commit's full message retained in the aggregate commit body. The user explicitly +confirmed this strategy; it is the exception to the normal individual-commit rule +for this destination. Do not apply it automatically to another destination or to +an unrelated integration request. + +The consolidation creates a new aggregate commit on `fixes-develop`. Keep the +original commits and source branch refs; do not rebase, amend, reset or delete +them as part of this operation. `develop` and `main` remain unchanged unless the +user separately requests their update. + +### Establish the integration set + +1. Inspect status and relevant diffs. Require a clean index and working tree for + the consolidation; resolve or safely separate authorized task work first. + Do not discard or automatically stash another person's work. +2. Inspect local branch tips, tracking refs and worktrees. Record the full + destination/source commit IDs before switching branches. Preserve active + worktrees and their outputs; do not force a checkout of a branch used elsewhere. +3. Check ancestry and divergence, including whether `develop` and `main` are + already ancestors. A detached review worktree is not another branch to merge. + Local tracking refs describe the last fetched state; do not claim they prove + the current remote state. +4. Determine the distinct commits being integrated relative to the destination. + For stacked branches, count each shared ancestor once. Do not concatenate a + separate overlapping log for every branch. The union from + `git rev-list --reverse --topo-order --not ` identifies + distinct reachable commits; retain their full messages, not just subjects. +5. If the stack is linear and all selected branch tips are ancestors of one final + tip, integrate that final tip once. This includes the intermediate branches + without repeated squash operations. Include an authorized user commit at that + tip as part of the requested branch contents; do not silently drop it. + +An older squash may represent changes without preserving ancestry from its +source branch. Establish whether those changes are already integrated rather +than replaying a historical snapshot merely because it is not an ancestor. +Resolve routine integration details within the requested scope; seek direction +when source histories or conflicting requirements leave a material choice unclear. + +### Produce and verify the aggregate + +Prepare the complete message in a temporary file before integrating. Start with a +summary such as `Merges completed branches into fixes-develop (squash)`, then include +each distinct integrated commit's full message in chronological/topological order. +Commit IDs may identify the entries. Preserve their text and original message +bodies; do not replace them with a newly written summary of the work. + +Switch to `fixes-develop`, squash the reviewed integration tip or tips, and inspect +the staged result before creating the aggregate with the identity above. Do not +treat a merge conflict as permission to overwrite pre-existing work or silently +choose between incompatible contracts. + +For a linear stack whose destination is an ancestor of the selected final tip, +the committed aggregate tree must equal that tip's tree. Compare their tree IDs +or use `git diff --exit-code fixes-develop`, then confirm clean +status and that the source refs still point to their recorded commits. Squashing +does not make those original commit IDs ancestors of the destination, so +`git branch --merged` alone cannot establish consolidation success. + +An unchanged final tree does not require another native build merely because the +commit shape changed. Report the tree comparison and relevant prior verification +accurately. If conflict resolution changes content, assess the affected contracts +and run the smallest checks permitted by the [testing instructions](testing.md); +do not infer executable acceptance from a clean Git merge. + +Report the new destination commit, source tips included, preserved original refs, +tree-equivalence result and any unresolved verification. Keep the result local +unless the user requested a push. diff --git a/docs/for-agents/implementation.md b/docs/for-agents/implementation.md new file mode 100644 index 00000000..051e8b8b --- /dev/null +++ b/docs/for-agents/implementation.md @@ -0,0 +1,150 @@ +# Implementation, refactoring and tooling + +Use this guide to turn an agreed outcome or design into a coherent development +unit. It covers implementation planning, producer-to-consumer changes and Cheryl +code conventions. The [shared workflow](workflow.md) owns scope, source authority, +authorization, repository safety and shared-work boundaries. + +Use [architecture and design](architecture.md) for unresolved contract decisions, +[testing](testing.md) for test work and verification, [Git](git.md) for commits, +and [documentation](documentation.md) for a documentation phase. + +## Prepare a coherent development unit + +Before nontrivial implementation, establish a concise plan containing: + +- The requested outcome and the behavior or contract being changed. +- Coherent tasks that can be completed and reviewed together. +- Risky assumptions and the evidence needed to resolve them. +- Dependency boundaries and any required decision checkpoints. + +Keep the plan in the working context unless a persistent planning artifact was +explicitly requested. Resolve foundational decisions and uncertain boundaries +before accumulating dependent code. Follow the plan within its approved scope; +revise it when implementation reveals material new information. + +Identify the current owner of the behavior and trace its callers and dependencies +far enough to understand the change. Read the relevant implementation, tests and +configuration rather than treating a public declaration as its complete behavior. +Use the [architecture guide](architecture.md#specify-the-boundary-precisely) when +ownership, execution, failure or compatibility needs a new decision. + +Before nontrivial behavior changes, identify existing assumptions the change +could invalidate. Keep a short map in the working plan: changed behavior → +previous assumption → affected consumer or fixture → smallest useful evidence. +Include unchanged callers affected by defaults, validation, ownership or scheduling. +Use [coverage selection](testing.md#select-relevant-coverage) to resolve those risks +with focused checks and [existing builds](testing.md#reuse-existing-builds). + +## Implement the complete behavior + +Make the approved design concrete through its relevant lifecycle: construction, +ordinary use, state transitions, failure, cleanup and the affected consumer path. +Keep application policy and reusable responsibilities with their selected owners. +Preserve the established contract outside the requested change. + +Necessary investigation, affected test maintenance and focused regression coverage +belong with the behavior being implemented. Follow the [testing guide](testing.md) +for their design, execution and evidence limits. Address material failure paths +as well as the straightforward success case. + +Add essential comments and runtime logging required by the implementation at the +relevant operation. Keep broader comment cleanup, unrelated logging improvements +and explanatory documentation with their own objectives. The +[documentation workflow](documentation.md) owns synchronization and maintenance. + +## Trace build, tooling, SDK and CI changes + +### Establish the producer and consumer contract + +Before changing behavior, identify the affected contract, its owner and the path +from producer to consumer. Follow the portions relevant to the task through: + +1. Artifact creation and the inputs/configuration that select it. +2. Installation and packaging, including dependency ownership. +3. Relocation and runtime discovery/loading. +4. Cache reuse and invalidation at the affected boundary. +5. Workflow activation and the prerequisites that make the path run. + +An artifact being created does not establish that it was installed, shipped, +found after relocation or used by the intended consumer. Inspect only the affected +path; broad inventories do not replace tracing that contract. + +### Extend the existing policy owner + +Locate the existing authority for each policy before adding another implementation. +Prefer extending that owner over duplicating policy in a wrapper, classifier, +cache rule, fallback or compatibility branch. + +When CMake, Python or workflow code must express the same policy, keep their +semantics aligned and cover their agreement with focused checks. Check both the +decision and the context in which it is evaluated; identical-looking expressions +can receive different inputs at configuration, installation or script execution. + +### Select the affected execution contexts + +Choose coverage according to the changed boundary, including relevant differences: + +| Context | Question | +| --- | --- | +| Project configuration versus generated CMake install/script execution | Are values, paths and available targets valid at the time the code runs? | +| Source versus installed SDK consumption | Does the installed consumer receive the promised headers, targets and dependencies? | +| Static versus shared linkage | Are dependency closure and runtime loading correct for the selected linkage? | +| Linux versus Windows | Are platform paths, binaries and native requirements handled by the responsible owner? | +| Cold versus warm caches | Can fresh production and retained artifacts both follow the intended policy? | +| Default-branch workflow prerequisites | Will the intended event, branch and upstream artifacts actually activate the changed workflow? | + +Use the relevant subset; do not run the entire matrix for every edit. Follow +[testing and verification](testing.md) for permitted checks, resource use and +reuse of compatible builds/caches. + +### Preserve the contract when repairing a check + +Trace dependency ownership before changing bundling rules or system-library +exceptions. Distinguish correcting a verifier from changing what the SDK promises +to supply. A failing check is evidence to investigate, not permission to weaken +the producer or consumer contract. + +For repeated failures, revisit the common assumption before growing special cases. +Remove superseded paths within the requested unit; keep a broader redesign as +separate discovered work. + +## Follow Cheryl conventions + +- Follow the repository's existing style and `.clang-format`. +- Put data members above methods when organizing class declarations. +- Keep unit-test names short and intuitive. +- Exercise behavior without intentionally emitting compiler warnings in normal + test builds. Consume results that matter to an assertion; explicitly discard an + irrelevant `[[nodiscard]]` result with `static_cast(...)`, including inside + exception assertions. Apply the same discipline to other intentional diagnostic + triggers. +- Test compiler diagnostics in isolated probes that capture and assert the expected + diagnostic. Keep suppression scoped to that construct and compiler. Preserve API + attributes and target/project warning settings rather than weakening them to + accommodate a test. +- Evaluate interface changes through their + [architectural purpose and contract](architecture.md#compare-viable-approaches), + including supported extension and replacement responsibilities. + +## Review, verify and commit the unit + +Review the resulting behavior and consumer path against the agreed outcome. +Check that a local repair has not shifted ownership, changed unrelated guarantees +or introduced a second policy authority. Use the smallest relevant permitted +checks and maintain the directly affected coverage through the +[testing guide](testing.md). + +Account for each material regression risk with observed evidence or an explicit +verification gap. If new evidence invalidates an assumption, revise the selection +before treating the unit as verified. + +Complete and commit coherent development units independently after appropriate +permitted checks, following the [Git guide](git.md). State outstanding executable +acceptance explicitly; a commit does not establish that an unrun native, installed +or runtime path has succeeded. + +At handoff, describe what changed and why, the relevant contract, evidence actually +observed, and remaining verification or decisions. Preserve discovered testing +needs for the [testing-request process](testing-requests.md), and use the +[documentation workflow](documentation.md) for any authorized documentation phase. diff --git a/docs/for-agents/investigation-and-planning.md b/docs/for-agents/investigation-and-planning.md new file mode 100644 index 00000000..99b5d6ce --- /dev/null +++ b/docs/for-agents/investigation-and-planning.md @@ -0,0 +1,91 @@ +# Investigate and plan work + +Use this guide for review, investigation, research and working plans. The shared +[workflow](workflow.md) owns work-mode authorization, progression, source authority +and shared-tree safety. This guide supplies the method for establishing an answer +or a concrete next step within that scope. + +## Frame the question + +Identify the requested outcome and the decision the investigation must support. +State what is being reviewed, which contracts or owners may be affected, and what +evidence would answer the question. Read-only investigation does not authorize +implementation when a fix becomes apparent; retain that fix as a recommendation. + +Acquire context purposefully. Start with the relevant declarations, +implementations, configuration and concrete consumers. Follow dependencies far +enough to establish the affected contract, rather than loading the whole project +or every plan by default. Consult the subject guide, roadmap or history when it +explains intended behavior or a still-relevant constraint. + +When the user or another agent is changing the same source, establish what revision +the observations describe. Use a stable existing snapshot or commit-based reads +where appropriate; do not turn a transient mixed read into a claim about a coherent +implementation. Record relevant uncertainty and preserve ongoing work. + +## Trace the contract and evidence + +Find the owner and follow the path from caller to result, including applicable +ownership, lifetime, execution, failure and cleanup boundaries. Check whether an +existing API or supported composition already solves the apparent problem before +recommending another abstraction or application workaround. + +Distinguish these findings in the working notes and handoff: + +| Kind | State | +| --- | --- | +| Observed implementation | What the current source/configuration does, with a concrete path and location. | +| Documented intent | The requirement, design rationale or planned direction, identified as such. | +| Verified behavior | What an actually executed check establishes, with its configuration and limits. | +| Inference or uncertainty | The reasoning, missing evidence and consequence for the decision. | + +Source inspection can establish a code path or declared contract; test presence +does not establish a successful run. If implementation and documented intent +disagree, explain both rather than silently treating either as correct. Expand +the investigation when a dependency invalidates the current assumption, and use +the shared workflow's boundaries when further work needs user direction. + +## Form a working plan + +When the investigation produces a plan, make the next work concrete without +accumulating implementation before its foundations are settled. Include: + +- The intended result, scope and affected owner/contract. +- Coherent tasks in dependency order, with independent work identified where useful. +- Risky assumptions, unfinished-work dependencies and decisions that must be settled + before accumulating dependent changes. +- Applicable completion and acceptance criteria, including the smallest relevant + checks and any unavailable or unauthorized executable verification. +- Mandatory decision checkpoints from the user or approved plan. + +Resolve foundational decisions before extending their dependency surface. Revise +the plan when evidence invalidates an assumption; explain material changes and +their effect on scope or acceptance. Routine implementation choices within the +agreed scope do not each require another approval. A plan itself does not authorize +an operation or an independent objective outside that scope. + +Keep plans and phase handoffs in context unless a persistent artifact is explicitly +requested. For an authorized durable plan, follow the existing plan owner and the +[documentation-maintenance rules](documentation.md#maintenance-policy). +Do not create a new report or planning file merely to record intermediate progress. + +For a design decision, continue with [architecture guidance](architecture.md). +For authorized code changes, the +[implementation preparation](implementation.md#prepare-a-coherent-development-unit) +owns the required plan and development-unit sequencing. +Read the [testing instructions](testing.md) before executing unfamiliar checks. + +## Hand off the result + +Give a concise answer supported by concrete source locations and relevant +consumers. Report findings and recommendations in review/investigation mode; +distinguish them from changes actually implemented. For a material problem, explain +its consequence, existing owner, smallest coherent correction and any decision +that requires user direction. Do not manufacture findings to fill a target count. + +Carry forward established decisions, constraints, outstanding work and affected +contracts. State the evidence collected, verification limits and recommended next +action so the next phase can proceed without reconstructing the investigation. +Stop at the requested objective or mandatory decision boundary; do not begin an +independent implementation, audit or documentation phase on the strength of a +recommendation alone. diff --git a/docs/for-agents/testing-requests.md b/docs/for-agents/testing-requests.md new file mode 100644 index 00000000..17a6d249 --- /dev/null +++ b/docs/for-agents/testing-requests.md @@ -0,0 +1,30 @@ +# Testing-request maintenance + +This guide governs maintenance of [the user-facing testing queue](../testing-requests.md). +It does not contain the queue itself. Use the [workflow](workflow.md) for task +scope and progression, [testing and verification](testing.md) for operation +permissions and evidence, and [documentation](documentation.md) for general +documentation maintenance. + +Testing-request documentation is maintained only when explicitly authorized. Do not update `docs/testing-requests.md` automatically during implementation, refactoring, verification, or other non-documentation phases. + +Preserve discovered testing requirements in the task handoff until a testing-request maintenance phase is authorized. At that point, reconcile accumulated requirements against existing requests. + +These rules govern testing-request documentation. They do not defer directly related test maintenance or permitted verification from an implementation task. + +During authorized testing-request maintenance: + +- Maintain `docs/testing-requests.md` as the user-facing queue of testing needed for implemented work. Reconcile accumulated testing needs with the current source, targets, configuration, reported results, and required coverage. +- Keep a summary table near the top with exactly three columns: **TR#**, **Short description / title**, and **Long description (plain language)**. Give every active request one row, with its `TR` linked directly to its detailed section/heading. Include the same ID in that heading. Preserve request IDs when updating requests; do not renumber remaining requests or reuse retired IDs. +- Prefix each summary-table **Short description / title** cell with **Automated:** or **QA:**, matching the request type in its detailed section. +- Write each table's long description for a reader unfamiliar with engine or testing internals: explain what will be checked, why it matters, and any setup requirement or blocker affecting whether they can run it. Explain necessary jargon; keep exact commands and detailed acceptance criteria in the linked section. +- Aggregate deferred testing across development cycles. Extend an existing request when it covers the same behavior and configuration; avoid duplicate case runs through owner and aggregate runners, and batch needed build targets into as few invocations as practical. +- Label each request as **Automated** or **QA**, with its platform, prerequisites, readiness and acceptance scope. Keep development progress in the owning plan rather than duplicating its macro-task checklist here. +- For each automated request, provide a runnable fenced command block. Include required CMake configuration, explicit build targets, environment opt-ins, working directory and test selection. State when a matching existing build can be reused. +- Command blocks must establish their working directory explicitly, including QA launch blocks. Commands launched from a document may inherit that document's directory; locate the checkout root rather than assuming the shell already runs there, and preserve the caller's starting directory. +- User-run command-line CMake configuration must explicitly select Ninja with `-G Ninja`. Build and test commands use that configured build directory. +- For each QA request, provide a brief detailed summary and concise requirement bullets covering setup, actions, expected observations and required platform/runtime modes. +- Mark requests blocked when fixtures, hardware or an observation harness are unavailable, and identify the next action. Do not invent commands or count skipped, unselected or device-free checks as the missing acceptance. +- When the user reports results, reconcile requests and owning plans during the next authorized maintenance phase against the actual tested revision, configuration, platforms and coverage. Remove accepted requests, preserve unresolved portions and repair affected links; retain durable coverage limits in the relevant subject guide rather than appending routine execution logs. + +Submitting a testing request does not authorize operations that require explicit permission under [Verification and resource use](testing.md#verification-and-resource-use). diff --git a/docs/for-agents/testing.md b/docs/for-agents/testing.md new file mode 100644 index 00000000..27d85e54 --- /dev/null +++ b/docs/for-agents/testing.md @@ -0,0 +1,119 @@ +# Testing and verification + +Use this guide for test design, test implementation, test execution and +verification. The [workflow](workflow.md) owns task scope, authorization and +progression between independent objectives. [Implementation](implementation.md) +owns code conventions, test naming and compiler-warning discipline. + +The [testing and validation guide](../site/development/testing.md) maps repository +runners and procedures. Maintaining the user-facing testing queue is a separate +objective governed by [testing-request maintenance](testing-requests.md). + +## Choose the objective and evidence + +| Objective | Work | Handoff | +| --- | --- | --- | +| Test design | Read the contract, implementation and existing coverage. Identify observable outcomes, relevant failure paths, prerequisites and the smallest cases that distinguish correct behavior. | Proposed cases, expected results, fixtures and coverage limits. A design is not implemented or executed coverage. | +| Test implementation | Update affected cases and shared fixtures; add focused regression coverage where needed. Follow the implementation guide's naming and warning rules. | Cases changed and the behavior they exercise, with any permitted checks actually run. Written cases alone do not establish acceptance. | +| Test execution | Inspect the selected runner and its subprocesses, confirm the authorized operation and compatible configuration, then run the focused selection. | Actual revision, configuration, selection, results, skips and unavailable prerequisites. | +| Verification | Trace the affected contract through its owner and consumers, choose appropriate evidence, and compare observed results with the required outcome. | Confirmed behavior or failure, inferred cause, and remaining acceptance needed. | + +For supporting regression work within an implementation, apply the workflow's +existing authorization and progression rules. Keep supporting work within that +development unit; an independent testing campaign follows the workflow's task +boundaries. + +## Select relevant coverage + +Choose cases from the assumptions the change could invalidate, including its +failure and ownership boundaries. Search existing callers and shared fixtures, +including default construction, omitted arguments and custom overrides, even when +their files are unchanged. Prefer an existing case when it exercises the risk; +add coverage when a material gap remains. + +Inspect owner and aggregate registration before selecting targets. Follow each +case to the runner that actually includes it; a source directory or aggregate +name alone does not establish which cases run. Tie each selected check to a +specific risk, using the least costly evidence that can distinguish it. + +For build, tooling, SDK and CI work, use the producer-to-consumer analysis and +[affected execution contexts](implementation.md#select-the-affected-execution-contexts) +from the implementation guide to choose relevant configurations and consumers. +Tie each selected check to the boundary it verifies. + +A mocked command or fixture can establish orchestration without establishing +that the generated command succeeds or its output can be consumed. Select +artifact, relocation, runtime or device evidence when that is the claim being +verified, subject to the authorization rules below. + +### Check that tests reach the intended behavior + +Complete valid setup outside a negative assertion, then assert around the specific +operation expected to fail. An exception type alone can hide an earlier failure +in argument evaluation or fixture setup. Check relevant state or failure identity +when needed to establish that the intended boundary was reached. + +For asynchronous cases, prefer controlled coordination over assumptions about +completion speed. Keep hang watchdogs separate from behavioral expectations and +allow for scheduling, Debug diagnostics and instrumentation. A test deadline is +not a performance requirement unless the contract defines one. Select the relevant +configuration when such costs can invalidate a new wait assumption. + +For example, enabling automatic image insertion in `Context2D` also affects a +CPU frame fixture with no texture and a custom pipeline with no image parameter. +That fixture needs an explicit opt-out; select its existing `render_frame` cases +alongside automatic/manual binding checks. Construct a valid packet before +asserting that adding it violates pass constraints, so image validation cannot +make the assertion pass before the frame operation runs. + +## Verification and resource use + +- Run the smallest relevant permitted checks before handing off an implementation. Update affected tests with the behavior they cover, and add focused regression coverage when existing checks do not adequately exercise a material change. +- Static analysis and focused script or tooling tests using mocks or disposable fixtures are permitted without separate authorization when they do not compile project or dependency code, run native project executables, install or download tools or dependencies, generate full documentation, or access devices or external services. CMake script and configuration probes that enable no compiled languages may qualify. +- Inspect an unfamiliar check before executing it, including the subprocesses it launches. A Python filename or a description such as "tooling check" does not establish that a check is lightweight. Configuration that invokes compiler or linker probes requires build authorization. +- Project or dependency builds, native test execution, full documentation generation, dependency or tool downloads or installation, and tests using devices or external services require explicit authorization. Existing authorization persists for relevant fixes and reruns within its scope. When additional authorization is necessary to establish the requested outcome, identify the missing verification and request it together rather than waiting for an unrelated blocker. +- Batch related checks using [existing builds and caches](#reuse-existing-builds). After fixing a failed check, rerun the affected checks when permitted. Broaden or repeat successful checks only when changed inputs, failures, or unresolved concerns justify it. +- Share machine resources during authorized heavy work by bounding agent-run parallelism or reducing process priority as needed. Commands supplied for the user run at normal priority with available parallelism; do not add low-priority wrappers or single-job limits to user-run commands. +- Test behavior and contracts rather than mirroring implementation details. Use mocks to check orchestration, and distinguish that coverage from executing the generated commands or consuming the resulting artifacts. +- Report what actually ran and what remains unverified. Static checks, mocked tests, and successful configuration do not establish native build, link, relocation, or runtime success. Distinguish confirmed failures, inferred causes, and fixes awaiting executable acceptance. + +### Reuse existing builds + +Inspect existing configurations first. Prefer an established comprehensive build +when it can exercise the selected behavior. Compatibility depends on the affected +source, compiler, ABI and behavior under test; extra enabled modules are acceptable +unless they mask the boundary being checked. Incrementally build the necessary +targets and filter test cases within that build. Enabling many options does not +require building every target or running every discovered case. + +Create another configuration only when existing builds cannot exercise a material +risk. Explain the specific coverage gap first: for example, Debug versus Release, +static versus shared linkage, a disabled dependency or standalone consumption. +A focused selection alone does not justify another build directory. Preserve +active runs, build settings, outputs and caches under the shared-work rules; +build/configuration operations retain their authorization requirements above. + +### Local Windows tooling + +- On this workstation, reusable Windows tooling lives in `/mnt/projects/cheryl-windows-ci/`. Check it before downloading tools, creating another Wine environment, or reporting that Windows tooling is unavailable. If it is absent on another host, look for compatible installed tools there. +- The `tools/` directory contains Windows Python, CMake, Ninja, Git, ccache, and vcpkg. Read `env.sh` and `run.sh` for current paths, environment settings, and supported phases before using them; inspect the selected phase before execution. MSVC and the Windows SDK are installed under `/opt/msvc/`. +- Preserve `tools/`, `downloads/`, and `wine-prefix/` across sessions. Store additional reusable tools and isolated Wine prefixes under this disk-backed tooling root, preserving existing versions and active work. +- `/tmp` is memory-backed on this workstation. During RAM cleanup, preserve reusable tools and cached installers; move them to disk-backed storage when needed. Stop idle agent-owned processes and remove disposable test outputs within the authorized scope. +- These locations do not authorize builds, native tests, downloads, or changes to an active environment. The existing verification and shared-work restrictions still apply. + +## Report the result + +State what changed, which checks actually ran, and what remains unverified. +Include the tested revision and relevant configuration/selection when they affect +the conclusion. Separate an unexecuted case, a skipped case, a confirmed failure, +an inferred cause and a fix awaiting executable acceptance. + +Relate that evidence to the material regression risks and identify any outstanding +checks needed to resolve them. Passing a selected case establishes only the +behavior and configuration it actually exercised. + +For deferred requirements and queue maintenance, follow +[testing-request maintenance](testing-requests.md). Use the +[documentation workflow](documentation.md) for other authorized documentation +work. Follow [Git](git.md) for a coherent development-unit commit and its +verification limits. diff --git a/docs/for-agents/workflow.md b/docs/for-agents/workflow.md new file mode 100644 index 00000000..09e02793 --- /dev/null +++ b/docs/for-agents/workflow.md @@ -0,0 +1,114 @@ +# Shared work rules + +Read this guide for every task. The root [AGENTS.md](../../AGENTS.md) indexes +focused instructions; read the guides for the actual work being performed. +These instructions apply across the repository, including source comments outside +`docs/`. User authorization and constraints persist across relevant fixes and reruns. + +## Work modes and progression + +Each task has one primary work mode: + +- Review and planning +- Investigation and research +- Architecture and design +- Implementation, refactoring, and tooling +- Test design +- Test implementation +- Test execution +- Verification +- Documentation audit +- Documentation maintenance + +Keep independent work objectives separated by mode. A request for review or investigation does not authorize implementation merely because a fix becomes apparent. + +A request to implement or fix behavior includes necessary investigation, updates to affected tests, focused regression coverage where useful, and verification permitted by the [testing guide](testing.md). These supporting activities belong to the same development unit. Independent test design, broader verification campaigns, and documentation work remain separate objectives unless authorized. + +User-directed progression is the default: complete the requested objective and stop before starting an independent objective. When a bounded sequence would benefit from plan-directed progression, propose its phases and decision checkpoints before adopting it. An approved plan may authorize progression across modes without further permission for each coherent subtask. + +Stop when: + +- The requested scope is complete or an approved plan reaches a mandatory decision checkpoint. +- A material requirement, architectural decision, or invalidated assumption requires user direction. +- A necessary operation or scope expansion is not already authorized. +- Continuing would depend on unfinished user work or risk modifying protected changes. +- A blocking condition prevents safe or correct completion. + +A completed subtask or necessary supporting activity is not itself a stopping point. Continue authorized work that can safely proceed, including independent work unaffected by a blocked portion. Report any boundary, its implications, and the recommended next action. + +At handoff, distinguish implemented changes, observed verification, and outstanding or deferred work. Completing an implementation phase does not establish executable acceptance or complete a broader task with required work remaining. Optional improvements and advisory documentation checkpoints neither block completion nor authorize further work. + +## Source authority and context + +Prefer source code, configuration, and actual implementation when determining the repository's current state and behavior. + +Use documentation to understand intended architecture, requirements, design rationale, historical decisions, and planned work. Documentation may describe previous, intended, or partially implemented behavior. + +Verify documentation's claims about current implementation against the relevant code. When implementation and documented intent disagree, distinguish the observed state from the intended state rather than automatically treating either as correct. + +Acquire context purposefully. Inspect the code, documentation, history, and dependencies relevant to the current objective. Expand investigation when necessary rather than loading unrelated project context by default. + +Prefer concise phase handoffs containing established decisions, relevant constraints, outstanding work, and affected contracts. Do not create persistent handoff or planning documents unless explicitly requested. + +## Repository safety + +- Preserve active user runs and their outputs. Do not change their configuration, clean their build directories, or invalidate their caches as a routine preparation step. +- Do not perform opportunistic refactoring, cleanup, formatting, or API changes merely because they are convenient while working nearby. +- Report related work outside the current development unit as discovered work. Resolve necessary dependencies within the authorized scope; use the progression boundaries above when further work requires user direction. + +Git mutations and consolidation follow [Git](git.md). Builds, native execution, +full documentation generation, dependencies and devices follow the precise +permissions and resource rules in [Testing and verification](testing.md). +A mode guide or available tool does not grant authorization for its operations. + +## Shared working tree + +The repository is a shared human/agent working tree. Pre-existing uncommitted changes may represent deliberate work in progress and may be incomplete, temporarily inconsistent, non-functional, transitional, preparatory for later refactoring, or difficult to reason about safely. + +Before modifying affected code: + +- Inspect the current Git status and relevant pre-existing diffs. +- Treat pre-existing user changes as authoritative work in progress. Do not revert, overwrite, normalize, reformat, or replace them merely to simplify the task. +- Do not assume pre-existing changes form a valid, complete, or stable implementation baseline. +- Do not build new task work on top of pre-existing unfinished user work merely because its current behavior is understandable. +- Avoid introducing new dependencies on unfinished user work, propagating its current structure into additional code, constraining its future evolution, or otherwise increasing the amount of work that would need to change when the user's work is completed. + +A file containing user changes is not automatically off-limits. Work with it only when the requested change is independent of the unfinished work, or when the change can be made without increasing the dependency surface, constraining future evolution, or increasing the likely scope of subsequent refactoring. + +If completing the requested work would require treating unfinished user work as a stable foundation, extending its current design into other code, or making additional code depend on it, treat that as a development boundary. + +When such a boundary is encountered: + +- Preserve the existing user work unchanged where possible. +- Stop at that boundary. +- Continue with independent planned work only when it is clearly unaffected by, and does not depend on, the blocked work. +- Clearly identify what cannot be completed safely and why. + +The [Git guide](git.md#create-coherent-commits) owns separating task commits from user work. + +## Documentation phases + +Reading documentation is permitted in any work mode when relevant to understanding requirements, intent, constraints, or history. Prefer the relevant source code when establishing current behavior, and consult the relevant roadmap or subject document for direction rather than loading the entire planning folder. + +Documentation auditing and maintenance are separate work modes. Do not create, update, reorganize, or delete documentation during other work modes unless explicitly requested. + +Essential code comments and runtime logging directly required by the implementation are permitted. Do not perform general comment cleanup, logging improvements, or explanatory documentation work opportunistically. + +Defer documentation synchronization until a dedicated maintenance phase. Prefer stabilizing related implementation work before reconciling documentation. + +Recommend a documentation checkpoint at a meaningful phase boundary when architectural contracts stabilize, substantial implementation changes accumulate, or documentation is likely to have become materially inaccurate. + +Documentation checkpoints are advisory. Do not conduct a documentation audit or begin maintenance merely to make the recommendation. The user decides whether to continue engineering work or authorize documentation maintenance unless the transition was already approved. + +For authorized auditing and maintenance, use the +[documentation workflow](documentation.md), plus the +[API instructions](api-documentation.md) when declarations or comments are involved. +A documentation audit reports findings without changing documents. Maintenance +requires its own authorization or an already approved sequence. + +## Complete the requested scope + +Use the relevant mode guide's completion criteria. Report implemented changes, +observed verification and remaining decisions or acceptance separately. Finish +independent authorized work when another portion reaches a boundary; do not turn +an optional improvement into either a blocker or authorization for another task. diff --git a/docs/generation/build.py b/docs/generation/build.py deleted file mode 100644 index b0ffc282..00000000 --- a/docs/generation/build.py +++ /dev/null @@ -1,330 +0,0 @@ -#!/usr/bin/env python3 -"""Generate the developer manual and C++ reference without configuring Cheryl.""" - -from __future__ import annotations - -import argparse -import hashlib -import json -import os -from pathlib import Path -import platform -import re -import shutil -import subprocess -import sys -import xml.etree.ElementTree as ElementTree - -# Set the cache location before importing repository-local modules. -GENERATION_ROOT = Path(__file__).resolve().parent -GENERATED_ROOT = (GENERATION_ROOT / "generated").resolve() -REPO_ROOT = GENERATION_ROOT.parents[1] -if sys.pycache_prefix is None: - sys.pycache_prefix = str(GENERATED_ROOT / "pycache") -os.environ["PYTHONPYCACHEPREFIX"] = sys.pycache_prefix - -from bootstrap import file_lock, validate_doxygen - - -PUBLIC_INCLUDE_ROOTS = tuple( - REPO_ROOT / name - for name in ( - "projects/engine/include", - "projects/engine/support/startup/include", - "projects/engine/support/worker-topology/include", - "projects/engine/support/debug-terminal/include", - "projects/modules/platform/native-glfw/include", - "projects/modules/graphics/opengl/include", - "projects/modules/graphics/opengl/support/startup/include", - "projects/modules/ui/tgui/include", - "projects/modules/ui/rmlui/include", - "projects/modules/audio/miniaudio/include", - ) -) -HEADER_SUFFIXES = {".h", ".hpp", ".inl", ".ipp", ".tpp"} - - -def header_inputs() -> list[Path]: - headers = [] - for include_root in PUBLIC_INCLUDE_ROOTS: - source = include_root / "cheryl" - if not source.is_dir(): - raise RuntimeError(f"Missing public API include root: {source}") - headers.extend( - path - for path in source.rglob("*") - if path.is_file() and path.suffix in HEADER_SUFFIXES - ) - return sorted(headers, key=lambda path: path.as_posix()) - - -def quoted(path: Path) -> str: - return '"' + path.as_posix().replace('"', '\\"') + '"' - - -def revision() -> str: - head = subprocess.check_output( - ["git", "rev-parse", "HEAD"], cwd=REPO_ROOT, text=True - ).strip().lower() - value = os.environ.get("CHERYL_DOCS_REVISION") or os.environ.get( - "READTHEDOCS_GIT_COMMIT_HASH" - ) - if not value: - value = head - if not re.fullmatch(r"[0-9a-fA-F]{40}|[0-9a-fA-F]{64}", value): - raise RuntimeError("Documentation source links require a full Git commit ID.") - if value.lower() != head: - raise RuntimeError("The documentation revision must match the checked-out Git HEAD.") - return head - - -def provenance(commit: str, doxygen_version: str, output: Path) -> dict: - tools = json.loads(os.environ.get("CHERYL_DOCS_TOOLCHAIN", "{}")) - if (not isinstance(tools, dict) or tools.get("python_source") not in {"system", "pinned"} - or not isinstance(tools.get("packages"), dict)): - raise RuntimeError("Documentation tool identity is missing; invoke the generator through bootstrap.") - ref = ( - os.environ.get("CHERYL_DOCS_REF") - or os.environ.get("READTHEDOCS_GIT_IDENTIFIER") - or os.environ.get("GITHUB_REF") - ) - if not ref: - result = subprocess.run( - ["git", "symbolic-ref", "--quiet", "--short", "HEAD"], - cwd=REPO_ROOT, capture_output=True, text=True, - ) - ref = result.stdout.strip() if result.returncode == 0 else "detached" - paths = ["."] - if output.is_relative_to(REPO_ROOT): - paths.append(":(exclude)" + output.relative_to(REPO_ROOT).as_posix()) - dirty = bool(subprocess.check_output( - ["git", "status", "--porcelain", "--untracked-files=normal", "--", *paths], - cwd=REPO_ROOT, text=True, - ).strip()) - return { - "schema_version": 1, - "source_repository": "https://github.com/cppcooper/cheryl-engine", - "source_revision": commit, - "source_ref": ref, - "dirty": dirty, - "header_count": len(header_inputs()), - "tools": { - "python": platform.python_version(), - "python_source": tools["python_source"], - "doxygen": doxygen_version, - "packages": tools["packages"], - }, - "requirements_sha256": hashlib.sha256( - Path(__file__).with_name("requirements.txt").read_bytes() - ).hexdigest(), - "native_manifest_sha256": hashlib.sha256( - Path(__file__).with_name("native-tools.json").read_bytes() - ).hexdigest(), - } - - -def extract(output: Path, doxygen: str, commit: str) -> None: - headers = header_inputs() - template = Path(__file__).with_name("Doxyfile.in").read_text(encoding="utf-8") - replacements = { - "REVISION": commit, - "OUTPUT": quoted(output), - "REPO_ROOT": quoted(REPO_ROOT), - "INCLUDE_ROOTS": " ".join(quoted(root) for root in PUBLIC_INCLUDE_ROOTS), - "INPUT": " ".join(quoted(header) for header in headers), - } - for key, value in replacements.items(): - template = template.replace(f"@{key}@", value) - output.mkdir(parents=True, exist_ok=True) - configuration = output / "Doxyfile" - configuration.write_text(template, encoding="utf-8") - if (output / "xml").exists(): - shutil.rmtree(output / "xml") - subprocess.run([doxygen, str(configuration)], cwd=REPO_ROOT, check=True) - if not (output / "xml/index.xml").is_file(): - raise RuntimeError("Doxygen succeeded without creating xml/index.xml.") - # Doxygen's XML includes private nested compounds even with EXTRACT_PRIVATE=NO. - # Keep their declarations available for exact source links, but remove them - # from the public index and relationship navigation before Exhale reads it. - index_path = output / "xml/index.xml" - index = ElementTree.parse(index_path) - doxygen_configuration = ElementTree.parse(output / "xml/Doxyfile.xml") - private_namespaces = { - value.text[:-3] - for value in doxygen_configuration.findall("option[@id='EXCLUDE_SYMBOLS']/value") - if value.text and value.text.endswith("::*") - } - declarations = {} - private = set() - changed = set() - for compound in index.findall("compound"): - identifier = compound.get("refid") - document = ElementTree.parse(output / "xml" / (identifier + ".xml")) - declarations[identifier] = document - declaration = document.find("compounddef") - if declaration is not None: - # Doxygen can merge a leading global return-type qualifier with - # `virtual`. Breathe already renders virtuality from the XML attribute. - for member in declaration.findall(".//memberdef[@kind='function']"): - if member.get("virt") not in {"virtual", "pure-virtual"}: - continue - for tag in ("type", "definition"): - value = member.find(tag) - if value is not None and value.text and value.text.startswith("virtual::"): - value.text = value.text.removeprefix("virtual") - changed.add(identifier) - # Qualified implementation friendships are sometimes reported public. - # Apply only the explicit namespace policy, preserving public friends. - for member in declaration.findall(".//memberdef[@kind='friend']"): - name = member.findtext("name", "") - scope = declaration.findtext("compoundname", "") - while True: - qualified = "::".join(part for part in (scope, name) if part).removeprefix("::") - if any(qualified.startswith(namespace + "::") for namespace in private_namespaces): - member.set("prot", "private") - for reference in declaration.findall("listofallmembers/member"): - if reference.get("refid") == member.get("id"): - reference.set("prot", "private") - changed.add(identifier) - break - if not scope: - break - scope = scope.rsplit("::", 1)[0] if "::" in scope else "" - if declaration is not None and declaration.get("prot") == "private": - private.add(identifier) - pending = list(private) - while pending: - declaration = declarations[pending.pop()].find("compounddef") - for reference in declaration.findall("innerclass"): - identifier = reference.get("refid") - if identifier in declarations and identifier not in private: - private.add(identifier) - pending.append(identifier) - if private: - for compound in list(index.findall("compound")): - if compound.get("refid") in private: - index.getroot().remove(compound) - index.write(index_path, encoding="utf-8", xml_declaration=True) - for identifier, document in declarations.items(): - declaration = document.find("compounddef") - if declaration is None: - continue - hidden = [reference for reference in declaration.findall("innerclass") if reference.get("refid") in private] - for reference in hidden: - declaration.remove(reference) - if hidden: - changed.add(identifier) - for identifier in changed: - declarations[identifier].write( - output / "xml" / (identifier + ".xml"), encoding="utf-8", xml_declaration=True - ) - - -def main() -> int: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument( - "--output", - type=Path, - default=GENERATED_ROOT, - help="Output root containing xml/, html/, and doctrees/ (default: docs/generation/generated).", - ) - parser.add_argument( - "--extract-only", action="store_true", help="Generate Doxygen XML only." - ) - parser.add_argument("--doxygen", help="Use this Doxygen executable explicitly.") - arguments = parser.parse_args() - if arguments.doxygen is not None and os.environ.get("CHERYL_DOCS_FORCE_PINNED") == "1": - parser.error("An explicit Doxygen override cannot be combined with forced pinned tools.") - if os.environ.get("CHERYL_DOCS_TOOLS_ACTIVE") != "1": - overrides = ["--doxygen", arguments.doxygen] if arguments.doxygen is not None else [] - return subprocess.call( - [ - sys.executable, - str(Path(__file__).with_name("bootstrap.py")), - "exec", - *overrides, - "--", - "python", - str(Path(__file__).resolve()), - *sys.argv[1:], - ] - ) - output = arguments.output.expanduser().resolve() - if ((output.is_relative_to(REPO_ROOT / "docs") and not output.is_relative_to(GENERATED_ROOT)) - or (REPO_ROOT / "docs").is_relative_to(output) - or any(root.is_relative_to(output) for root in PUBLIC_INCLUDE_ROOTS) - or output.is_relative_to(GENERATED_ROOT / "api") - or output.is_relative_to(GENERATED_ROOT / "pycache")): - parser.error("The output root must not overwrite authored sources, generated API pages or Python caches.") - doxygen = arguments.doxygen or os.environ.get("CHERYL_DOCS_DOXYGEN") or shutil.which( - "doxygen" - ) - if not doxygen: - parser.error("Doxygen is unavailable; prepare the documentation tools first.") - if arguments.doxygen is not None: - candidate = Path(arguments.doxygen).expanduser() - doxygen = ( - str(candidate.resolve()) - if candidate.is_file() - else shutil.which(arguments.doxygen) - ) - if not doxygen: - parser.error(f"Doxygen executable not found: {arguments.doxygen}") - doxygen_version = validate_doxygen(Path(doxygen)) - commit = revision() - # Every output/profile uses this same lock because Exhale writes source pages. - with file_lock(GENERATED_ROOT / ".generation.lock"): - extract(output, doxygen, commit) - if arguments.extract_only: - return 0 - generated = GENERATED_ROOT / "api" - if generated.exists(): - shutil.rmtree(generated) - for directory in (output / "html", output / "doctrees"): - if directory.exists(): - shutil.rmtree(directory) - environment = os.environ.copy() - environment["CHERYL_DOCS_XML_DIR"] = str(output / "xml") - environment["CHERYL_DOCS_REVISION"] = commit - metadata = provenance(commit, doxygen_version, output) - environment["CHERYL_DOCS_DIRTY"] = "1" if metadata["dirty"] else "0" - subprocess.run( - [ - sys.executable, - "-m", - "sphinx", - "-c", - str(GENERATION_ROOT), - "-b", - "html", - "-W", - "--keep-going", - "-n", - "-w", - str(output / "sphinx-warnings.log"), - "-d", - str(output / "doctrees"), - str(REPO_ROOT / "docs"), - str(output / "html"), - ], - cwd=REPO_ROOT, - env=environment, - check=True, - ) - (output / "html/revision.json").write_text( - json.dumps(metadata, indent=2, sort_keys=True) + "\n", encoding="utf-8" - ) - subprocess.run( - [sys.executable, str(Path(__file__).with_name("check.py")), - "--html", str(output / "html"), "--xml", str(output / "xml")], - cwd=REPO_ROOT, check=True, - ) - return 0 - - -if __name__ == "__main__": - try: - raise SystemExit(main()) - except (OSError, ValueError, RuntimeError, subprocess.CalledProcessError) as error: - print(f"Documentation generation failed: {error}", file=sys.stderr) - raise SystemExit(1) from error diff --git a/docs/getting-started/first-game.md b/docs/getting-started/first-game.md deleted file mode 100644 index 179224a3..00000000 --- a/docs/getting-started/first-game.md +++ /dev/null @@ -1,418 +0,0 @@ -# Your first game - -Start with a small application that opens a window, loads its own sprite and -shaders, and draws the sprite and a score label. The Left and Right arrow keys -move the sprite, Space selects its other image cell, and Escape closes the -application. The score stays at zero while you establish the application and -resource flow. - -The maintained source lives in -[projects/apps/first-game](../../projects/apps/first-game/). The excerpts below -come directly from that example, so you can inspect the complete application as -you work through each step. - -## 1. Set up the checkout and build the application - -Install Git, CMake 3.28 or newer, Ninja, and a C++23 compiler and standard library -with `std::format`. Follow the -[system dependency table](../development/building.md#system-dependencies) for -FreeType, HarfBuzz, ICU, and the native GLFW/OpenGL requirements. On Linux, this -example uses X11 and needs a desktop driver supporting an OpenGL 3.3 core context. -Python and Jinja2 are required when GLAD is generated during the source build. - -Choose a checkout location and a revision matching the documentation you are -reading. Use `main` for stable documentation, `develop` for latest documentation, -or the corresponding release tag for a versioned manual. Replace -`/path/to/cheryl-engine` with your desired checkout location: - -```sh -( - set -e - cheryl_version=main - git clone --branch "$cheryl_version" https://github.com/cppcooper/cheryl-engine.git /path/to/cheryl-engine - cd /path/to/cheryl-engine - git submodule update --init --recursive -) -``` - -The example's PNG, shader sources, and manifests are checked in under its own -`assets` directory. The Engine's embedded fallback font comes from the pinned -assets submodule. This walkthrough needs no optional artwork downloads or asset -initialization script. - -Run the following block from anywhere inside the checkout. It selects only the -native graphics/input composition and this application: - -```sh -( - set -e - cd "$(git rev-parse --show-toplevel)" - cmake -S . -B build/first-game -G Ninja -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ - -DCHERYL_BUILD_FIRST_GAME=ON -DCHERYL_BUILD_DEMO=OFF \ - -DCHERYL_BUILD_NATIVE_GLFW=ON -DCHERYL_BUILD_OPENGL=ON \ - -DCHERYL_NATIVE_INPUT=ON -DCHERYL_NATIVE_NULL_PLATFORM=OFF \ - -DCHERYL_BUILD_UI_TGUI=OFF -DCHERYL_BUILD_UI_RMLUI=OFF \ - -DCHERYL_BUILD_AUDIO_MINIAUDIO=OFF -DCHERYL_BUILD_DEBUG_TERMINAL_LINUX=OFF \ - -DCHERYL_BUILD_WORKER_TOPOLOGY_HWLOC=OFF -DCHERYL_BUILD_WORKER_TOPOLOGY_JSON=OFF \ - -DCHERYL_BUILD_TESTS=OFF -DCHERYL_BUILD_CONSUMER_TESTS=OFF \ - -DCHERYL_BUILD_SDK=OFF -DCHERYL_BUILD_DOCS=OFF \ - -DGLFW_BUILD_WAYLAND=OFF -DGAINPUT_ENABLE_HID=OFF - cmake --build build/first-game --target first-game --parallel - ./build/first-game/first-game -) -``` - -`CMAKE_POLICY_VERSION_MINIMUM=3.5` accommodates the pinned legacy dependency -projects on CMake 4 hosts. `GLFW_BUILD_WAYLAND=OFF` selects the Linux X11 route; -`GAINPUT_ENABLE_HID=OFF` avoids the separate HID dependency download. See the -[build reference](../development/building.md) for other platform and toolchain -choices. Windows executables have an `.exe` suffix. - -The expected result is a 640 × 360 window containing a cyan sprite and -"Score: 0". Space changes the sprite to amber. Check movement, cell selection, -resizing, and Escape on the desktop; compilation and command-line help alone do -not establish those graphics and input observations. - -## 2. Start the engine and keep its owners alive - -The application's entry point creates -{cpp:class}`CE::Application::Startup` through -{cpp:func}`CE::Application::GLFWOpenGL::make_startup`. That factory selects the native -GLFW window/input adapter and the OpenGL renderer and resource provider. - -```{literalinclude} ../../projects/apps/first-game/src/main.cpp -:language: cpp -:start-after: // [startup] -:end-before: // [/startup] -:dedent: 8 -``` - -Register your own options before calling `initialize()`. Here, `--assets` binds a -string that stays alive until parsing completes. Its default is an absolute path -supplied by the example's CMake target, so launching the executable from another -directory still selects the same assets. An explicit relative override resolves -from the application's working directory. - -Help and argument errors return a {cpp:struct}`CE::Application::StartupResult` with no -context; return its exit code when `should_start()` is false. On success, construct -your game from the owned context and use `make_runtime(game)` to apply the parsed -execution, input polling, and timing options together. - -Declare the result, game, and runtime in that order. The game borrows the -{cpp:class}`CE::Runtime::Context`, and the runtime borrows both. Their -declaration order keeps those owners alive through runtime destruction. Add -`--concurrent` when launching to place simulation on a separate thread; the same -game hooks and resource ownership rules apply. - -## 3. Give the game its hooks and controls - -Implement {cpp:struct}`CE::Game::AbstractGame` and keep the state your game -needs in that object. The example owns its camera, bindings, resources, and actor: - -```{literalinclude} ../../projects/apps/first-game/src/first-game.h -:language: cpp -:start-after: // [game] -:end-before: // [/game] -:dedent: 4 -``` - -| Hook | What belongs here | -| --- | --- | -| `init()` | Bind controls and create initial resources after the platform and graphics adapters start. | -| `update(tick)` | Advance game state using the supplied input and simulation delta. | -| `prepare_render_frame(frame) const` | Resolve that state into complete render submissions. | -| `quiesce()` | Stop external producers before final cleanup when your game adds them. | -| `deinit()` | Release game-owned resources and registrations after simulation and accepted work settle. | - -`init()` and `deinit()` run on the platform/graphics owner. In concurrent mode, -`update()` and frame preparation run on the simulation thread. Bindings belong to -the platform owner, so define them during initialization: - -```{literalinclude} ../../projects/apps/first-game/src/first-game.cpp -:language: cpp -:start-after: // [bindings] -:end-before: // [/bindings] -:dedent: 8 -``` - -The native key codes appear at this boundary. Gameplay reads the game's semantic -{cpp:struct}`CE::Input::Actions::Id` values instead. Retaining each returned binding -ID lets the game remove its own mappings during cleanup. The -[input guide](../runtime/input-state-model.md) covers additional actions, events, -and focus handling. - -## 4. Load a controlled game asset directory - -Put the resources required by this game under one root. The example uses: - -```text -assets/ -└── graphics/ - ├── graphics-manifests.json - ├── definitions/sprites.json - ├── shaders/materials.json - ├── shaders/first-game.vert - ├── shaders/first-game.frag - └── textures/actor.png -``` - -The index selects the sprite and shader/material definitions: - -```{literalinclude} ../../projects/apps/first-game/assets/graphics/graphics-manifests.json -:language: json -``` - -Only files named `graphics-manifests.json` select definitions automatically. -Unlisted JSON files are ignored. The listed paths, texture references, and shader -source references resolve from the enclosing `graphics` directory. The `$schema` -URLs support editor validation; runtime loading uses local files. - -Create an owned {cpp:struct}`CE::Assets::Loading::Loader` and load the root during `init()`: - -```{literalinclude} ../../projects/apps/first-game/src/first-game.cpp -:language: cpp -:start-after: // [assets] -:end-before: // [/assets] -:dedent: 8 -``` - -`load_assets()` performs CPU preparation followed by upload on the active -provider's loading owner. It processes indexed programs/materials, images, -sprites, and tilesets, along with standalone PNGs below the root. Fonts and audio -use separate loading paths. Preparation validates definitions and image bounds -before publication; an upload failure can leave already completed cache entries. - -Use a controlled root containing just this game's required assets. Pointing the -general loader at the repository's larger asset collection also selects its -optional downloaded artwork. `register_files()` only registers paths for manual -loading; it does not parse definitions, decode images, or upload resources. -The [asset-loading guide](../assets/asset-loading.md) explains publication and -retry behavior. - -Resolve required named resources during initialization and check each result. -{cpp:struct}`CE::Assets::Sprites::Manager` returns a retained sprite handle or null; -{cpp:struct}`CE::Assets::Shaders::Manager` supplies the materials used for drawing. -For this manifest, `get_asset("first-game", "actor")` identifies -`first-game:actor` exactly. `get_assets("first-game")` provides a namespace -collection keyed by local names when your game needs several sprites. - -## 5. Pass retained resources into game-owned state - -The sprite is shared resource data. The actor owns its position and selected -cell, while retaining the sprite passed into its constructor: - -```{literalinclude} ../../projects/apps/first-game/src/first-game.h -:language: cpp -:start-after: // [actor] -:end-before: // [/actor] -:dedent: 4 -``` - -The `actor_.emplace(...)` call above transfers a retained handle into this small -game-owned structure. Use the same approach when an enemy manager or another -game system needs a sprite collection: resolve the required names once, then -pass the handles into that system. Keep per-entity transforms, counters, and -playback state with their application owners. A namespace collection's iteration -order supplies no gameplay identity. - -The loader can go out of scope after initialization. Cache lookups and the -actor's retained handles have their own lifetimes. The resources still belong to -their original backend domain; retaining a handle does not make it usable after -that backend has been destroyed. See the -[resource ownership guide](../resources/resource-residency.md). - -## 6. Select a font and prepare the score label - -Font selection chooses immutable font bytes and faces. It does not draw text or -upload a glyph atlas. Select a preferred installed family, lay out the message, -prepare its pixels and geometry, then upload that complete value: - -```{literalinclude} ../../projects/apps/first-game/src/first-game.cpp -:language: cpp -:start-after: // [score] -:end-before: // [/score] -:dedent: 8 -``` - -{cpp:class}`CE::Text::FontCollection` tries the preferred `DejaVu Sans` family -and appends the embedded fallback. `automatic_system_fonts=false` disables the -additional automatic family choices; the explicitly preferred installed family -is still considered. A missing family is skipped, so the score remains usable -without that system installation. - -{cpp:func}`CE::Text::layout_text` shapes the UTF-8 message using the selected -font data. {cpp:func}`CE::Text::Rendering::prepare` creates owned CPU atlas pages and -glyph geometry. {cpp:func}`CE::Text::Rendering::upload` uses the resource provider to -create a {cpp:class}`CE::Text::Rendering::Generation` generation. Initial upload is safe -here because `init()` runs on the graphics owner. - -The retained text owns the layout and matching resources it needs after the local -font and preparation variables leave scope. Keep this static label as one -generation. When gameplay starts changing the score, prepare a replacement and -adopt it only when upload succeeds. The -[Unicode text guide](../assets/text-layout.md) covers fallback, wrapping, and -replacement ownership. - -## 7. Update simulation and select image cells - -Use {cpp:struct}`CE::Game::TickContext` during each update. It supplies the -simulation delta, accumulated semantic input, and copied window dimensions: - -```{literalinclude} ../../projects/apps/first-game/src/first-game.cpp -:language: cpp -:start-after: // [update] -:end-before: // [/update] -:dedent: 4 -``` - -Movement uses `delta_seconds`, and the camera follows the tick's framebuffer -size. This supports concurrent mode without querying a live window from the -simulation thread. Escape requests a stop through the tick; the runtime performs -its normal cleanup after the update returns. - -The sprite definition divides the 32 × 16 PNG into two 16 × 16 cells: - -```{literalinclude} ../../projects/apps/first-game/assets/graphics/definitions/sprites.json -:language: json -``` - -A grid describes which image rectangle each cell selects. Space advances the -actor's cell index explicitly. This manifest supplies no timed animation clip. -For a sprite with authored timed clips, keep a separate -{cpp:class}`CE::Assets::Sprites::Animation` cursor per entity and advance it using -simulation time. The shared cached sprite holds definitions and resources; -its entities choose playback. See -[asset values and playback](../assets/asset-values-and-playback.md). - -## 8. Submit the sprite and text as a complete frame - -Frame preparation copies the simulation's current transforms and selects -retained resources. The renderer consumes those resolved packets later: - -```{literalinclude} ../../projects/apps/first-game/src/first-game.cpp -:language: cpp -:start-after: // [render] -:end-before: // [/render] -:dedent: 4 -``` - -{cpp:class}`CE::Rendering::Camera2D` uses Y-up pixel coordinates. Opening a pass copies its -projection and view matrices. The -{cpp:struct}`CE::Rendering::Submission::Context2D` carries those pass values and -selects the public `image` parameter at texture unit zero. Each -{cpp:struct}`CE::Rendering::DrawStyle2D` chooses a material and supplies the -draw's model transform, scale, and alpha. - -The checked-in -[material manifest](../../projects/apps/first-game/assets/graphics/shaders/materials.json) -gives the shared shader program two compatible draw recipes: - -| Material | Geometry topology | Why it is separate | -| --- | --- | --- | -| `first-game:images` | Triangle strip | Each sprite cell uses a four-vertex strip, with nearest sampling for the small image. | -| `first-game:text` | Triangles | Each uploaded glyph uses a six-vertex quad, with linear sampling for its alpha atlas. | - -The vertex shader applies the copied pass and draw transforms: - -```{literalinclude} ../../projects/apps/first-game/assets/graphics/shaders/first-game.vert -:language: glsl -``` - -Material bindings map public parameter keys to the shader's native uniform names. -Your common submission code uses `image`, while this OpenGL recipe maps it to -`mytexture`. {cpp:func}`CE::Rendering::Submission::resolve_sprite` and -{cpp:func}`CE::Rendering::Submission::resolve_text CE::Rendering::Submission::resolve_text(const CE::Text::Rendering::Generation&, const DrawStyle2D&, const Context2D&)>` -resolve complete CPU packets without binding -graphics state or uploading resources. Adding those packets to the pass retains -their geometry, materials, and selected images and copies their parameter values. -The renderer receives no borrowed actor, camera, or playback cursor. The -[pipeline and material guide](../rendering/pipelines-and-materials.md) explains -the contracts and additional recipes. - -## 9. Release the game's resources - -The runtime joins simulation and recycles accepted frames before calling -`deinit()` on the platform/graphics owner. Remove the game's mappings and release -its retained generations there: - -```{literalinclude} ../../projects/apps/first-game/src/first-game.cpp -:language: cpp -:start-after: // [cleanup] -:end-before: // [/cleanup] -:dedent: 4 -``` - -Cleanup also follows attempted initialization, so these resets tolerate resources -that were never created. The example uses the default `quiesce()` because it has -no external producers. Games that add listeners, audio producers, or background -services should stop those producers in `quiesce()` while their dependencies -remain alive. The [runtime frame boundary](../runtime/runtime-frame-boundary.md) -defines the complete shutdown ordering. - -## Continue with background loading - -The synchronous path makes initial ownership clear. For larger loads, use an -Engine worker group for `Loader::prepare()` and submit the resulting owned -`CE::Assets::Loading::Prepared` to the platform dispatcher for `upload()`. Simulation checks -futures for readiness and adopts complete retained results during update. Keep -the loader alive through preparation and handoff, and keep the prior resource -generation while its replacement is pending. Avoid waiting on the thread that -must complete the upload. - -Text follows the same boundary: font/layout preparation can run on workers; -upload remains on the graphics owner. Follow the working sequences in -[asset loading](../assets/asset-loading.md), -[worker execution](../runtime/worker-execution.md), and -[thread dispatch](../runtime/thread-dispatch.md) when you add that flow. This -example keeps initialization synchronous and frame preparation CPU-only. - -## Use the example as an independent project - -The example's own CMake entry point supports source and installed SDK consumers. -Its application target links the public owners explicitly: - -```{literalinclude} ../../projects/apps/first-game/CMakeLists.txt -:language: cmake -:start-at: add_executable -:end-before: Source Debug -``` - -To configure the example directly against the checkout, use a separate build -directory. Its bootstrap selects the required Engine, Native GLFW, OpenGL, and -startup targets and disables unrelated applications and modules: - -```sh -( - set -e - cd "$(git rev-parse --show-toplevel)" - cmake -S projects/apps/first-game -B build/first-game-source -G Ninja \ - -DCMAKE_BUILD_TYPE=Release -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ - -DCHERYL_FIRST_GAME_ENGINE_SOURCE="$PWD" \ - -DGLFW_BUILD_WAYLAND=OFF -DGAINPUT_ENABLE_HID=OFF - cmake --build build/first-game-source --target first-game --parallel - ./build/first-game-source/first-game -) -``` - -For an installed SDK, leave `CHERYL_FIRST_GAME_ENGINE_SOURCE` empty and supply a -compatible SDK through `CMAKE_PREFIX_PATH`. It must include Native GLFW with -native input, OpenGL, and OpenGL startup. The SDK contains libraries and headers; -keep this example's source and `assets` directory alongside its CMake project: - -```sh -( - set -e - cd "$(git rev-parse --show-toplevel)" - cmake -S projects/apps/first-game -B build/first-game-sdk -G Ninja \ - -DCMAKE_BUILD_TYPE=Release -DCHERYL_FIRST_GAME_ENGINE_SOURCE= \ - -DCMAKE_PREFIX_PATH=/absolute/path/to/extracted-sdk - cmake --build build/first-game-sdk --target first-game --parallel - ./build/first-game-sdk/first-game -) -``` - -The application's compiled default asset path names its source `assets` -directory. Use `--assets` when relocating those files with a packaged application. -For SDK ABI/configuration matching and Windows runtime DLL paths, follow -[consuming the engine](../development/consuming-engine.md#prebuilt-development-sdks). diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index 3847726b..00000000 --- a/docs/index.md +++ /dev/null @@ -1,90 +0,0 @@ -# Cheryl Engine - -Build a game with Cheryl's application lifecycle, input actions, retained assets, -and rendering interfaces. Follow the first-game walkthrough to assemble a small -application, then use the task guides and generated reference as you add features. - -```{toctree} -:maxdepth: 2 -:caption: Getting started - -First game -Project setup -``` - -```{toctree} -:maxdepth: 1 -:caption: Assets - -Loading -Manifests -Types -Discovery -Text -Text encoding -Packages -Legacy fonts -``` - -```{toctree} -:maxdepth: 1 -:caption: Rendering - -Drawing -``` - -```{toctree} -:maxdepth: 1 -:caption: Resources - -Caches -Memory -``` - -```{toctree} -:maxdepth: 1 -:caption: Runtime contracts - -Architecture -Lifecycle -Timing -Display -Input -Events -Dispatch -Workers -Audio -Logging -Diagnostics -Failures -``` - -```{toctree} -:maxdepth: 1 -:caption: Reference - -C++ API -Utilities -``` - -```{toctree} -:maxdepth: 1 -:caption: Development - -Build -Modules -UI adapters -Documentation -Contributing -Code style -``` - -```{toctree} -:maxdepth: 1 -:caption: Validation - -Tests -Architecture -Logging -Desktop -``` diff --git a/docs/planning/README.md b/docs/planning/README.md index 9d6310cc..1870aff4 100644 --- a/docs/planning/README.md +++ b/docs/planning/README.md @@ -2,19 +2,39 @@ Horizons describe sequencing, not delivery dates. Detailed checklists live in the plan owning each active task; current contracts live in the [subject guides](../README.md). -The [status inventory](../STATUS.md) records implementation classifications. -The [roadmap](develop-review-and-development-plan.md#nearest-planned-work) owns the -nearest work and acceptance checkpoints, -and [testing requests](../testing-requests.md) contains pending user-run acceptance. - -| Folder | Scope and owning plans | -| --- | --- | -| `short-term/` | [Unicode font-selection regressions](short-term/unicode-text.md). The roadmap owns [startup/UI follow-up](develop-review-and-development-plan.md#startup-and-ui-follow-up) and links pending main-push SDK collection, terminal desktop QA and demo acceptance to the testing queue. Completed cache-locality source work lives in the worker guide. | -| `mid-term/` | [Planned color emoji and reorder-safe sorting, with deferred general batching and measured text/timing optimization](mid-term/README.md); design and workload prerequisites remain | -| `long-term/` | [Planned RmlUi effects, alternative audio and the graphical console alongside deferred integrations, NUMA and consumer-selected extensions](long-term/README.md), [deferred HID/platform acceptance](long-term/platform-acceptance.md) and [unresolved artwork metadata](long-term/asset-manifest-metadata.md) | - -The roadmap owns the three selected -[GoogleTest, nlohmann/json and spdlog dependency reviews](develop-review-and-development-plan.md#dependency-reviews). +The [status inventory](../STATUS.md) owns implementation classifications, selected +work and deferred review order. [Testing requests](../testing-requests.md) owns +ready pending user-run acceptance; the plans below retain unfinished work and +its prerequisites. + +## Find the owning plan + +Use this table to reach the actual requirements. Selection and deferred phase order +remain in STATUS; a horizon is not a delivery commitment. + +| Horizon | Plan or section | Next checkpoint | +| --- | --- | --- | +| Deferred follow-up | [Event capabilities](short-term/engine-events.md) | Selected non-font integration is implemented. Select a consumer before font catalogue, independent native pumping or further device/OS event work. | +| Short term | [Font-selection regressions](short-term/README.md#font-selection-regressions) | Cover automatic weight boundaries and explicit preferences. | +| Short term | [Startup and UI follow-up](short-term/README.md#startup-and-ui-follow-up) | Cover successful context/runtime handoff, callback/batch and placeholder cases; obtain a corrected supplied RmlUi host. | +| Short term | [Upstream placeholder follow-up](short-term/README.md#upstream-placeholder-follow-up) | Reproduce against unmodified upstream code before proposing a regression or submission. | +| Selected review | [Dependency reviews](#dependency-reviews) | Assess GoogleTest, nlohmann/json and spdlog independently; recommend exact pins. | +| Mid term | [Color emoji](mid-term/README.md#color-emoji) | Select formats and sequences; settle RGBA pages, materials and fallback. | +| Mid term | [Reorder-safe sorting](mid-term/README.md#reorder-safe-render-sorting) | Identify reorderable regions and a representative workload. | +| Mid term | [General batching](mid-term/README.md#general-render-batching) and [measured optimization](mid-term/README.md#measured-optimization) | Gather measurements before selecting an implementation. | +| Long term | [UI extensions](long-term/README.md#ui-adapters) | Settle RmlUi effects and required rendering/backend contracts. | +| Long term | [Alternative audio](long-term/README.md#alternative-audio-sdk) and [graphical console](long-term/README.md#graphical-developer-console) | Settle SDK/deployment or command/history/input requirements. | +| Long term | [Native integration](long-term/README.md#deferred-native-integration) and [platform acceptance](long-term/platform-acceptance.md) | Select a consumer/platform and supply the backend or observation prerequisites. | +| Long term | [Steam services and input](long-term/README.md#steam-api-and-input-integration) | Establish SDK-free input/session policy before selecting transport. | +| Long term | [Residency/cache domains](long-term/README.md#residency-accounting) and [monitor hotplug](long-term/README.md#monitor-hotplug) | Settle accounting or inventory identity/lifetime before adding policy. | +| Long term | [Atlas padding](long-term/README.md#atlas-padding-and-repacking) and [NUMA locality](long-term/README.md#numa-locality) | Select scope for padding; NUMA remains indefinitely deferred. | +| Long term | [Artwork metadata](long-term/asset-manifest-metadata.md) | Resolve source metadata and consumer-specific manifest choices. | +| Long term | [Additional dungeon manifests](long-term/README.md#additional-dungeon-asset-manifests) | Inventory acquired packages and author each manifest independently. | + +For implemented work awaiting execution, use [STATUS's acceptance summary](../STATUS.md#remaining-acceptance) +and the [testing queue](../testing-requests.md). Those procedures are not development +plans. + Deferred phases in the status inventory describe review order within that section; they do not replace these subject horizons or authorize implementation. @@ -24,3 +44,36 @@ The current RmlUi core integration is sufficient for the selected Linux scope; [rendering effects](long-term/README.md#ui-adapters) are planned, including any required backend support. OS services and additional toolkit selection remain deferred. + +## Dependency reviews + +The three reviews selected in [STATUS](../STATUS.md#2-planned) concern GoogleTest, +nlohmann/json and spdlog. Keep them independent. + +For each dependency, compare its pinned commit with upstream history and candidate +revisions, including changed ancestry and tag/commit availability. Review release +notes, integration/API changes, CMake targets/options, supported compilers and +license changes. Check Cheryl-owned source and supplied target/package paths where +applicable. Recommend an exact reproducible candidate or retention of the current +pin; a review does not itself select an update. + +If an update is selected, keep each submodule change a separate development unit. +Design focused acceptance for affected owners, independent consumers and the +Linux/Windows CI configurations. Preserve unrelated pins and local dependency work. + +## Topics awaiting plans + +The [deferred STATUS entries](../STATUS.md#3-deferred) include topics with no +concrete plan yet: expanded manifest formats and automatic asset hot reload. +ECS, tilemap/world, physics, save serialization, 3D and networking currently have +only a [consumer-required deferral](long-term/README.md#game-systems). Spatial +audio and OS/UI services likewise need specific design and acceptance checkpoints +beyond their [audio](long-term/README.md#alternative-audio-sdk) and +[UI](long-term/README.md#ui-adapters) scope notes. + +When one is selected, establish its consumer, scope, contracts, prerequisites and +observable acceptance in an owning plan, then link that plan from this index and +STATUS. Conditional possibilities mentioned in a plan remain unselected until +promoted explicitly. When an actual plan completes, preserve its resulting +contracts and unresolved acceptance, remove the completed plan and reconcile both +indexes. diff --git a/docs/planning/develop-review-and-development-plan.md b/docs/planning/develop-review-and-development-plan.md deleted file mode 100644 index 7ed10a73..00000000 --- a/docs/planning/develop-review-and-development-plan.md +++ /dev/null @@ -1,156 +0,0 @@ -# Development roadmap - -The [planning catalogue](README.md) groups work by horizon. Linux/GLFW/X11/OpenGL -is the active native platform; deferred platform coverage does not gate Linux work. -Current architecture and API contracts live in the [subject guides](../README.md). - -## Nearest planned work - -This roadmap owns the nearest selected work; the catalogue and status inventory -link here. Priorities do not authorize implementation, builds or mode transitions. -The selected cache-locality → positioned-text → CI/SDK source work is complete. -Its contracts live in the [worker guide](../runtime/worker-execution.md), -[text guide](../assets/text-layout.md#positioned-text-runs) and -[build/CI guide](../development/building.md#github-ci-and-sdk-artifacts). - -### Selected planned work - -The following work is selected for investigation and design. Dependency reviews -remain three independent tasks; each feature needs its own settled scope and -acceptance before implementation. This selection does not promote general packet -batching or the other deferred integrations. - -| Work | Owning scope and next checkpoint | -| --- | --- | -| GoogleTest submodule review | [Dependency review](#dependency-reviews): compare upstream history with the current pin, assess test/CMake/toolchain changes and recommend retaining the pin or selecting an update. | -| nlohmann/json submodule review | [Dependency review](#dependency-reviews): assess parsing, serialization, configuration and source/package compatibility against upstream candidates. | -| spdlog submodule review | [Dependency review](#dependency-reviews): assess logger/sink behavior, formatting dependencies, exported targets and source/package/toolchain compatibility. | -| RmlUi rendering effects | [UI extension scope](long-term/README.md#ui-adapters): settle masks, transforms, layers, filters, custom shaders and repeating coordinates, including required neutral-contract and graphics-backend support. | -| Reorder-safe render sorting | [Sorting scope](mid-term/README.md#reorder-safe-render-sorting): identify reorderable regions and measure a policy that preserves authored order where required, clipping and retained generations. | -| Color emoji glyphs | [Emoji scope](mid-term/README.md#color-emoji): select color-font formats and sequences, then settle RGBA pages, materials, fallback and CPU/native acceptance. | -| Alternative audio SDK | [Audio scope](long-term/README.md#other-engine-extensions): select an SDK, assess licensing/deployment and preserve the ordinary playback contract; any richer authoring model needs explicit scope. | -| In-game developer console | [Console scope](long-term/README.md#other-engine-extensions): settle graphical output, commands, scrolling/history and input routing independently of the native terminal. | - -The current RmlUi document/font/input/clipping and retained-upload integration -remains sufficient for the selected Linux root composition. Its planned rendering -effects include any required backend work; the -[startup/UI follow-up](#startup-and-ui-follow-up) keeps regression and supplied-Core -acceptance separate from extending that integration. - -### Dependency reviews - -For each dependency, compare its pinned commit with upstream history and candidate -revisions, including changed ancestry or tag/commit availability. Review relevant -release notes, integration/API changes, CMake targets/options, supported compilers -and license changes. Check both Cheryl-owned source and supplied target/package -paths where applicable. Recommend an exact reproducible candidate or retention of -the existing pin; reviewing history does not automatically authorize an update. - -If an update is selected, keep each submodule change a separate development unit -and design focused acceptance for the affected owners, independent consumers and -Linux/Windows CI configurations. Preserve unrelated pins and local dependency work. - -### Remaining acceptance - -Remaining acceptance has independent prerequisites: - -| Work | Next phase and checkpoint | -| --- | --- | -| Main-push SDK collection | Hosted Ubuntu/Windows builds and relocated SDK consumers are accepted. Verify the first successful main-push collection gate in [TR20](../testing-requests.md#tr20-automated-main-push-sdk-collection). PR/develop/manual runs intentionally skip collection. | -| Hosted Windows skipped cases | Supply a CFF font fixture and a temporary directory on the checkout drive, then accept the affected font-allocation and logging cases on hosted Windows. The [CI guide](../development/building.md#hosted-windows-coverage-limits) retains their scope and prerequisites. | -| Linux Debug terminal | Complete real-desktop [TR18](../testing-requests.md#tr18-qa-linux-debug-terminal-on-a-real-desktop) using matching binaries from the accepted configuration matrix. Observe actual emulator windows and desktop/IDE application launch. | -| Demo material/artwork QA | Complete shader-pair failure/recovery and available artwork observations in [TR19](../testing-requests.md#tr19-qa-indexed-demo-materials-and-recovered-graphics). Missing images/showcase fixtures remain explicit blockers. | - -Sampling/TGUI and indexed shader/material loading have selected Linux CPU/backend -and native pixel acceptance. Only main text/image materials migrated; both UI -adapters retain [manual builders](../rendering/pipelines-and-materials.md#manual-material-construction). -The reusable [graphics procedure](../development/architecture-validation.md#sampling-and-positioned-text) -preserves coverage limits. Completed implementation plans are retired after their -contracts and unresolved acceptance have moved to the subject guides/testing queue. - -NUMA discovery and memory placement remain [indefinitely deferred](long-term/README.md#other-engine-extensions). -General packet batching remains deferred under the -[mid-term scope](mid-term/README.md#general-render-batching). Reorder-safe sorting -is selected separately; positioned text runs do not implement either policy. -[Release publication](long-term/README.md#other-engine-extensions) -remains a future decision: current SDK outputs are workflow artifacts and contain -prebuilt development libraries/headers, not applications or a demo package. - -## Work-mode progression - -Finish each workstream's authorized phase before advancing: investigation and -research, architecture and design, test design, implementation/refactoring/tooling, -test implementation, test execution, then verification. Complete and commit coherent -implementation units independently. Keep documentation maintenance separate and -recommend synchronization when contracts stabilize; testing-request maintenance -also requires authorization. - -User-directed progression remains the default under [AGENTS.md](../../AGENTS.md). -A bounded plan-directed sequence may be approved, with mandatory checkpoints for -unresolved design decisions, changed scope or protected unfinished user work. -Builds and test execution still require explicit authorization. Source completion -and executable/native acceptance remain distinct. - -## Existing regression and composition follow-up - -The remaining [Unicode font-selection regressions](short-term/unicode-text.md) -and startup/UI checklist below are independent testing/composition objectives. -They are not blanket pending QA for the accepted Linux baseline. Select them in -their own work modes rather than mixing them into integration implementation. -The [testing queue](../testing-requests.md) owns ready pending acceptance; -the [demo guide](../../projects/apps/demo/README.md#repeating-native-qa) preserves -reusable native procedures and coverage limits. Existing regressions do not close -the dedicated-case gaps listed in the plans. - -### Startup and UI follow-up - -The [application guide](../development/consuming-engine.md#command-line-startup) -owns startup/result lifetimes and option semantics; the UI guides own -[batch routing](../development/ui-adapters.md#routing-and-unavailable-services) and -[RmlUi correction declarations](../../projects/modules/ui/rmlui/README.md#placeholder-dependency-contract). -Font-weight acceptance remains with the Unicode plan rather than this checklist. - -- [x] Add common/backend startup support and migrate the demo's application options. -- [x] Preserve session entry epochs across complete input batches with per-record controls. -- [x] Correct RmlUi field focus, placeholder markup and vertical list rows; apply the - downstream source guard and require correction declarations for supplied Core. -- [x] Compile Engine-only/native UI selections and accept existing font/layout/resource - and session/coexistence checks with Cheryl-owned dependencies. -- [x] Compile independent Engine/OpenGL Startup first-include probes through the - installed SDK consumer assembly. -- [ ] Add dedicated Startup parser/factory, callback/batch routing and placeholder - hit-testing regressions in separately authorized - testing phases. Existing suites do not exercise these new paths directly. -- [x] Accept Linux native startup/field/focus/list observations in the selected - root composition; [demo QA](../../projects/apps/demo/README.md#startup-and-ui-validation) - retains the procedure and coverage limits. -- [ ] Obtain a corrected supplied RmlUi target/package and consuming host, then - accept declaration/rejection behavior independently of Cheryl-owned source. - -The [upstream investigation](../external-work/rmlui-placeholder-issue.md) retains -the unmodified reproduction, sanitizer and submission prerequisites. Downstream -demo success does not close that work. Dynamic backend selection remains outside -the current programmatic startup contract and has no selected implementation phase. - -## Deferred integrations - -Multiple active rendering windows and HID integration belong to the -[long-term native plan](long-term/README.md#deferred-native-integration). -Toolkit OS services remain [deferred UI extensions](long-term/README.md#ui-adapters). - -### Deferred HID integration - -The [long-term HID section](long-term/README.md#deferred-hid-integration) owns the -backend deferral, Gainput handoff and observation prerequisites. -[TR6](long-term/platform-acceptance.md#deferred-hid-lifecycle-and-windows-notifications) -is deferred there, outside the active testing queue. - -## Later work - -Use the [horizon catalogue](README.md) for work beyond the nearest sequence. -Color emoji and reorder-safe sorting are selected above, with their design and -workload prerequisites retained. General batching and text/timing optimization -remain deferred. RmlUi effects, alternative audio and the graphical console are -also selected; platform, Steam and other optional modules retain their existing -consumer and prerequisite requirements. Deferred phases in the -[status inventory](../STATUS.md#3-deferred) describe review order within that section. diff --git a/docs/planning/long-term/README.md b/docs/planning/long-term/README.md index dcbb6389..e8ef917d 100644 --- a/docs/planning/long-term/README.md +++ b/docs/planning/long-term/README.md @@ -3,7 +3,7 @@ This guide retains the scope and prerequisites for deferred integrations and consumer-driven extensions. RmlUi rendering effects, an alternative audio SDK and the in-game developer console are now selected in the -[planned roadmap](../develop-review-and-development-plan.md#selected-planned-work). +[status inventory](../../STATUS.md#2-planned). Other unselected engine and module extensions still need a named application consumer to establish scope and prerequisites. Additional asset manifests below remain deferred to the long term. Steam API services, Steam Input and their @@ -12,11 +12,11 @@ Multi-platform testing is also shelved; Linux/X11 remains the active short-term platform. Use the current ownership, lifetime, selection and standalone contracts in the -[module guide](../../development/modules.md). Its -[module boundary criteria](../../development/modules.md#module-boundary-criteria) +[module guide](../../site/development/modules.md). Its +[module boundary criteria](../../site/development/modules.md#module-boundary-criteria) govern new owners. Reusable isolation/composition procedures and native/fixture coverage limits are in -[architecture validation](../../development/architecture-validation.md). +[architecture validation](../../site/development/architecture-validation.md). ## Multi-platform acceptance @@ -40,7 +40,7 @@ are selection prerequisites; no mobile or console module is implemented. ### Multiple active rendering windows Multiple active rendering windows and switching the selected rendering window are -deferred. The [display/window contract](../../runtime/display-and-window-contract.md) +deferred. The [display/window contract](../../site/runtime/display-and-window-contract.md) allows a display to own several windows but permits one active rendering selection; replacement of that selection rejects. Window ownership alone does not establish multiple graphics contexts, presentation surfaces or independent resource domains. @@ -57,7 +57,7 @@ Windows worker CPU affinity is deferred to phase 4 of the [status inventory](../../STATUS.md#3-deferred), alongside platform acceptance and OS services. The current Windows hwloc provider retains CPU/core/cache discovery but reports thread binding unavailable because its backend rejects -`HWLOC_CPUBIND_NOMEMBIND`. The [worker contract](../../runtime/worker-execution.md#cpu-policy-and-capabilities) +`HWLOC_CPUBIND_NOMEMBIND`. The [worker contract](../../site/runtime/worker-execution.md#cpu-policy-and-capabilities) requires CPU binding to preserve memory placement; preferred policies use portable scheduling and required CPU/cache locality requests reject. @@ -92,7 +92,7 @@ so explicit startup-failure reporting requires a contract change if selected. Broader native ownership requires a consumer case. Battery and touch/motion exposure remain separate from the handoff's initial scope; use the -[native capability limits](../../../projects/modules/platform/native-glfw/README.md#hid-capability-and-platform-scope) +[native capability limits](../../site/modules/native-glfw.md#hid-capability-and-platform-scope) when selecting extensions. Windows/macOS source, fallback and notification work remains with the deferred platform scope. @@ -144,7 +144,7 @@ a concrete consumer requirement. Coordinate process-wide Gainput HID collection, shutdown and notification ownership before supporting them; independent `Init`/`Exit` calls cannot establish that behavior. Preserve one collection path per effective controller. The current -[native adapter lifetime](../../../projects/modules/platform/native-glfw/README.md#input-lifetime-and-mapping) +[native adapter lifetime](../../site/modules/native-glfw.md#input-lifetime-and-mapping) permits one initialized owner and retains its original Windows notification window. Explicit HID startup-failure reporting follows the [deferred HID contract](#deferred-hid-integration); ownership checks do not supply @@ -158,12 +158,12 @@ The RmlUi core integration supplies native documents/fonts, keyboard/text/pointe input, rectangular clipping, retained CPU/GPU scenes and lifecycle handling. Its selected Linux root-composition baseline remains usable while rendering effects are planned. Existing dedicated regressions and supplied-Core acceptance remain in -the [startup/UI follow-up](../develop-review-and-development-plan.md#startup-and-ui-follow-up). +the [startup/UI follow-up](../short-term/README.md#startup-and-ui-follow-up). RmlUi masks, nonidentity transforms, offscreen layers, filters, custom shaders and repeating texture coordinates are planned extensions and currently reject publication under the -[current recording contract](../../../projects/modules/ui/rmlui/README.md#recording-and-publication). +[current recording contract](../../site/modules/rmlui.md#recording-and-publication). Determine which effects can be implemented by the adapter and which require changes to neutral render/resource contracts and the graphics backend. Include necessary OpenGL/backend support in the selected task, covering render targets, @@ -181,7 +181,7 @@ authoring API. Similarity to existing bridges does not require shared widgets. Each toolkit remains independently selectable and depends on neutral Engine contracts; toolkit code must not become an Engine dependency. TGUI and RmlUi establish the current boundary in the -[adapter-author guide](../../development/ui-adapters.md). +[adapter-author guide](../../site/development/ui-adapters.md). ### Additional graphics backends @@ -193,24 +193,28 @@ omit/replace/test benefit; a seam alone does not justify it. ## Other engine extensions +### Graphical developer console + The built-in graphical developer console is planned as the engine's own graphical interface. Its scope consists of that console, with the renderer supplying its rendering; other application UI remains consumer-owned. It supplies eventual in-app console output independently of whether an application selects the optional native terminal. Establish scrolling/history, input routing and command requirements before -implementation. Until then, Release uses its existing logging destinations. The -implemented [Linux Debug terminal](../../../projects/modules/platform/debug-terminal-linux/README.md) uses a native terminal and can -provide diagnostics independently of game rendering; closing it leaves the -application running. +implementation. Keep it independent of the optional +[native terminal](../../site/modules/debug-terminal-linux.md). + +### Text coverage -Beyond the initial [grayscale scope](../../assets/text-layout.md#native-acceptance-scope), +Beyond the initial [grayscale scope](../../site/assets/text-layout.md#native-acceptance-scope), consider wider CJK/script acceptance when a consumer establishes its required fonts, language-specific shaping, line-breaking and coverage fixtures. Color emoji is a planned [nearer text follow-on](../mid-term/README.md#color-emoji). Neither belongs to the current grayscale Latin/Cyrillic and bidi batch. -The [miniaudio module](../../../projects/modules/audio/miniaudio/README.md) +### Alternative audio SDK + +The [miniaudio module](../../site/modules/miniaudio.md) implements ordinary playback with an accepted Linux root-assembly WAV baseline. An alternative audio SDK/module is planned; select its SDK and assess licensing, runtime/dependency deployment, source/package integration and coexistence before @@ -221,12 +225,16 @@ define consumer/native acceptance for the chosen alternative. Spatial audio, effects graphs, capture, device enumeration/hotplug and custom codecs also need separately selected consumer requirements. -Immutable image sampling and TGUI smoothing integration are implemented in the -[sampling contract](../../rendering/pipelines-and-materials.md#immutable-sampling). -Additional atlas padding or repacking remains a separately selected extension. Current grid UVs address cell edges -without padding; the [demo samples](../../../projects/apps/demo/README.md#tile-and-sprite-samples) +### Atlas padding and repacking + +Additional atlas padding or repacking needs a separately selected scope. Preserve +the [sampling contract](../../site/rendering/pipelines-and-materials.md#immutable-sampling): +current grid UVs address cell edges without padding. The +[demo samples](../../../projects/apps/demo/README.md#tile-and-sprite-samples) retain the baseline observations. +### Game systems + Manual collision checks for object overlap/clipping are deferred to phase 1 of the [status inventory](../../STATUS.md#3-deferred). Define their shape, coordinate and boundary rules separately from physics simulation. Save-game serialization, @@ -236,32 +244,35 @@ Cheryl as a Git submodule and supply its own optional modules from the start, or implement its features directly. Do not plan on a later extraction or game refactor as a prerequisite for starting development. +### NUMA locality + NUMA discovery and memory placement are deferred indefinitely; no client/server architecture is scheduled. Reopen only for a consumer and hardware that justify multi-node locality. Any resumed facility must be optional at compile time and preserve ordinary submission/completion behavior. Separate CPU-to-node mapping from allocation, first-touch and migration; single-node runs cannot establish -multi-node acceptance. +multi-node acceptance. Preserve the +[worker provider contract](../../site/runtime/worker-execution.md#topology-providers-and-capacity). +Topology alone does not prove a speedup or guarantee residency; performance claims +require representative workload measurements. -General cache/core locality is implemented through optional hwloc support and -[application-owned worker providers](../../runtime/worker-execution.md#topology-providers-and-capacity), -separately from the NUMA deferral. Topology does not prove a speedup or guarantee -residency; performance claims still require representative workload measurements. +### Other extensions 3D, networking, world/entity/physics, serialization, device-loss recovery and broader OS/device validation remain separate consumer-driven work. Placeholders do not imply supported facilities. Establish scope, prerequisites, -ownership and acceptance before promoting any candidate into the development roadmap. +ownership and acceptance before selecting any candidate in STATUS. Broader pointer capture, modal/controller routing and clipboard/cursor/IME services -need explicit consumer requirements under the [UI capability boundaries](../../development/ui-adapters.md#routing-and-unavailable-services). -Build-tree and [prebuilt SDK consumption](../../development/consuming-engine.md#prebuilt-development-sdks) -are implemented. [CI](../../development/building.md#github-ci-and-sdk-artifacts) -prepares compiled development libraries/headers as workflow artifacts; it publishes -no GitHub releases or demo packages. Release publication requires a separate choice -of trigger/policy and accepted platform/toolchain configurations. Pending hosted -Windows and main artifact collection remain in the active testing queue; successful -builds do not establish desktop/device acceptance. +need explicit consumer requirements under the [UI capability boundaries](../../site/development/ui-adapters.md#routing-and-unavailable-services). +Build-tree and [prebuilt SDK consumption](../../site/development/consuming-engine.md#prebuilt-development-sdks) +are implemented. SDK collection and release publication belong to the +[CI workflow](../../../.github/workflows/ci.yml); they are not a deferred engine +extension. The first main-push collection remains in the +[testing queue](../../testing-requests.md#tr20-automated-main-push-sdk-collection). +Hosted build/SDK acceptance does not establish desktop/device acceptance. + +### Monitor hotplug Native monitor hotplug is deferred in [phase 3 of the status inventory](../../STATUS.md#3-deferred); monitor hotplug and @@ -269,7 +280,7 @@ per-window scale/change reporting still need explicit consumers. Settle refreshed inventory identity, native-handle invalidation and existing-window rebinding before adding hotplug support; monitor scale alone does not satisfy a per-window service. The current -[display/window contract](../../runtime/display-and-window-contract.md) defines the +[display/window contract](../../site/runtime/display-and-window-contract.md) defines the construction-time inventory and copied-size capability limits. ### Residency accounting @@ -281,13 +292,13 @@ backend allocation, and advisory budgets from enforceable admission limits. Settle owner-thread admission and independent provider/cache ownership before allowing multiple domains. Eviction must preserve in-flight frames and avoid counting shared images/materials repeatedly. The current -[residency contract](../../resources/resource-residency.md) supplies strong ownership +[residency contract](../../site/resources/resource-residency.md) supplies strong ownership and explicit clear/replacement/teardown; cache counts do not provide byte budgets. ## Additional dungeon asset manifests Prepare one manifest for each of the two 0x72 dungeon packages in the -[download catalog](../../assets/catalog.md#packages-awaiting-manifests) when +[download catalog](../../site/assets/catalog.md#packages-awaiting-manifests) when this long-term task becomes active. Keep the original downloads available for source comparison. Preserve the existing Buch dungeon image and manifest; the added packages need distinct namespaces and image directories. @@ -301,8 +312,8 @@ Handle one package per coherent development unit: semantics rather than inferring them from dimensions alone. 3. Write a separate JSON manifest covering the selected package images and add its final locations and manifest link to the catalog. Use the current - [manifest format](../../assets/asset-manifests.md) and - [loading validation contract](../../assets/asset-loading.md). + [manifest format](../../site/assets/asset-manifests.md) and + [loading validation contract](../../site/assets/asset-loading.md). **Acceptance:** the catalog identifies each package revision, and its manifest uses valid image paths and grids; slices and animations match that revision. Schema/source checks and diff --git a/docs/planning/long-term/asset-manifest-metadata.md b/docs/planning/long-term/asset-manifest-metadata.md index 07311021..25c77992 100644 --- a/docs/planning/long-term/asset-manifest-metadata.md +++ b/docs/planning/long-term/asset-manifest-metadata.md @@ -13,7 +13,7 @@ an artwork-owner decision before additional semantics can be encoded safely. - Buch dungeon sheet: locate metadata matching the 23×24 grid of 16-pixel cells (368×384 occupied pixels), or explicitly replace the sheet and remap it. Metadata for a different revision/layout cannot safely supply this sheet's named slices. - The [download catalog](../../assets/catalog.md#dungeon-tileset) identifies its source. + The [download catalog](../../site/assets/catalog.md#dungeon-tileset) identifies its source. - Colored/Orc buildings and Mage City: add per-cell gameplay names only if the engine needs semantic lookup below the existing regional/color view level. - Character Customizer: decide whether runtime customization loads composited exports @@ -24,11 +24,11 @@ an artwork-owner decision before additional semantics can be encoded safely. geometry, but no durations or verified action names. Do not infer animation timing or material/FX intent. Invaders' draw scale is a submission setting; irregular Rover frames remain separately addressable entries. The - [recovery contract](../../assets/asset-manifests.md#recovered-college-definitions) + [recovery contract](../../site/assets/asset-manifests.md#recovered-college-definitions) records pivot evidence and retained originals. Tile selection uses the declared rules; -the [selection contract](../../assets/asset-values-and-playback.md#tile-selection) +the [selection contract](../../site/assets/asset-values-and-playback.md#tile-selection) does not infer missing artwork semantics. Additional downloaded dungeon packages await separate manifests in the [long-term plan](README.md#additional-dungeon-asset-manifests). diff --git a/docs/planning/long-term/platform-acceptance.md b/docs/planning/long-term/platform-acceptance.md index 302bb0be..a5899ca0 100644 --- a/docs/planning/long-term/platform-acceptance.md +++ b/docs/planning/long-term/platform-acceptance.md @@ -7,26 +7,32 @@ coverage; they are outside the active scheduled, reconcile the current source/targets/configuration first, then restore ready requests to that queue. Linux/X11 is the current native acceptance platform. Windows hosted compilation, focused tests and installed SDK consumption are -accepted within the [CI coverage limits](../../development/building.md#github-ci-and-sdk-artifacts). +accepted within the [CI coverage limits](../../site/development/ci.md). The remaining [TR20](../../testing-requests.md#tr20-automated-main-push-sdk-collection) checks main-push SDK collection; it does not activate the desktop/controller/HID procedures here. ## Scope and prerequisites -Windows acceptance includes native input ownership and consumer/header probes, -typed-event and resize delivery, desktop resize, controller reports and HID -notification lifetime. TR3–TR5 below retain their request IDs for later activation. -They are **deferred**. The hosted MSVC configuration is accepted for the focused -CI selection; desktop and physical-device behavior remain unaccepted. +Remaining Windows acceptance covers native-window input ownership and resize +delivery, desktop resize, controller reports and HID notification lifetime. +TR3–TR5 retain their request IDs for later activation and are **deferred**. +Hosted MSVC already covers consumer/header probes, tile-animation, typed-event +delivery and the native input-lifetime unit suite in its selected configuration; +the [CI runner](../../../.github/ci/run.py) selects those owners. Treat their runs +below as baseline prerequisites for the chosen configuration, not new unfinished +features. The six native OpenGL cases in TR3 and the desktop/physical-device +observations remain outside that hosted selection. A configuration/build failure is an unresolved prerequisite, not an accepted or skipped case. Commands locate the checkout root with Git and preserve the caller's starting directory. Use a Windows Developer PowerShell with Ninja, CMake 3.28 or newer, a C++23 toolchain and initialized pinned submodules; native builds also need OpenGL -and Python with Jinja2. Engine text also requires FreeType, HarfBuzz and ICU uc/i18n; -the demo now has a builtin font fallback. Reuse a matching +and Python with Jinja2. Engine text requires FreeType and HarfBuzz; Windows uses +the operating system's ICU C API on Windows 10 version 1903 or newer, as described +in the [text dependency contract](../../site/assets/text-layout.md#supported-rendering-scope). +The demo has a builtin font fallback. Reuse a matching build directory when its compiler/configuration agree. Builds and CTest use normal priority and available parallelism. Stop on failures and require nonempty test selection without skips. Record revision, configuration, platform and coverage; @@ -36,8 +42,11 @@ automation does not establish separate desktop or physical-controller observatio Accept native input integration and resize callback cases on Windows, particularly rejection of reattachment to a different notification window and successful -reattachment to the original live window. Include tile-animation and typed-event -regressions and consumer probes on this platform. +reattachment to the original live window. Rerun the tile-animation, typed-event, +input-lifetime and consumer baseline in the selected native configuration before +the six OpenGL cases. The commands request `GAINPUT_ENABLE_HID=ON`; recheck actual +HID source/compiler selection before activation. The dependency can override that +option on Windows, and these checks do not establish HID readiness. Use a usable Windows desktop session. These native observations extend beyond the accepted hosted CI dependency/toolchain selection. @@ -50,7 +59,7 @@ try { -DCMAKE_BUILD_TYPE=Release -DCMAKE_POLICY_VERSION_MINIMUM=3.5 ` -DCMAKE_CXX_SCAN_FOR_MODULES=OFF ` -DCMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE=PRE_TEST ` - -DCHERYL_LOG_PROFILE=developer -DCHERYL_SANDBOX_BUILD=OFF ` + -DCHERYL_LOG_PROFILE=developer ` -DCHERYL_BUILD_NATIVE_GLFW=ON -DCHERYL_BUILD_OPENGL=ON ` -DCHERYL_NATIVE_INPUT=ON -DCHERYL_NATIVE_NULL_PLATFORM=OFF ` -DGAINPUT_ENABLE_HID=ON ` @@ -126,7 +135,7 @@ try { normally. Report the platform and mode for any failure. This request does not establish per-window scale transitions or GPU reset recovery. -The reusable [desktop checks](../../development/native-desktop-checks.md) retain the +The reusable [desktop checks](../../site/development/native-desktop-checks.md) retain the Linux resize procedure. ## TR5: QA controller reports and reconnection @@ -156,7 +165,7 @@ or establish the Windows notification route. **TR6 · QA — Linux/X11 and Windows; deferred and blocked on backend work and an observation harness.** Preserve the -[single initialized native-owner contract](../../../projects/modules/platform/native-glfw/README.md#input-lifetime-and-mapping). +[single initialized native-owner contract](../../site/modules/native-glfw.md#input-lifetime-and-mapping). The [HID plan](README.md#deferred-hid-integration) owns backend corrections. Successful Gainput initialization does not prove HID readiness: HID can be compiled out, and the dependency discards its initialization return code when enabled. @@ -199,13 +208,14 @@ DirectInput fallback and notification corrections accompany future platform work macOS needs source/dependency selection and native/HID fallback ownership resolved before desktop and USB/Bluetooth controller acceptance. The current Apple source selection omits the shared HID runtime/parsers. Preserve the -[native module's capability limits](../../../projects/modules/platform/native-glfw/README.md#hid-capability-and-platform-scope) +[native module's capability limits](../../site/modules/native-glfw.md#hid-capability-and-platform-scope) until those configurations are implemented and accepted. Full Wayland and other operating systems need their own runtime acceptance for input, rendering, UI coexistence, resizing and shutdown in sequential/concurrent -modes and normal/sandbox configurations. Establish machines, fixtures and native +modes and their selected `CHERYL_NATIVE_INPUT` / `CHERYL_NATIVE_NULL_PLATFORM` +configurations. Establish machines, fixtures and native observation requirements when activating each platform; do not infer acceptance from Linux/X11 or software-driver execution. Reuse the -[architecture procedures](../../development/architecture-validation.md) and the -[desktop checks](../../development/native-desktop-checks.md) where applicable. +[architecture procedures](../../site/development/architecture-validation.md) and the +[desktop checks](../../site/development/native-desktop-checks.md) where applicable. diff --git a/docs/planning/mid-term/README.md b/docs/planning/mid-term/README.md index dbadbbed..29b4046d 100644 --- a/docs/planning/mid-term/README.md +++ b/docs/planning/mid-term/README.md @@ -1,20 +1,23 @@ # Mid-term work Color emoji and reorder-safe sorting are planned in the -[selected roadmap](../develop-review-and-development-plan.md#selected-planned-work). +[status inventory](../../STATUS.md#2-planned). General render batching and text/timing optimization remain deferred. Sorting and optimization retain their representative-workload and settled-contract prerequisites. These tasks have no active implementation checklist yet; activate one coherent unit after its investigation/design checkpoint. Opt-in -[positioned text runs](../../assets/text-layout.md#positioned-text-runs) already +[positioned text runs](../../site/assets/text-layout.md#positioned-text-runs) already prepare glyph placements once and preserve contiguous page order. ## Color emoji -This is selected planned work beyond the current grayscale implementation. +This is selected planned work for Engine's shaped-text layout and atlas path. +The shared [font rasterizer](../../site/assets/text-layout.md#supported-rendering-scope) +already exposes alpha and premultiplied RGBA glyph pixels; that lower-level support +does not complete color-font layout, atlas submission or emoji acceptance. Select required color-font formats, fonts and emoji sequences beyond the accepted -initial [grayscale scope](../../assets/text-layout.md#native-acceptance-scope). +initial [grayscale scope](../../site/assets/text-layout.md#native-acceptance-scope). Establish RGBA page orientation, material/alpha handling and sequence/fallback policy while preserving source clusters and immutable generations. Keep grayscale text independently usable. Color glyphs diff --git a/docs/planning/short-term/README.md b/docs/planning/short-term/README.md new file mode 100644 index 00000000..532938c8 --- /dev/null +++ b/docs/planning/short-term/README.md @@ -0,0 +1,171 @@ +# Short-term follow-up + +This page owns unfinished regressions and dependency acceptance for implemented +font, Startup and UI behavior. The accepted Linux baseline remains in place; +these are separate gaps rather than a request to repeat all native QA. Subject +guides own current contracts, and the [testing queue](../../testing-requests.md) +owns ready acceptance requests. Each task needs its own authorized testing or +investigation phase before execution. + +Implemented event contracts and deferred capability prerequisites are mapped in +the [event follow-up](engine-events.md). + +## Font-selection regressions + +The [font-selection contract](../../site/assets/text-layout.md#font-selection) +and [implementation](../../../projects/engine/src/text/font-collection.cpp) +already rank discovered faces by declared weight/style. Existing discovery tests +cover lookup and ordering; registered-family CPU and GUI fixtures cover style +selection and collection face indices. The remaining task is automatic discovery +at numeric-weight boundaries. + +- [x] Implement weight/style ranking and accept the existing selection, layout + and resource checks; cover registered-family styles and face indices. +- [ ] Supply licensed fixtures with known style flags and numeric weights around + the automatic limit of 500. Verify that a non-bold face at the limit is eligible, + a face above it is excluded, and a bold flag also excludes an automatic face. +- [ ] Cover ranking around Regular (400), skipping a heavy-only automatic family, + and an asset override whose heavy face suppresses that automatic family even + when a lighter system copy exists. +- [ ] Verify that explicit preferred names, installed families and files remain + unrestricted by the automatic-weight limit. Isolate asset/system directories + from host-installed fonts and assert the selected face identity and metadata, + so an embedded fallback cannot make a failed selection look successful. + +Start with the existing +[selection fixtures](../../../projects/engine/tests/unit/src/font-selection.cpp) +and [font fixture helpers](../../../projects/engine/tests/include/testing/font-fixture.h). +Preserve fixture licensing and distinguish these cases from already covered +registered-family selection. Color emoji, wider CJK acceptance and editing/IME +remain outside the accepted [grayscale scope](../../site/assets/text-layout.md#native-acceptance-scope). + +## Startup and UI follow-up + +The [application guide](../../site/development/consuming-engine.md#command-line-startup) +owns parser, factory, result/context lifetime and option contracts. The +[adapter guide](../../site/development/ui-adapters.md#routing-and-unavailable-services) +owns complete-batch routing and focus epochs. The existing Linux root composition +has accepted native startup, field focus, placeholder and list-layout observations; +the [demo procedure](../../../projects/apps/demo/README.md#startup-and-ui-validation) +retains repeatable steps and coverage limits. + +The [Startup suite](../../../projects/modules/runtime/startup/tests/src/startup.cpp) +already covers option registration, a supplied factory's invocation and worker +count, the backend validation callback, single-use initialization, help before +creation, invalid worker count and malformed argument arrays. With OpenGL startup +configured it also covers default option help, borrowed input remaining +uninitialized on help, invalid backend selection and a zero default window width; +without that backend it covers the standard factory's rejection. The +[CI runner](../../../.github/ci/run.py) selects `tests-startup`. + +- [x] Implement common/backend Startup, complete-batch entry epochs and + `before_record` controls; add the owned RmlUi correction and supplied-Core gate. + Accept existing session/coexistence checks and independent installed-SDK Startup + first-include probes. +- [x] Add the Startup parser, supplied-factory invocation and preconstruction + validation regressions described above. +- [ ] Complete Startup factory/result handoff regressions: return a successful + context with the selected runtime policy, reject a null factory result, and + construct a runtime borrowing the successful result. The existing supplied + factory probe deliberately throws before returning a context; the standard + factory tests stop at help or invalid configuration. They do not exercise these + result branches or successful native context construction. Keep native + construction observations with the existing demo procedure. +- [ ] Add dedicated callback/batch-routing regressions. Deliver one complete + batch while application controls run before individual records; verify that + focus/visibility changes preserve the entry epoch and pending records, and + that each callback's result selects pointer delivery for that record. Existing + session/coexistence cases cover poll-latched preemption between calls; they do + not call the `before_record` overload or change focus within its batch. +- [ ] Add a placeholder hit-testing regression exercising the corrected owned + Core through public input and completed layout. Cover the state/selection cases + in the [upstream follow-up](#upstream-placeholder-follow-up). Existing editing + tests and accepted demo observations do not provide that dedicated regression. +- [ ] Complete the supplied dependency acceptance below with an independent host. + +### Supplied RmlUi acceptance + +Obtain a corrected RmlUi 6.3 target/package and a consuming host that bypass +Cheryl's source helper. Follow the full +[dependency contract](../../site/modules/rmlui.md#dependency-configuration), including +Engine font services and rasterizer ABI compatibility as well as the +[placeholder correction declaration](../../site/modules/rmlui.md#placeholder-dependency-contract). +Metadata must describe the actual library; setting a property does not correct it. + +Accept the selected supplied-target or installed-package composition, resolving +aliases before declaring properties. Check rejection when the required correction +declaration is absent or false, independently of successful Cheryl-owned source +configuration. Exercise the supplied library's placeholder focus, editing and +shutdown in the consuming host. Record which supplied composition was exercised; +configuration rejection and downstream-owned native success do not establish +supplied-binary behavior. + +## Upstream placeholder follow-up + +The [RmlUi guide](../../site/modules/rmlui.md#placeholder-dependency-contract) +owns the source invariant and Cheryl's downstream correction. The affected pin is +RmlUi 6.3, `ba95ffe8bfb6370efb2cdcca927eaad4710c5413`. Source inspection establishes +a placeholder/display-length mismatch; it does not identify the instruction that +caused the original reported crash. No unmodified-upstream reproduction, +pre-fix sanitizer evidence or upstream submission has been completed for this task. + +- [ ] Reproduce in a separate, unmodified upstream checkout, preserving shared + `extern/rmlui`. Start at the pin, then check current upstream and record each full + revision and actual runtime configuration. A guarded Cheryl build cannot serve + as the pre-fix case. Recheck existing issues before proposing a submission. +- [ ] Load a real font, show the document, complete `Context::Update()` and verify + a valid face, positive field width and formatted lines. Send mouse movement + before button down/up through the public context API. Separate ordinary clicks + in manual time unless deliberately testing double-clicks. +- [ ] Capture a demonstrably failing pre-fix regression, with a stack/memory + diagnostic or another observable failure, then show the same case passing after + the C++ correction. A value-stays-empty assertion alone can miss the invalid + read. Include a long ASCII hint (for example 256 characters), verify its actual + formatted length and click toward the right; a short hint need not cross a + sanitizer red zone. +- [ ] Inspect logical value/display sizes, line offsets/lengths and iterator range + without dereferencing an invalid range for logging. Distinguish this mismatch + from absent-font or empty-line failures. Later cursor clamping cannot undo an + earlier invalid read. +- [ ] Review the guard's placement: Cheryl returns before resetting + `ideal_cursor_position_to_the_right_of_cursor`. Follow relative indices through + absolute cursor/selection updates, especially later textarea lines. Correct + this in upstream C++, with an upstream regression; the CMake source-copy helper + remains downstream integration. +- [ ] Complete the cases below, run the selected regression and full upstream + unit suite with AddressSanitizer/UndefinedBehaviorSanitizer, and check that the + selected cases actually ran. Choose a compatible configuration from the pinned + [sanitizer workflow](../../../extern/rmlui/.github/workflows/build.yml) and + [unit-test target](../../../extern/rmlui/Tests/Source/UnitTests/CMakeLists.txt). + Record compiler/standard library, font engine, backend and runtime mode; + unexecuted configurations remain unverified. +- [ ] Check current contribution and [AI policy](https://github.com/mikke89/RmlUi/blob/master/AI_POLICY.md) + before any external submission. The source analysis and Cheryl guard were + AI-authored. A human submitter must personally understand, reproduce, review and + test the change, author required human communication, and disclose the actual + AI contribution/model as required. Do not repurpose an AI-written issue draft + as human-authored communication. No upstream contact is part of this plan's + maintenance. + +### Required placeholder cases + +Use the existing [input unit-test fixture](../../../extern/rmlui/Tests/Source/UnitTests/ElementFormControlInput.cpp) +and [text-input benchmark](../../../extern/rmlui/Tests/Source/Benchmarks/WidgetTextInput.cpp) +for public event submission, selection inspection and time advancement. The +existing `form.input.text.no_font` case covers a different failure. + +| Case | Required observation | +| --- | --- | +| Short/long ASCII hints; left/middle/right clicks | Focus works; editable value, caret and selection endpoints remain empty/zero; no invalid access. The long case demonstrates pre-fix failure. | +| Drag, Shift-click and double-click on a hint | Hint never becomes selected or editable text. | +| Type, clear and click again | Only typed text becomes the value; clearing restores safe placeholder behavior, including retained cursor state. | +| Nonempty value; empty field without a hint | Existing caret placement and selection still work. | +| Multibyte UTF-8 hint and entered value | Hint stays outside the editable value; entered-text character boundaries remain valid. | +| Left, center and right alignment | Visible caret uses the empty value's aligned insertion position. | +| Wrapped/newline textarea hints; later-line clicks and vertical movement | No invalid range; final absolute caret/selection endpoints are zero and selected text is empty. | + +The [test shell](../../../extern/rmlui/Tests/Source/Common/TestsShell.cpp) performs +font loading/layout with a dummy renderer by default; `RMLUI_TESTS_USE_SHELL` +selects its native backend. After logic/sanitizer checks, separately observe real +focus, visible caret, placeholder appearance and cleanup in native mode. A +headless pass does not establish those visual results. diff --git a/docs/planning/short-term/engine-events.md b/docs/planning/short-term/engine-events.md new file mode 100644 index 00000000..c0d83276 --- /dev/null +++ b/docs/planning/short-term/engine-events.md @@ -0,0 +1,87 @@ +# Event capability follow-up + +The selected non-font event integration is implemented. This document retains +unselected capability prerequisites; it does not authorize another implementation +batch. Current contracts live in the linked subject guides. The historical +[design assessment](../../../reports/event-coverage/design.md) and +[event catalogue](../../../reports/event-coverage/event-catalogue.csv) retain the +original rationale and stable event IDs, rather than serving as progress logs. + +## Current readiness + +| Implemented capability | Contract owner | +| --- | --- | +| Typed notifications, weak publisher routes and owned required attachments | [Event delivery](../../site/runtime/event-delivery.md#observe-a-change-or-participate-in-an-operation) | +| Window selection, renderer viewport and automatic UI view/time following | [Committed views](../../site/runtime/display-and-window-contract.md#committed-window-and-simulation-views), [TGUI](../../site/modules/tgui.md#automatic-view-and-time), [RmlUi](../../site/modules/rmlui.md#automatic-view-and-time) | +| Runtime milestones, retained outcomes, negotiable close and owner service phases | [Runtime boundary](../../site/runtime/runtime-frame-boundary.md#lifecycle-observation) | +| Sampled gamepad availability and aggregate upload outcomes | [Input](../../site/runtime/input-state-model.md#gamepad-availability), [upload settlement](../../site/assets/asset-loading.md#upload-settlement-notifications) | +| Exact-key asset catalogues and native-domain provenance/invalidation | [Catalogue changes](../../site/assets/asset-loading.md#catalogue-changes), [graphics domains](../../site/resources/resource-residency.md#graphics-domain-observation) | +| Logical playback outcomes and optional runtime notification pumping | [Audio outcomes](../../site/runtime/audio.md#playback-outcomes), [maintenance attachment](../../site/modules/miniaudio.md#runtime-notification-pump) | + +Runtime pressure remains an internal observation. Manual standalone UI and audio +use remains supported. Notification does not replace direct results, game hooks, +joins, queue closure, accepted-work settlement or final native collection. + +Focused Linux controlled and native integration coverage exists for these +boundaries; it does not establish physical-controller behavior or hardware audio +outcome delivery. Other platform and SDK-composition coverage remains subject to +its existing [platform plan](../long-term/platform-acceptance.md) and module limits. +Reusable runner selection belongs to the [testing guide](../../site/development/testing.md): +new Engine cases run in `tests-engine`, legacy resource/runtime/bus cases in +`acceptance-engine`, and real GLFW resize/close/render cases in `acceptance-opengl` +(`tests-acceptance-opengl`). Offline logical outcomes use `tests-audio-miniaudio`. +Do not mistake an aggregate, skipped native case or source presence for execution. + +## Deferred font catalogue + +R3 / RES03 is explicitly deferred. Existing FontService configuration and retained +font identity remain unchanged; current UI sessions import their catalogue once. +If a consumer later requires font catalogue observation, first select its concrete +use and preserve these requirements from the original design: + +- A coherent immutable named/default/ordered-fallback snapshot, rather than + separately locked reads. The embedded initial catalogue has revision zero. +- Successful explicit configuration commits a revision; failed preparation + preserves the old snapshot. Permitted concurrent configuration callers stage + observations for one designated publication owner with a race-free watch seed. +- No implicit widget rebinding or native family-alias replacement. RmlUi's + retained-name restriction and application fallback policy need their own design + before adopting a new font generation in an existing session. + +Do not activate this work merely to match other catalogue event families. + +## Deferred capability boundaries + +| Candidate | Prerequisite before selecting implementation | +| --- | --- | +| DEV04 DisplayTopologyChanged | Refreshable inventory, connection identity, native-handle invalidation and fallback/rebinding. Follow the existing [monitor plan](../long-term/README.md#monitor-hotplug). | +| DEV05 InputDeviceTopologyChanged | Portable enumeration/connection identities and real backend lifecycle observation; coordinate with [HID](../long-term/README.md#deferred-hid-integration) and native input ownership. Convenience-slot availability is not physical topology. | +| DEV06 AudioOutputStateChanged | Actual output/device/route observation, capability limits and recovery policy. Logical PlaybackEnded does not imply device-loss detection. | +| SYS11 PlatformLifecycleChanged | Real OS suspend/resume/session-ending adapters, owner marshalling and forced/deadline policy. Focus/minimize is insufficient. | +| Independent native pumping | Bounded staging on every pending input path, visible overflow, whole-poll FIFO/capacity and explicit routing/capture latch and spacing semantics across pumps. | + +### Independent native pumping + +Current observation is sampled and poll-gated: a full input backlog can delay +GLFW close/focus/resize observation. Host shutdown requests and platform +maintenance remain independent of that backlog. Do not add an extra native pump +under saturation without settling the pending-record contract first. + +A stronger native-response capability must choose between keeping one logical +poll/capture latch open across several pumps and relatching routing/capture each +pump. Preserve complete immutable poll publication, reconciliation and FIFO +ordering. Bound every pending path and report overflow at a safe owner boundary, +including when no semantic poll completes. Arbitrary lossless input, bounded +memory and endless pumping with a permanently stalled consumer cannot all be +promised. Specify spacing, focus changes, callback failure and standalone +`begin_poll -> pump -> update` behavior before dependent code accumulates. + +## Separate consumer decisions + +Presentation/density/observed-mode view extensions, live font adoption, native +graphics recovery, complete playback history, residency budgets and automatic +asset hot reload remain separate capabilities. Keep application save/gameplay +policy and absent service domains out of the event layer. The +[planning catalogue](../README.md) and [STATUS](../../STATUS.md) own selection and +review order; choose a concrete consumer and acceptance scope before activating +one of these follow-ups. diff --git a/docs/planning/short-term/unicode-text.md b/docs/planning/short-term/unicode-text.md deleted file mode 100644 index ef7ecf8a..00000000 --- a/docs/planning/short-term/unicode-text.md +++ /dev/null @@ -1,41 +0,0 @@ -# Unicode text layout and native acceptance - -The initial grayscale [Unicode scope](../../assets/text-layout.md#native-acceptance-scope) -has an accepted Linux native baseline for appearance, alignment, fallback and -local-width wrapping. Dedicated font-style/weight fixtures and regressions remain -unfinished below. The subject guide owns font, layout and resource contracts and -the rationale for a separate service. Color emoji, wider CJK acceptance and -editing/IME remain outside this task. - -## Progress - -- [x] Add owned UTF-8 scalar/source records with maximal-subpart replacement and - preserved NUL/BOM/unassigned scalars. -- [x] Add decoder regressions and a neutral first-include probe. -- [x] Make STBFont consume scalars while preserving ASCII metrics and FFont's byte API. -- [x] Cover malformed/multilingual fallback and CPU submission. -- [x] Settle script, direction, wrapping, fallback and grayscale scope. -- [x] Add immutable file/family selection, inspected coverage and licensed embedded - fallback through private FreeType implementation. -- [x] Add HarfBuzz shaping and ICU bidi/grapheme/line boundaries with owned clusters, - face-qualified glyphs and measured accepted-line widths. -- [x] Prepare CPU grayscale pages and upload complete immutable retained generations; - failure preserves the preceding application generation. -- [x] Integrate worker preparation/platform uploads into the builtin demo, with - multilingual, wrapping, failure/retention checks and a visual harness. -- [x] Correct bidi-control source mapping while preserving shaping joiners/selectors. -- [x] Accept Linux Release Engine-only decoder, selection, shaping/bidi/wrapping, - resource/submission and header coverage. -- [x] Compile the native Linux/X11/OpenGL demo with HID and both UI adapters disabled. -- [x] Rank installed faces by declared weight/style and exclude heavy automatic defaults. -- [x] Accept existing font/layout/resource checks after the font-style selection changes - in the Linux Release Engine-only selection. -- [ ] Supply licensed style/weight fixtures and dedicated regressions for the - automatic limit, heavy-only family skipping and unrestricted explicit preferences - in separately authorized testing phases. Existing family cases cover baseline - discovery/order rather than those numeric-weight boundaries. -- [x] Accept reported sequential/concurrent visual, fallback and replacement - observations within the [native scope](../../assets/text-layout.md#native-acceptance-scope). - -Possible [color emoji and measured layout/cache work](../mid-term/README.md) -follow this task without changing its acceptance scope. diff --git a/docs/runtime/audio.md b/docs/runtime/audio.md deleted file mode 100644 index 8e02524c..00000000 --- a/docs/runtime/audio.md +++ /dev/null @@ -1,75 +0,0 @@ -# Audio ownership and playback - -The public [audio contracts](../../projects/engine/include/cheryl/audio.h) belong -to Engine and contain no SDK, device or graphics types. Applications own an audio -system independently of `CE::Runtime::Context` and compose a selected audio module through -its public CMake target. Engine has no reverse dependency on that module. - -## Clips and voices - -`CE::Audio::Clip::from_samples` takes owned interleaved float PCM, with mono/stereo -channels and a sample rate between 8 and 192 kHz. Data is nonempty, frames are -complete, and every sample is finite. The nominal sample range is `[-1, 1]`; -storage does not clamp samples or change their rate. Copies share immutable -storage. Moving preserves the original valid snapshot. Clip creation does not -open a device. Its frame count and duration describe source audio, independently -of a mixer's output rate. - -`CE::Audio::System::play` creates a voice with an independent cursor. Voices can share -a clip without sharing playback progress. `stream` opens file-backed playback for -music; the file must remain available until playback ends. Decode and initial file -opening are synchronous preparation operations, so applications perform them at -loading boundaries or dispatch owned decode requests through their worker groups. - -Voice and master volumes are normalized linear gains in `[0, 1]`, with zero as -mute. Invalid/non-finite gains throw. Multiple voices are mixed; overlapping loud -sources can exceed nominal PCM amplitude and applications manage their mix levels. - -| Operation/state | Behavior | -| --- | --- | -| Initial playback | Starts immediately, or starts `Paused` when requested. | -| `pause` | Pauses a playing voice and preserves progress. | -| `resume` | Resumes a paused voice; other live states are unchanged. | -| `stop` | Leaves the voice `Stopped` until explicit restart. | -| Natural end | Reports `Finished`; explicit restart is required. | -| `restart` | Requests source frame zero and starts playback. | -| `set_looping` | Changes the loop policy without restarting stopped/finished playback. | -| System closure | Surviving handles report `Closed`; their controls throw. | - -Snapshots copy playback state, volume, loop policy and known source duration. -They are observations, not an atomic freeze of native playback. Live decoder -cursors are outside the initial API; reading them concurrently with mixing needs -additional synchronization. No game callback runs on the mixing thread. - -## Lifetime and scheduling - -Controls, submissions, snapshots and maintenance are synchronized across -application producers. Their order follows control admission, rather than a -deterministic simulation clock. Device playback progresses on its own clock -without render or simulation admission. Offline processing is explicitly selected; -an unavailable native device must not silently produce inaudible success. - -The system retains active playback after the application releases its voice -handle. Call `maintain` regularly to reclaim completed/stopped/paused voices that -have no application handles. Maintenance does not advance playback. Discarded -looping voices remain active until system closure; retain their handles when they -need individual control. - -Create the system before playback. Settle all producers before destroying it. -`close` is idempotent, stops output, retires native voices before their mixer/device, -and leaves surviving handles safe. A system cannot reopen; construct another -system for a new session. Audio need not share the graphics owner's thread, and -this contract does not add audio to the runtime's existing adapter teardown. - -## Backends and limits - -The [miniaudio module](../../projects/modules/audio/miniaudio/README.md) supplies CPU -decode, explicit device/offline output and streamed playback. Its independent -consumer provides native observation without a game, window or graphics module. -Linux root-assembly native stereo output and sustained WAV streaming are accepted; -the module guide owns reusable validation and compressed-stream, composition and -platform coverage limits. - -World, entity and collision organization belongs -to the game or its application-selected modules. FMOD is a later optional backend; -Studio authoring formats/capabilities require a separate contract. diff --git a/docs/runtime/event-delivery.md b/docs/runtime/event-delivery.md deleted file mode 100644 index 3883acf8..00000000 --- a/docs/runtime/event-delivery.md +++ /dev/null @@ -1,170 +0,0 @@ -# Event buses and persistent registration - -`CE::Events::EventSystem` provides the global default access layer. Its owned -`CE::Events::EventBus` contains registrations. A subsystem may instead own a bus to isolate its channels and -lifecycle; a bus is a registry, not a thread or worker pool. - -```cpp -CE::Events::EventBus local_bus; -auto id = local_bus.register_listener("level-loaded", callback); -local_bus.dispatch("level-loaded", payload); -// CE::Events::EventSystem::get() exposes the same API through its default bus. -``` - -## Registrations - -Registration is persistent. The bus strongly owns its callback until explicit -unregister or bus destruction; throwing away `id` does not remove it. Identifiers -include bus identity, so an ID from a different bus cannot remove a local listener. - -## Immediate delivery - -Immediate delivery runs on dispatch's caller, in registration order within that -dispatch. Overlapping producers can overlap callbacks; immediate listeners must -be safe on each producer thread. Nested dispatch is synchronous and completes -inside the outer callback. Exceptions propagate to the immediate caller. - -The registry lock is released before callbacks run. Registration during dispatch -joins the next snapshot, not the already detached one. String channel names and -`std::any` payload typing retain their current application-level contract. - -## Removal and shutdown - -`unregister_listener(id)` invalidates invocation entry before it returns. A -listener already running may finish. For a callback borrowing an object (for -example `[this]`), call `unregister_and_wait(id)` before destroying that object, or -unregister then call `wait_for_listener(id)`. The barrier waits only for invocations -already entered, not for queued tasks which can safely discard invalid entries. -Do not wait while holding a lock needed by the callback. Self-unregister works; -waiting from the same listener, including nested invocation, rejects instead of -deadlocking. Callback exceptions still release in-flight accounting. - -`close()` invalidates all registrations and rejects subsequent registration and -dispatch. Concurrent close/removal calls serialize invalidation with registry -removal: every returning closer has closed all remaining invocation gates. -Callback captures are still released outside both locks, so their destructors may -reenter the bus. Close does not wait for running callbacks. Bus destruction closes -without blocking; callers must settle any -in-flight callbacks that borrow a bus or other target before destroying it. -Registry snapshots and invocation guards retain callback ownership as needed, -without permitting a fresh invocation after invalidation. Queued delivery is optional and described below. - -## Typed channels - -`CE::Events::EventChannel` owns an exact channel name. Its name plus its unqualified, -copy-constructible payload type identify a channel within each bus. Equivalent -name/type values share registrations, independent of token lifetime; another -payload type with the same name is a separate channel. Legacy string/`std::any` -channels retain their application-defined type contract and are separate from typed -channels. Type identity is in-process; names and RTTI are not a serialized protocol. - -```cpp -struct LevelLoaded { - int level; -}; -const CE::Events::EventChannel loaded{"level-loaded"}; -int last_level = 0; -auto id = local_bus.register_listener(loaded, [&](const LevelLoaded& event) { - last_level = event.level; -}); -local_bus.dispatch(loaded, LevelLoaded{3}); -local_bus.unregister_and_wait(id); -``` - -Typed submission requires the exact payload type rather than implicit numeric or -other conversions. It copies an lvalue or constructs from an rvalue into an owned -payload before dispatch; even `std::any` can be a typed payload without flattening -its contents. Each listener receives the existing invocation-owned payload copy -through a borrowed `const Payload&`. Copy it for retained use, and keep pointees -alive according to Payload's own ownership. A checked erased-type lookup precedes -the typed callback; inconsistent internal payloads throw `bad_any_cast` instead of -reaching that callback. Public named producers cannot target a typed channel. - -Persistent registration, snapshot order, invalidation, waits, close and optional -delivery use the same listener machinery as named channels. `CE::Events::EventSystem` forwards -both APIs to its default bus. Initial typed-payload construction failures reach the -producer before the registry dispatch begins. Per-listener queued copying, -callback/target failures and cancellation still belong to the registered error sink; -invalidation waits protect the callback's borrowed target, not that error sink's -independent lifetime through pending-ticket destruction. No typed API chooses a -thread or changes worker-stream ordering. - -`CE::Platform::window_resized_event` in `core/display/window-interface.h` is the canonical -typed channel for `CE::Platform::WindowResized`. Native GLFW explicitly publishes both it and the -legacy named `"window-resized"` notification; this producer bridge does not make -generic named and typed channels equivalent. Register with the typed channel to -receive a checked `const CE::Platform::WindowResized&`, optionally selecting platform or simulation -delivery as below. Its dimensions are copied, while its window pointer remains -borrowed and cannot be dereferenced on simulation. The -[display/window contract](display-and-window-contract.md#sizes-modes-and-observation) -defines observation, ordering and native failure behavior. - -## Optional queued delivery - -Pass a delivery callable and error sink to `register_listener`. The bus accepts -only the small `bool(Work)` delivery contract, with move-owned `Work`; its header -has no dependency on dispatchers, runtime, or worker pools. A target owns deferred -work when it returns true. False or an exception rejects it without retaining it. -It must not invoke work inline or wait for it, and must provide FIFO execution for -each listener stream. Concurrent producers serialize enqueue for a given listener. -There is no global completion order between listeners or independent targets. - -Queued tasks copy their payload and retain registration state. At actual execution -they enter the same invalidation handshake as immediate callbacks. Callback -exceptions, payload/task preparation failures, rejected posts, and accepted tasks later dropped by the target reach -the required error sink. Target cancellation reports `future_error/broken_promise`; -explicit unregister/close discards pending callbacks intentionally. Error sinks -receive the original preparation/target exception; such queued failures do not -also escape from dispatch. Immediate failures still propagate to the caller. Sinks -must not throw and must remain valid through pending-task destruction; a throwing -sink terminates rather than disappearing in a discarded target future. - -The local delivery ticket owns the copied payload as well as rejection reporting. -If a target destroys rejected work during its offer, final payload destruction -waits until the listener's posting lock has been released. Payload destructors, -including final shared-owner deleters, may therefore dispatch again without -reentering that lock. Payload-copy failure still reports the original exception. - -The completion barrier protects the listener callback's borrowed target. An error -sink is independent: prefer an owned logging/reporting handle, not the same raw -`this`. Unregister can race a delivery already being offered; that task can still -be accepted but will not enter the invalidated listener. - -`CE::Events::platform_delivery(engine.platform_dispatcher().submission())` and -`CE::Events::simulation_delivery(runtime.simulation_dispatcher().submission())` are -optional composition adapters in `core/engine/event-delivery.h`. Their namespace -is `CE::Events`; this separate composition header keeps the bus -independent of `CE::Runtime` and `CE::Workers`. Their saved -endpoints reject safely after service closure. `CE::Events::worker_delivery(group)` provides one serial drain stream on the group's -shared parallel capacity. Copies of that delivery callable share FIFO execution -and completion order, including across listeners. Separate adapter construction -creates independent streams. Ordinary jobs in the group remain parallel; a pool's -FIFO selection alone does not impose completion order. - -Accepted stream work drains before group completion. Group closure rejects new -stream work even when a pump is active. A failed/cancelled pump releases its -pending captures outside stream locks, letting owned event tickets report loss. -Do not block a stream callback on another callback in that same stream. - -## Worker-stream acceptance and cancellation - -Initial pump submission is serialized with stream publication. Another producer -cannot return accepted before that submission succeeds. Closure checks share the -stream mutex with the pump's final empty-queue check. Submission failure removes -only the initiating request, and its captures leave the stream lock before error -reporting. Lock order is stream then pool; worker accounting releases the pool lock -before touching a stream or releasing callback captures. - -Cancellation belongs to the submitted callable. An atomic preparing/accepted/ -cancelled handshake records loss before callback entry without taking the producer's -already-held stream lock. Before publication, the producer withdraws only its own -request. After publication, unentered callable destruction abandons the stream -outside producer posting locks. This permits error-sink redispatch and capture- -destructor reentry without running another listener's cancellation under that lock. - -[Validation procedures](../development/architecture-validation.md) includes controlled pump rejection, -concurrent producers, payload-copy invalidation, native-policy suppression, and -reentrant recovery. Typed identity/ownership, compile-time constraints, waits, -queued failure/error-sink lifetime and worker FIFO have additional regression -coverage. Linux/X11 typed-event and resize acceptance is complete. Remaining Windows -acceptance is shelved in the [platform plan](../planning/long-term/platform-acceptance.md). diff --git a/docs/runtime/input-state-model.md b/docs/runtime/input-state-model.md deleted file mode 100644 index 73efbc10..00000000 --- a/docs/runtime/input-state-model.md +++ /dev/null @@ -1,169 +0,0 @@ -# Input actions and state - -## Bindings - -`CE::Input` defines the input protocols shared by all backends, games and other -consumers. `Actions` maps physical controls into semantic state; `Events` -contains ordered physical and committed-text observations. `Capture` requests -those record channels, while `Routing` selects keyboard/text recipients. Capture -and routing serve gameplay, tools and UI without requiring a widget toolkit. The -native implementation is `CE::Platform::GLFW::InputSystem`; optional toolkit -adapters live separately under `CE::UI`. - -Cheryl collects physical input on the platform thread. `CE::Input::Actions::Bindings` can map one control or a simultaneous `CE::Input::Actions::Chord` to a game-defined `CE::Input::Actions::Id`. Several button bindings for the same action combine with OR; controls inside one chord combine with AND. Axis bindings add their scaled values and can be gated by a chord. Negative scale inverts an axis, and an optional dead zone rescales the remaining input range. An action cannot be both a button and an axis while bound. A returned `CE::Input::Actions::BindingId` can remove one mapping; `unbind_action` removes all of its mappings. A physical `CE::Input::Control` stores the device ID in `device` and that device's control token in `control`. - -This binding example uses Gainput control tokens supplied by the GLFW backend. -The `context` is the runtime context available during game initialization. - -```cpp -constexpr CE::Input::Actions::Id Jump{1}; -constexpr CE::Input::Actions::Id Save{2}; - -auto& input = context.input(); -auto& bindings = input.bindings(); -const auto keyboard = input.keyboard_id(); // The adapter is initialized before game init. -(void)bindings.bind_button({keyboard, gainput::KeySpace}, Jump); -(void)bindings.bind_button( - CE::Input::Actions::Chord{{{keyboard, gainput::KeyCtrlL}, {keyboard, gainput::KeyS}}}, Save -); -``` - -## Poll snapshots - -Each adapter processes its physical changes and publishes one complete poll. The GLFW adapter feeds State from key/mouse callbacks in their shared observation order, then updates Gainput and publishes the State/record pair through `publish_input()`. Gainput notifications for those externally mapped devices are ignored to prevent replay and cross-device reordering. Gainput-owned devices still use the mapper; the adapter also reconciles the gamepad's current state for reattachment/disconnection when no delta was delivered. Readers receive a `std::shared_ptr` through `CE::Input::System::action_snapshot()`. The published pointer is replaced atomically; an earlier handle keeps its complete sample while later polls proceed. - -## Tick input - -`CE::Game::AbstractGame::update(const CE::Game::TickContext&)` runs for each selected simulation step, including steps in a bounded recovery batch. At its start the runtime consumes the entire accumulated polling backlog, leaving an empty backlog for new polls. Input never invokes updates or divides the simulation timestep. A press observed at 20 ms and a release at 80 ms of a 100 ms observation interval report 60 ms of raw down-time even when the selected fixed delta is 20 ms. `CE::Game::TickContext::delta_seconds` belongs to the simulation policy; `observed_seconds()` and raw input durations remain on the observation clock. The named `button_simulation_seconds()` helper maps observed down-time proportion onto the selected delta. [Simulation timing](simulation-timing.md) explains variable/fixed stepping, caps, and dropped time. - -`CE::Input::Tick::button(id)` reports current/previous state, `pressed()` and `released()`, `press_count`, `release_count`, `held_duration`, `down_duration`, and `completed_holds`. All durations are `std::chrono::duration` (seconds). `held_duration` is the age of the still-active hold, zero after release. `down_duration` is time observed down since the previous consumption. `completed_holds` contains the full duration of every hold released in this batch, including holds started in earlier cycles. Consumption clears activity, counts, completed holds, and relative movement; current buttons, active hold starts, and absolute axes persist. - -State timestamps are poll observation times. Multiple semantic transitions inside one poll retain counts, but share that poll's timestamp; a same-poll tap has zero measured duration rather than an invented hardware duration. There is no threshold that erases taps. Repeated unchanged polls do not repeat edges. If a poll crosses a consumption boundary before publication, its State activity arrives in the next batch and down-time is clamped to that batch's interval; ordered records keep their original observation timestamps. `CE::Input::Accumulator::consume()` retains the last State baseline and consumption boundary; it has no simulation callback. - -`CE::Input::Actions::AxisOptions::kind` defaults to `CE::Input::AxisKind::Absolute`: current position/value persists and `delta()` compares the final value with the previous consumption's value. `CE::Input::AxisKind::Relative` uses `on_delta()` and sums movement across the batch, then resets to zero. Absolute cursor coordinates and controller axes use the first contract; wheel and relative pointer movement use the second. Bindings for one action cannot mix kinds. Unbinding a held action publishes one release or zero absolute-axis sample. `CE::Input::Tick::polls()` exposes retained sample handles, but does not promise a physical event stream or reconstruct event order within a poll. - -Input adapters report physical changes; bindings translate them into semantic action state. GLFW callbacks retain key and mouse-button edges plus scroll pulses, while cursor position is sampled once after the event pump. Game logic receives complete tick input at the update boundary instead of running from device callbacks on the polling thread. - -Bind controls to semantic `CE::Input::Actions::Id` values and read them in `update(const CE::Game::TickContext&)`. Custom adapters call `on_button(control, held)` or `on_axis(control, value)`; Gainput's old value is not needed by the binding layer. - -## Ownership and validation - -`CE::Input::Actions::Bindings` configuration, physical notifications, gates and publication share -one platform owner. `action_snapshot()` atomically copies a retained immutable -handle from any reader thread. Retrieve `poll_snapshot()` after the complete poll -on the platform owner, then share that handle; reading a State and complete-poll -pointer separately during publication is not a combined snapshot operation. -The initialized window is borrowed until callback detachment. Request handles keep -only capture/routing state, rather than extending adapter or window lifetime. -Independent handles can be requested/released across threads, with exclusive access -to each individual move-only handle. - -Button chords must be nonempty. Axis scales must be finite, dead zones finite in -`[0, 1)`, and physical values/deltas finite. Values are not clamped to `[-1, 1]`; -dead-zone rescaling can apply to logical movement as well as controller values. -An empty axis modifier chord is unconditional. Publication requires nondecreasing -observation time and preserves the earlier published handle on failure; poll IDs -can skip. Mutable collection/configuration staging has no general rollback/retry -guarantee after allocation or arithmetic failure. Adapters report such failures -through their platform failure boundary rather than publishing a partial sample. - -Manual `CE::Input::Tick`/accumulator consumers must provide a nonnull baseline, strictly -newer poll IDs with nondecreasing observation times, and a consumption interval -starting at/after the baseline and ending at/after its start. Samples cannot be later -than that end. These checks precede advancing the accumulator baseline. `CE::Input::Tick` -owns its handles and copied record storage; spans and filtered record views borrow -that value and must not survive its move/destruction. Missing action queries return -inactive/zero state copies. An axis kind change within one batch fails when queried; -consumption boundaries separate supported kind changes. - -`CE::Input::PollingBacklog` rejects unknown policies, negative spacing and zero finite capacity. -`complete()` requires eligible capacity/spacing, a newer nonnull State and observation -time no later than completion. Its mutable staging has no allocation-failure rollback -guarantee. A full batch reports no next eligible poll; consuming it restores capacity -while retaining the existing spacing deadline. - -## Polling backlog and scheduling - -`CE::Runtime::GameRuntime` accepts `CE::Input::PollingOptions` independently of `CE::Runtime::RunMode`. The default `Lockstep` policy permits one completed poll between simulation consumptions. `Finite` permits `capacity` completed polls; `Unlimited` removes that limit. Every completed poll counts, even if no action changed. This implementation keeps all completed poll handles in its transport batch; State derives the useful activity from them. Capacity therefore describes observations, not the number of button transitions or retained changed samples. - -`spacing` is a configurable minimum delay from the end of one poll to the beginning of the next (default 1 ms; zero permits polling as fast as the platform loop can run). Consumption reopens capacity without resetting that delay. When a finite/lockstep batch fills, the platform stops pumping input until consumption. It continues rendering and recycling frames. Pending OS events remain with the backend; one later `glfwPollEvents()` processes all waiting events. Cheryl can retain the callbacks that GLFW delivers, but cannot reconstruct transitions or hardware timestamps the backend never exposed. - -The worker transfers the entire backlog under the scheduler lock before each actual update and immediately leaves an empty backlog. Both runtime modes use independently configurable variable/fixed timing; the default variable interval is approximately 16.667 ms. A cycle with no update leaves input pending. Every recovery update consumes fresh polls or persistent State without replaying edges, relative motion, Events, or Text. Sequential mode uses the same eligibility rules and may collect multiple eligible polls before a paced update, within its selected capacity; it cannot poll while update or presentation blocks its thread. `stop()` and worker completion wake the scheduler even when capacity is full. - -```cpp -CE::Input::PollingOptions polling; -polling.policy = CE::Input::PollingPolicy::Finite; -polling.capacity = 8; -polling.spacing = std::chrono::milliseconds(2); -CE::Runtime::GameRuntime runtime(context, game, CE::Runtime::RunMode::Concurrent, polling); -``` - -## Ordered Events and OS Text - -State is always collected. `CE::Input::System::capture(CE::Input::Capture::Channel::Events)` and `capture(CE::Input::Capture::Channel::Text)` return move-only `CE::Input::Capture::Lease` handles. Requests are reference-counted per channel and may be made or released from simulation; activation is latched at the next platform poll. A lease controls capture, not a private queue. Releasing one leaves other requests active and never removes records already published. Adapters declare `supports(mode)`; requesting an unsupported channel fails explicitly. A State request is harmless and does not disable other channels. - -Each `CE::Input::PollSnapshot` contains its `CE::Input::Actions::Snapshot` and an independent ordered vector of `CE::Input::Events::Record`. The runtime transfers these complete polls with the same whole-batch/backpressure policy. `CE::Input::Tick::records()` exposes the concatenated read-only stream for that update. Multiple game/UI consumers may read it without consuming each other's data. The next update has only newly captured records. State flags, deduplication, or aggregation never replace the stream's only copy. - -A `CE::Input::Events::Record` has a shared sequence number, observation timestamp, device ID/kind, and a variant payload: `CE::Input::Events::Button`, `CE::Input::Events::Axis`, `CE::Input::Events::Pointer`, `CE::Input::Events::Scroll`, or `CE::Input::Events::Text`. Sequence spans physical and text records, preserving their relative order. Buttons retain press/release/repeat, modifier flags, and backend key/scancode information where available. Pointer records use logical coordinates and wheel records preserve fractional X/Y offsets. GLFW callbacks are recorded before Gainput mapping; unknown key tokens still retain native/scancode information. Gamepad events describe changes between actual samples, in sample-processing order; they cannot recover intermediate physical transitions the gamepad backend did not report. Callback/sample timestamps are observation times, not hardware timestamps. - -`CE::Input::Events::Button::key` and `mouse_button` identify known keyboard and mouse controls -through the neutral `CE::Input::KeyboardKey` and `CE::Input::MouseButton` enums. UI/tool consumers can -recognize editing keys, shortcuts and pointer buttons without interpreting GLFW or -Gainput constants. Letter/punctuation names refer to US keyboard positions, not -localized characters. `CE::Input::Events::Text` remains the sole committed-character source. -`button` remains an opaque per-device State binding ID; portable identity does not -change existing bindings or imply that a binding token exists. For example, GLFW -F20 has portable identity even though the current Gainput mapping has no token. -Adapters leave unrecognized/inapplicable identities `Unknown` and retain available -native/scancode diagnostics. Older aggregate construction defaults the added fields -to `Unknown`; numeric enum values are not a persisted or native-code interchange. - -Mouse button and scroll records carry optional observation-time logical `position`. -Native GLFW captures it in the corresponding callback, so a first click after -capture activation and a click followed by movement retain the correct location. -Sources without that information leave it absent. Consumers requiring a click/wheel -position must reject that unsupported input or arrange an explicit source contract; -they must not substitute a later cursor sample or assume a move preceded the event. - -GLFW's Unicode character callback supplies `CE::Input::Events::Text::codepoint` (one valid Unicode scalar). Characters follow OS keyboard layout and include whatever repeated committed characters the OS delivers. They are never reconstructed from physical key codes. Physical repeats remain `CE::Input::Events::ButtonPhase::Repeat` without creating repeated State presses. Text and editing controls share stream order: Backspace, Delete, arrows, Home/End, and shortcuts arrive as physical control events, not inferred characters. This backend provides committed text only; it does not expose composition/preedit, candidate selection, grapheme segmentation, clipboard editing, or a complete editor/IME contract. - -The GLFW adapter also reports relative State axes under `CE::Input::MouseControl::DeltaX`, `DeltaY`, `ScrollX`, and `ScrollY`. Bind these with `CE::Input::AxisKind::Relative` and read `delta()` once per update. Relative movement is scaled and gated by the modifiers active when it arrives, then accumulated until publication; releasing a modifier or unbinding afterwards does not erase already mapped movement. A newly installed relative binding receives subsequent movement. Absolute axes still use the final sampled value and modifier state. Changing an action's axis kind requires a consumption boundary; mixing kinds in one State batch is rejected explicitly. Existing wheel-button pulse bindings remain available, while fractional/repeated wheel movement uses the relative axis or ordered scroll records. - -Custom adapters use `begin_input_poll()` before collection and `publish_input()` once afterwards to publish the State/record pair. Existing State-only adapters can still publish actions directly. Adapters advertising Events/Text must publish the complete poll; a mismatched State-only publication fails rather than silently omitting captured data. `supports_focus()` separately declares routing support; an unsupported routing request fails explicitly. Focus-capable adapters call `begin_input_poll()` even when only State capture is used. A host managing GLFW pumping explicitly calls `CE::Platform::GLFW::InputSystem::begin_poll()`, pumps events, then calls `update()`. Polling, bindings, collection, and publication remain platform-thread operations; capture handles and immutable delivered data cross the thread boundary. Deinitialization detaches all callbacks and discards pending records without invalidating already delivered handles. - -## Focus, routing, and editing controls - -`CE::Input::Routing::Manager` is a separate focus-request abstraction accessed through `CE::Input::System::routing()`. `focus(nonzero_target_id, policy)` returns a move-only `CE::Input::Routing::Lease`. The latest published request owns keyboard focus; atomic publication decides overlapping requests. Releasing an older lease cannot clear a newer owner, even when the same target ID is reused. Releasing the current lease clears focus without restoring older requests. `CE::Input::Routing::Lease::epoch()` identifies that particular request, and `owns_focus()` is an observation rather than a lock on ownership. Capture requests and focus leases may be held by UI objects on the simulation thread; all platform input work remains on the platform thread. - -The platform latches focus at the beginning of each poll, together with capture activation. This one focus snapshot controls both keyboard State gating and record routing. Requests made during collection take effect at the next poll. `CE::Input::Events::Record::target` and `focus_epoch` retain the owner at collection time; pending input is never retroactively assigned to a new focus target. Target zero means no focused recipient. `CE::Input::Tick::records_for(target[, epoch])` exposes the ordered Text and editing-control records for a consumer, while `gameplay_events()` filters physical records allowed through to gameplay. These are non-destructive views, not platform-thread widget callbacks; the game/UI dispatches or reads them during its update. A reused ID can filter by epoch when it represents a new consumer lifetime. - -`CE::Input::Routing::KeyboardPolicy::Exclusive` (default) withholds keyboard-derived semantic actions and keyboard records from gameplay while leaving mouse/controller input available. Other-device alternatives for the same action still work. `PassThrough` assigns the focused target and also permits keyboard gameplay input. `CE::Input::Actions::Bindings` implements only the general device gate, not textbox policy: it continues tracking physical keys while disabled. When focus ends, the next poll resumes currently held keys as semantic presses; physical Events do not fabricate matching hardware presses. A focus transition can therefore change State independently of a physical event. - -Focusing does not activate capture. A textbox separately holds Events and Text leases, then reads its routed records in shared sequence order. `CE::Input::Events::Text` inserts committed Unicode scalars. `CE::Input::Events::Button` carries press/repeat/release and modifiers for Backspace, Delete, arrows, Home/End, and shortcuts. The widget decides editing semantics, selection, clipboard behavior, and whether a repeated control applies. Capture supplies the records without prescribing a complete text editor or calling UI objects from GLFW callbacks. - -```cpp -auto events = input.capture(CE::Input::Capture::Channel::Events); -auto text = input.capture(CE::Input::Capture::Channel::Text); -auto focus = input.routing().focus(textbox_id); // Retain these handles while active. -// During update: read tick.input.records_for(textbox_id) in order. -// On blur: focus.reset(); text.reset(); events.reset(); -``` - -The toolkit-free demo probe requests Events for F2 and Text while focused. It edits -logical Unicode scalars with Backspace/Delete, arrows and Home/End, releasing focus -with Enter/Escape. WASD is gated while controller/scroll input remains available. -Its preview uses the [Unicode text service](../assets/text-layout.md); capture and -editing remain independent of font coverage. Grapheme/bidi caret behavior and IME -need separate editing contracts. - -Generic [state utilities](utility-contracts.md) are independent of input. They -define sampled state, synchronized value/revision storage, immediate observation -and transition tables, including callback and failure boundaries. `State` and -`Trigger` describe whole domains, such as enums with several values. A game can -compose machines and let a guard inspect another machine without prescribing -controllers or simulation threads. - -GLFW callback work catches its first failure and reports it from `update()` instead of unwinding through the C callback stack. Runtime cleanup detaches callbacks, clears focus and pending capture, and tears down rendering after a poll/worker failure. Already delivered immutable handles remain valid. Capacity limits completed polls, not records inside one pump; an OS pump can deliver many records, all retained when their channels were active. At shutdown the runtime discards pending input after stopping/joining simulation; it does not perform an extra final update. - -Validation procedures and coverage limits are in -[architecture-validation.md](../development/architecture-validation.md). diff --git a/docs/runtime/runtime-architecture.md b/docs/runtime/runtime-architecture.md deleted file mode 100644 index ed217e76..00000000 --- a/docs/runtime/runtime-architecture.md +++ /dev/null @@ -1,78 +0,0 @@ -# Runtime architecture and backend boundaries - -The selected backend is composed through `CE::Runtime::Context`; `CE::Runtime::GameRuntime` uses its -display, window, input, presentation, renderer, and resource contracts. The current -GLFW/OpenGL implementation and the in-memory integration probe follow those same -contracts. The [application guide](../development/consuming-engine.md#game-hooks-and-ownership) -shows composition and game hooks; [architecture validation](../development/architecture-validation.md) -describes isolation and integration checks. - -`CE::Game::AbstractGame` defines application hooks and consumes copied tick values. -`CE::Runtime` owns session composition and execution; `CE::Runtime::Simulation` -owns timing selection. `CE::Platform` supplies neutral display/window contracts, -with GLFW implementations in `CE::Platform::GLFW`. `CE::Rendering` supplies frame, -camera and renderer contracts, with native implementations in -`CE::Rendering::OpenGL`. These owners describe responsibility rather than changing -the thread or lifetime boundaries below. - -Applications may use [command-line startup](../development/consuming-engine.md#command-line-startup) -to validate execution, polling, timing and backend options before constructing a -context. The result owns that context and supplies the runtime configuration; -`CE::Runtime::GameRuntime::run()` owns adapter/game initialization and shutdown. -`CE::Application` startup support is separate from the Engine and OpenGL libraries and does not -select a backend dynamically. -The optional [Linux Debug terminal](../../projects/modules/platform/debug-terminal-linux/README.md) -attaches at process startup rather than becoming a `CE::Runtime::Context` adapter. Its -native code and viewer lifetime belong to the module; the `CE::Application::DebugTerminal` support hook -does nothing without a supplied implementation. - -| Boundary | Contract | -| --- | --- | -| CPU asset data | Vertex layouts, indexed sprite/tileset and shader recipes, grid generation, owned RGBA pixels and shader-byte snapshots have no graphics-context dependency. Loader preparation runs independently of a provider. | -| Display and presentation | `CE::Platform::DisplaySystem` owns a selected window implementation. `CE::Platform::Window` and monitor snapshots carry neutral data; `CE::Rendering::PresentationSurface` presents. GLFW handles remain inside the platform/backend implementation. The renderer does not own the display. | -| Input | `CE::Input::System` supplies State plus independently requested ordered Events/Text. GLFW/Gainput translation stays in its adapter; games consume tick values and routed views. The default factory owns input; an explicit-reference overload borrows it. | -| Draw and material | Simulation writes complete ordered `CE::Rendering::Frame` passes. CPU submission helpers resolve sprite, tile, graphic, and glyph packets retaining geometry/material generations and copied values. `CE::Rendering::OpenGL::PipelineBindings` maps contract keys to native names; each pipeline applies full fixed state. | -| Resource creation and caches | `CE::Rendering::Resources::Provider` uploads decoded pixels and transient vertex spans, creates font atlases, and links programs. Optional sampler and shader-recipe builders reject unsupported requests. Cache readers retain handles under shared locks; construction and retired-handle destruction stay outside locks. One provider/loading thread binds the singleton caches until teardown. | -| Composition and scheduling | One runtime session owns platform polling and presentation. Concurrent mode adds one simulation worker and three reusable frame slots. `platform_dispatcher().submit()` transfers owned resource requests with future results; shutdown cancels pending requests before game cleanup. | -| OpenGL lifetime | Active resource use requires the owner thread and its actual current context. Handles retire without OpenGL calls from their destructors. Renderer shutdown restores its context, deletes tracked handles, closes their lifetime, and releases the context. Failed destructor cleanup invalidates retained handles. | -| Portable integration | The in-memory display/window, input, presentation, renderer, and resource provider exercise both runtime modes without OpenGL/GLFW headers. Aggregate regressions cover lifecycle, dispatch, cache publication, materials, preparation, and input routing. | - -## Application APIs - -Use `engine.display()`, `engine.window()`, and `engine.surface()` for the selected -platform and presentation contracts. -Use `engine.resources()` for backend asset creation after renderer initialization. -Construct an owned `CE::Assets::Loading::Loader(root)` for each root. `manifests()` returns a retained -immutable snapshot; singleton compatibility rejects a different root after its -first initialization. - -The [loading guide](../assets/asset-loading.md) owns preparation/publication and -application bootstrap; the [render guide](../rendering/pipelines-and-materials.md) -owns program/material construction and packet resolution. Optional -[audio](audio.md) is application-owned outside the runtime adapter graph. - -## Execution and shutdown - -Games request shutdown from `update()` with `tick.request_stop()`. The tick carries -the runtime's shared stop state, so games need no runtime singleton or callback -wiring. Applications holding the runtime can still call `CE::Runtime::GameRuntime::stop()`; -both requests use the same normal cleanup sequence. The stop request and tick -lifetime contracts are in [runtime-frame-boundary.md](runtime-frame-boundary.md). - -Platform and simulation requests use separate dispatchers with safe saved submission -endpoints. `CE::Events::EventBus` owns persistent registrations; delivery adapters select platform, -simulation, or serial worker-stream execution. `CE::Runtime::Context` creates tracked worker -groups on a lazy owned pool or an injected shared pool. The dedicated simulation -thread remains separate from that general CPU capacity. See -[thread-dispatch.md](thread-dispatch.md), [event-delivery.md](event-delivery.md), and -[worker-execution.md](worker-execution.md). - -The [frame boundary](runtime-frame-boundary.md#shutdown) owns shutdown ordering, -including producer quiescence and accepted-work completion. Native callback and -emergency fallback boundaries are in [failure-reporting.md](failure-reporting.md). - -## Current limits - -The [status inventory](../STATUS.md) records source-backed capabilities and known -gaps at its review baseline. The [planning catalogue](../planning/README.md) links -their owning plans; each subject guide defines its current capability limits. diff --git a/docs/runtime/runtime-frame-boundary.md b/docs/runtime/runtime-frame-boundary.md deleted file mode 100644 index 35fdccb2..00000000 --- a/docs/runtime/runtime-frame-boundary.md +++ /dev/null @@ -1,139 +0,0 @@ -# Runtime frame boundary - -`CE::Runtime::GameRuntime` coordinates input polling, simulation, rendering, presentation, and -shutdown. Both execution modes use the [same configurable timing policy](simulation-timing.md), with variable/fixed steps and bounded direct/hybrid recovery. The application chooses a compatible platform, graphics context, -renderer, resources, and input adapter. The renderer uses the selected context -and render target; it does not own the display system or read live game state. -The presentation surface presents a completed frame. -The GLFW/OpenGL factory accepts the game's window configuration, constructs the -display and window, and lends the window to its presentation context. The display -owns the GLFW library lifetime; renderer initialization makes the context current -and loads OpenGL entry points before game initialization uploads assets. - -## Input and owner threads - -Each platform poll produces a timestamped `CE::Input::Actions::Snapshot`. Each independently -scheduled `update()` consumes the complete pending batch in one `CE::Input::Tick`. -Input supplies edges, counts, and observed hold durations; it never subdivides -simulation time or schedules extra updates. Simulation -produces complete render state, which remains stable while rendering consumes it. The -renderer must never access mutable simulation objects across that boundary. -Each complete `CE::Input::PollSnapshot` carries State and independently captured ordered -Events/Text records. The runtime transfers both at the same consumption boundary; -`CE::Input::Tick::records()` retains their shared observation order. Scoped capture -requests and backend fidelity are documented in -[input-state-model.md](input-state-model.md#ordered-events-and-os-text). -Each tick also carries logical window and framebuffer dimensions sampled together -on the platform owner. Simulation uses these copied values for UI layout and pixel -scaling without reading the live window. Manually constructed ticks supply their -logical dimensions explicitly; the default is zero. -These rules also apply when simulation and rendering run sequentially. The -sequential runtime owns one reusable frame. Concurrent mode owns three slots: -one may be rendered, one may be the latest completed frame, and one may be in -preparation. Platform event polling and graphics-context use respect their -respective thread affinity. - -The caller of `CE::Runtime::GameRuntime::run()` owns platform polling and the graphics context -in both modes. Sequential mode polls, updates, prepares, renders, and presents on -that thread. Concurrent mode runs only `update()` and `prepare_render_frame()` on -one simulation worker; it passes complete polling batches and framebuffer dimensions -as values instead of giving that worker access to a live window. A resize -callback runs on the platform thread and must not mutate simulation state. -`init()` runs after input and graphics initialization but before starting the -worker, so it can register bindings and load GPU assets. After the worker joins -and frames are recycled, `deinit()` runs while the graphics context is current. -The default GLFW/OpenGL factory owns its input adapter; the overload taking an -input reference borrows it through context destruction. Owned input detaches -before the window is destroyed. A runtime and its context each permit one session. -Game cleanup is paired with attempted initialization, including partial failure; -cleanup preserves the first failure while still shutting down both adapters and -reports subsequent failures with phase context. See [failure-reporting.md](failure-reporting.md) -for deferred native callback checks and the bounded emergency reporting contract. - -## Queued preparation and stop requests - -`engine.platform_dispatcher().submit(work)` transfers owned request data to the platform -thread, where graphics is current. The result is a future: inspect readiness from -`update()`, then publish the resulting handle through the next render frame. Do not -block simulation on it or capture live simulation objects in platform callbacks. -The queue drains FIFO batches before polling, including while the input backlog -is full; nested requests wait for the next batch. Futures, cancellation and saved -submission endpoints follow the [dispatcher contract](thread-dispatch.md). -Accepted dependencies settle under the [shutdown sequence](#shutdown) below. -Callbacks must preserve the session's current graphics context. Backend resource -guards reject another context even when it is selected on the correct thread. -Game code calls `tick.request_stop()` to request graceful shutdown from `update()`. -Every `CE::Game::TickContext` shares the session's `std::stop_source` with `CE::Runtime::GameRuntime::stop()`; -both entry points wake scheduler waits and are safe to repeat. The request ends -further simulation updates and uses the normal join, quiesce and cleanup sequence. -It does not interrupt the current `update()`; return early when further game work -is unnecessary. Sequential execution can still prepare and present its final -updated frame before teardown. -A copied `tick.runtime_stop` retains only stop state and remains safe after runtime -destruction; do not retain the tick itself or its borrowed input view. Manually -constructed ticks default to no runtime stop source; `request_stop()` then throws -`failed_operation`. Tests or another scheduler can supply a source explicitly. - -## Frame publication and recycling - -`CE::Game::AbstractGame::prepare_render_frame(writer)` runs on the simulation thread after -the final useful update of a bounded batch when a slot is free. If all slots are occupied, the update still -advances the simulation and only that tick's visual snapshot is skipped. The -runtime lends the game a reusable `CE::Rendering::Frame` slot through the writer. The game -fills ordered passes with copied camera values, custom pass parameters, constraints, -and resolved `CE::Rendering::DrawPacket2D` values. Asset helpers select geometry ranges and lay out -text on simulation, resolving transforms, engine semantics, custom parameters, and -image requests into owned values. Packets retain geometry and material generations; -no asset, font interface, string, animation cursor, or live entity is needed by playback. -The caller model carries placement/rotation; `CE::Rendering::DrawStyle2D::scale` also scales local -geometry and glyph offsets explicitly. The runtime publishes the complete slot without -copying it and prevents further mutation while the renderer consumes it. - -Recycling a superseded or shutdown slot clears only the active passes' draw -packets and pass parameters, releasing their resource handles on the graphics thread while its context -is current. The platform retains its latest complete slot between publications. Recycling keeps pass objects and draw-vector capacity for later ticks. One -slot suffices when update and render are sequential; concurrent update/render -need at least two. A third permits a completed frame to wait while one is being -written and one is being rendered. -Text strings may still allocate, and the final release of a GPU-backed handle -must occur on the graphics thread or through backend-managed deferred destruction. - -At each concurrent render handoff, platform claims the newest completed slot. -If simulation supersedes a waiting frame, platform recycles that older slot before -lending it out again. Rendering consumes authored packet order and complete -validated pipeline state. The [render contract](../rendering/pipelines-and-materials.md) -owns packet resolution and writer rules; [asset playback](../assets/asset-values-and-playback.md) -and [Unicode text](../assets/text-layout.md) resolve their values before publication. - -Polling admission and fresh-batch consumption follow the -[input contract](input-state-model.md#polling-backlog-and-scheduling). Frame -preparation occurs once after a bounded update batch under the -[timing policy](simulation-timing.md). Retirement maintenance runs independently -of new frames under the [residency contract](../resources/resource-residency.md#native-retirement-and-maintenance). -The [demo guide](../../projects/apps/demo/README.md) owns its options, controls and -application bootstrap. - -## Shutdown - -Stopping closes context CPU-group acceptance and stops simulation. Simulation -cancels its pending dispatcher work on its owner before publishing completion; -initialization failure before owner binding cancels on the initializing thread. -Platform keeps pumping accepted dependencies while joining simulation, so an -accepted callback awaiting platform work cannot strand it in a blocking join. - -After simulation joins, `game.quiesce()` stops external producers and invalidates -borrowed event registrations while their targets/resources remain alive. Do not -block this hook on work that still needs platform dispatch. It pairs with attempted -game initialization, including partial startup. Accepted CPU work then settles -while platform continues servicing dependencies; bounded 1 ms shutdown waits -observe completion without borrowed runtime wake callbacks in workers. Only -context-created groups close; an injected pool's unrelated groups remain open. -An owned root joins before game/resource cleanup. - -Remaining platform requests cancel, all retained frames recycle on graphics, -and `game.deinit()` runs before input and graphics shutdown. Preserve the first -failure while completing cleanup; report later failures with phase context through -[failure reporting](failure-reporting.md). Owned input detaches before window -destruction, and the context must outlive its renderer. External logical handles -follow the [native lifetime contract](../resources/resource-residency.md): they can -survive for destruction/CPU inspection but cannot draw through a closed domain. diff --git a/docs/api/index.md b/docs/site/api/index.md similarity index 50% rename from docs/api/index.md rename to docs/site/api/index.md index cd777728..6fbe038b 100644 --- a/docs/api/index.md +++ b/docs/site/api/index.md @@ -1,9 +1,22 @@ # C++ API The reference is generated from the public headers in this documentation version. -It covers Cheryl-owned engine headers, startup and worker-topology support, -and the GLFW, OpenGL, TGUI, RmlUi and miniaudio modules. These are backend modules; -their availability depends on the targets enabled in your build. +It covers Cheryl-owned Engine headers, public Startup/Workers modules, +and the GLFW, OpenGL, TGUI, RmlUi and miniaudio modules. Module +availability depends on the targets enabled in your build. + +For a known symbol, use **Symbols** below or search its qualified name. For an +operation you have not chosen an API for, start with the +[Engine Systems tables](../engine-systems/index.md#find-a-system) or the +[task index](../getting-started/how-to/index.md), then return here for the exact +contract. [Migration](#migration) is for existing consumers moving from the older +names and binary boundaries; new projects can use the current entry points. + +```{toctree} +:maxdepth: 2 + +Symbols <../generation/generated/api/library_root> +``` ## Namespaces @@ -36,14 +49,32 @@ portable facilities are still provided by `Cheryl::Engine`. ## Entry points +The [Engine Systems tables](../engine-systems/index.md#find-a-system) group the +public types and important enums by task, with detailed contract links. For +physical control names, start with [Input controls](../engine-systems/input.md#controls-for-bindings); +for backend implementations, start with the +[adapter catalogue](../architecture/interfaces-adapters.md). + +Include `` and link `Cheryl::Cheryl` for the complete selected +assembly. Individual module umbrellas live under `cheryl/modules/`, and support +umbrellas under `cheryl/support/`. The [consumer guide](../development/consuming-engine.md#dependencies-and-headers) +explains source-checkout and SDK target selection. + | Task | Entry point | Declaring header | | --- | --- | --- | -| Configure application startup | {cpp:class}`CE::Application::Startup` | [startup.h](../../projects/engine/support/startup/include/cheryl/core/engine/startup.h) | -| Implement game hooks | {cpp:struct}`CE::Game::AbstractGame` | [abstract-game.h](../../projects/engine/include/cheryl/core/game-framework/abstract-game.h) | -| Prepare and upload assets | {cpp:struct}`CE::Assets::Loading::Loader` | [asset-loader.h](../../projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h) | -| Select immutable font data | {cpp:class}`CE::Text::FontCollection` | [font-collection.h](../../projects/engine/include/cheryl/text/font-collection.h) | -| Retain sprite resources | {cpp:struct}`CE::Assets::Sprites::Asset` | [sprite.h](../../projects/engine/include/cheryl/assets/types/2d/sprite.h) | -| Find loaded sprites | {cpp:struct}`CE::Assets::Sprites::Manager` | [sprite-mgr.h](../../projects/engine/include/cheryl/core/resources/asset-management/sprite-mgr.h) | +| Configure application startup | {cpp:class}`CE::Application::Startup` | [startup.h](../../../projects/modules/runtime/startup/include/cheryl/application/startup.h) | +| Implement game hooks | {cpp:struct}`CE::Game::AbstractGame` | [abstract-game.h](../../../projects/engine/include/cheryl/core/game-framework/abstract-game.h) | +| Choose physical input controls | {cpp:enum}`CE::Input::KeyboardKey`, {cpp:enum}`CE::Input::MouseButton`, {cpp:enum}`CE::Input::GamepadControl` | [button-codes.h](../../../projects/engine/include/cheryl/core/controls/button-codes.h) | +| Attach reusable per-consumer action bindings | {cpp:class}`CE::Input::Actions::ContextSubmission`, {cpp:class}`CE::Input::Actions::Context` | [action-context.h](../../../projects/engine/include/cheryl/core/controls/action-context.h); [action contexts](../runtime/input-state-model.md#action-contexts) | +| Prepare and upload assets | {cpp:struct}`CE::Assets::Loading::Loader` | [asset-loader.h](../../../projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h) | +| Configure the application's default and named fonts | {cpp:class}`CE::Text::FontService`, through `Context::fonts()` | [font-service.h](../../../projects/engine/include/cheryl/text/font-service.h); [configuration](../assets/text-layout.md#configure-fonts-once) | +| Select, save and restore a font choice | {cpp:class}`CE::Text::Font` and `FontService::choose_random`/`restore_or_choose` | [font.h](../../../projects/engine/include/cheryl/text/font.h); [saved choices](../assets/text-layout.md#choose-and-save-a-font) | +| Load a separate ordered font snapshot | {cpp:class}`CE::Text::FontCollection` | [font-collection.h](../../../projects/engine/include/cheryl/text/font-collection.h) | +| Retain and asynchronously replace a changing text label | {cpp:class}`CE::Text::Rendering::Updater`, {cpp:struct}`CE::Text::Rendering::Request` | [text-updater.h](../../../projects/engine/include/cheryl/text/text-updater.h); [retained text updates](../assets/text-layout.md#retained-text-updates) | +| Rasterize toolkit font glyphs on CPU | {cpp:class}`CE::Text::FontRasterizer` | [font-rasterizer.h](../../../projects/engine/include/cheryl/text/font-rasterizer.h) | +| Retain sprite resources | {cpp:struct}`CE::Assets::Sprites::Asset` | [sprite.h](../../../projects/engine/include/cheryl/assets/types/2d/sprite.h) | +| Draw a whole PNG without a sprite manifest | {cpp:struct}`CE::Assets::Graphics::Asset` | [graphic.h](../../../projects/engine/include/cheryl/assets/types/2d/graphic.h); [whole-image sequence](../getting-started/how-to/draw-sprite.md#draw-a-whole-image) | +| Find loaded sprites | {cpp:struct}`CE::Assets::Sprites::Manager` | [sprite-mgr.h](../../../projects/engine/include/cheryl/core/resources/asset-management/sprite-mgr.h) | See {cpp:enum}`CE::Runtime::Simulation::UpdateKind` for simulation update kinds, {cpp:type}`CE::Text::FontSource` for font selection sources, and @@ -70,9 +101,17 @@ in a maintained application. ## Migration -This reorganization changes C++ source names and ABI. Rebuild consumers with the -matching engine version. Existing include paths and CMake target names remain -available; old names are not re-exported through compatibility aliases. +The SDK/Engine/Startup/Workers major version is 4; Native GLFW, OpenGL, TGUI and +RmlUi are major version 2 because their public layouts/implementation boundaries +changed. Audio and the static terminal attachment retain their component versions. +Rebuild consumers with one matched header/binary set. Startup/worker compatibility +headers/targets remain available where their capability is enabled; the older +C++ namespace names below are not re-exported as aliases. + +Use the generic `CE::Application::make_startup()` and Cheryl-owned logging levels, +file callbacks, Engine input control enums/device snapshots and compiled OpenGL methods. +Private spdlog/Gainput/GLFW/GLAD implementation headers are no longer application +requirements. Native toolkit authoring remains part of the UI owners' public ABI. | Previous name | Current name | | --- | --- | @@ -101,8 +140,24 @@ resource kind. The shader catalogue uses `find_program()` and `find_material()`; the image manager's decoded-image upload is `upload()`. The aggregate loading entry point remains `CE::Assets::Loading::Loader::load_assets()`. -```{toctree} -:maxdepth: 2 +### Input bindings -Symbols <../generation/generated/api/library_root> -``` +Bind Engine enums directly, for example +`bindings.bind_button({input.keyboard_id(), CE::Input::KeyboardKey::Escape}, quit)`. +The same form accepts mouse buttons and gamepad controls, including in chords. + +| Previous interface | Current use | +| --- | --- | +| `CE::Platform::GLFW::keyboard_control(key)` / `mouse_control(button)` | Pass `CE::Input::KeyboardKey` / `CE::Input::MouseButton` directly. | +| `CE::Platform::GLFW::GamepadControl` constants | Use the scoped `CE::Input::GamepadControl` enum. Names include `DPadLeft`, `Back`, `Guide`, `LeftShoulder`, `LeftTriggerButton` and `LeftStick`; use `LeftTrigger` for its axis. Right-side equivalents follow the same naming. | +| `CE::Platform::GLFW::MouseControl` | Use `CE::Input::MouseControl` for absolute axes and wheel pulses as well as relative movement/scroll. | +| `CE::Platform::GLFW::InputMapper` | Access `CE::Input::Actions::Bindings` through `input.bindings()`. | + +Remove includes of the deleted `glfw-bindings.h` and `input-mapper.h`; include +`` alongside the input interface. These +removed interfaces have no compatibility aliases. Direct foreign-enum bindings +fail at compile time; numeric casts of Gainput/GLFW constants do not produce +Engine control IDs. Keep raw `ControlId` values for explicitly defined adapter +extensions. The [binding task](../getting-started/how-to/bind-input.md) and +[control guide](../runtime/input-state-model.md#control-identities-and-binding-tokens) +show the current headers, validation and examples. diff --git a/docs/site/architecture/assembling-engine.md b/docs/site/architecture/assembling-engine.md new file mode 100644 index 00000000..e79861e7 --- /dev/null +++ b/docs/site/architecture/assembling-engine.md @@ -0,0 +1,41 @@ +--- +orphan: true +--- + +# Assembling the Engine + +Application composition is now part of +[Source and SDK integration](../development/consuming-engine.md). This page preserves +the previous URL and section bookmarks. + +## Facades and public includes + +See [public target selection](../development/consuming-engine.md#granular-composition) +and [Dependencies and headers](../development/consuming-engine.md#dependencies-and-headers). + +## Factories and context composition + +See [Context assembly](../development/consuming-engine.md#context-assembly) and +[Command-line startup](../development/consuming-engine.md#command-line-startup). + +## Backend boundaries + +See [Context assembly](../development/consuming-engine.md#context-assembly), +[Engine Systems](../engine-systems/index.md) and +[Integration contracts](interfaces-adapters.md). + +## Using the assembled context + +See [Context assembly](../development/consuming-engine.md#context-assembly) and +[asset preparation/publication](../assets/asset-loading.md#runtime-entry-point). + +## Execution and shutdown + +See [Ownership and cleanup](ownership-cleanup.md#shut-down-in-dependency-order), +[Threads and data flow](threads-data-flow.md) and +[the runtime lifecycle](../runtime/runtime-frame-boundary.md). + +## Current limits + +See each subject guide's current limits and the +[capability inventory](../../STATUS.md). diff --git a/docs/site/architecture/index.md b/docs/site/architecture/index.md new file mode 100644 index 00000000..fc5776c5 --- /dev/null +++ b/docs/site/architecture/index.md @@ -0,0 +1,19 @@ +--- +orphan: true +--- + +# Architecture + +Architecture guidance now lives with its readers' tasks: + +- [Source and SDK integration](../development/consuming-engine.md) covers public + targets, headers and compatible application context assembly. +- [Ownership and cleanup](ownership-cleanup.md) and + [Threads and data flow](threads-data-flow.md) explain application lifetime and + execution decisions. +- [Integration contracts](interfaces-adapters.md) and + [Module authoring](../development/modules.md) cover implementing integrations. +- [Engine Systems](../engine-systems/index.md) maps public types and relationships; + [Guides by topic](../guides/index.md) owns the detailed procedures. + +This page preserves the previous overview URL. diff --git a/docs/site/architecture/interfaces-adapters.md b/docs/site/architecture/interfaces-adapters.md new file mode 100644 index 00000000..d4d2ae26 --- /dev/null +++ b/docs/site/architecture/interfaces-adapters.md @@ -0,0 +1,107 @@ +(interfaces-and-adapters)= +# Integration contracts + +Use this catalogue when implementing or replacing an integration. A runtime +context needs five compatible adapters: display, input, presentation, renderer +and resource provider. Display supplies the window contract as well. These are +interface obligations, not a requirement for five libraries. The +[Engine Systems overview](../engine-systems/index.md) explains their relationships. + +## Runtime adapters + +The runtime uses this adapter graph; the implementation tables below name the +existing modules and each interface's obligations. + +```mermaid +flowchart TB + accTitle: Required runtime adapters + accDescr: A Context owns display, presentation, renderer and resources, and owns or borrows input. Display owns the window borrowed by input and presentation. + context["Context"] -->|owns| display["Display"] + context -->|owns| presentation["Presentation"] + context -->|owns| renderer["Renderer"] + context -->|owns| resources["Resource provider"] + context -->|owns or borrows| input["Input"] + display -->|owns| window["Window"] + input -->|borrows| window + presentation -->|borrows in the native assembly| window +``` + +| Contract | Existing implementation | Obligations and detailed contract | +| --- | --- | --- | +| {cpp:class}`CE::Platform::DisplaySystem` | `CE::Platform::GLFW::DisplaySystem` | Own windows, expose monitor inventory and scale, create/select a compatible active window; document borrowed inventory freshness. [Display ownership](../runtime/display-and-window-contract.md); [Native GLFW](../modules/native-glfw.md); [platform replacement and current OpenGL coupling](#replacing-a-platform-integration). | +| {cpp:class}`CE::Platform::Window` | `CE::Platform::GLFW::Window` | Report logical size, drawable pixels, mode and close state; implement resize/mode/cursor operations and surface deferred native failures. The display owns the window. [Sizes, modes and observation](../runtime/display-and-window-contract.md#sizes-modes-and-observation); [native callback failures](../runtime/failure-reporting.md#native-callbacks). | +| {cpp:class}`CE::Input::System` | `CE::Platform::GLFW::InputSystem` | Attach/detach from a live window, identify devices, normalize standard controls to Engine IDs, own bindings and publish complete polls. Declare Events, Text and focus support; latch supported capture/routing before collection. [Input adapter publication](../runtime/input-state-model.md#adapter-publication); [Native GLFW](../modules/native-glfw.md). | +| {cpp:class}`CE::Rendering::PresentationSurface` | `CE::Rendering::OpenGL::GLFWContext` | Present on the platform/presentation owner while its borrowed window and graphics dependencies remain live. Backend-specific context operations extend this contract. [Context assembly](../development/consuming-engine.md#context-assembly); [OpenGL](../modules/opengl.md). | +| {cpp:class}`CE::Rendering::Renderer` | `CE::Rendering::OpenGL::Renderer` | Initialize/close graphics, consume complete ordered frames, apply viewport/camera/draw state and maintain deferred resources even without a new frame. [Frame publication](../runtime/runtime-frame-boundary.md#frame-publication-and-recycling); [native retirement](../resources/resource-residency.md#native-retirement-and-maintenance). | +| {cpp:struct}`CE::Rendering::Resources::Provider` | `CE::Rendering::OpenGL::ResourceProvider` | Upload images, alpha atlases and 2D geometry, and link executable programs. Copy transient CPU inputs; return retained immutable generations governed by one backend domain. [Publication and retirement](../resources/resource-residency.md); [OpenGL](../modules/opengl.md). | + +The context owns display, presentation, renderer and resources, and either owns +or explicitly borrows input. Returned windows and context accessors borrow their +owners. Initialization, partial failure and teardown must preserve those +dependencies; follow [runtime shutdown](../runtime/runtime-frame-boundary.md#shutdown) +rather than inferring cleanup order from the interface list. + +## Backend capabilities and resource implementations + +| Contract | Existing implementation | Obligations and detailed contract | +| --- | --- | --- | +| {cpp:class}`CE::Rendering::OpenGL::Context` | `CE::Rendering::OpenGL::GLFWContext` | A platform using Cheryl's OpenGL renderer supplies current-context acquisition/release, currentness checks, procedure lookup and presentation. The current implementation is `GLFWContext`. [OpenGL bootstrap and binding](../rendering/pipelines-and-materials.md#native-bootstrap-and-binding); [OpenGL owner](../modules/opengl.md). | +| {cpp:struct}`CE::Rendering::Resources::Image`, {cpp:struct}`CE::Rendering::Geometry::Buffer2D`, {cpp:struct}`CE::Rendering::Resources::Program` | OpenGL `Texture`, `VertexArray` and `Program` | Graphics implementations supply retained images, geometry metadata/draw operations and executable-program binding. Native use requires the original live domain and owner. [Resource lifetime](../resources/resource-residency.md#native-retirement-and-maintenance); [draw and material binding](../rendering/pipelines-and-materials.md#independent-binding). | +| {cpp:class}`CE::Rendering::Resources::Pipeline` | `CE::Rendering::OpenGL::Pipeline` | A graphics backend retains its executable and immutable pipeline definition; playback applies the declared fixed state and parameter bindings. `Material` is an Engine-owned value retaining that pipeline. [Pipelines and materials](../rendering/pipelines-and-materials.md); [manual construction](../rendering/pipelines-and-materials.md#manual-material-construction). | +| {cpp:class}`CE::Rendering::Sampling::Sampler` | `CE::Rendering::OpenGL::Sampler` | Optional `Provider::create_sampler()` capability. Preserve immutable filtering/wrap choices independently of image contents and expose effective anisotropy. Unsupported creation rejects. [Immutable sampling](../rendering/pipelines-and-materials.md#immutable-sampling). | +| {cpp:struct}`CE::Assets::Shaders::Builder` | OpenGL resource provider | Optional provider-owned shader/material recipe capability. Validate on CPU, then build complete candidates on the provider's loading owner. `shader_asset_builder()` returns a borrowed pointer or null. [Shader/material definitions](../assets/asset-manifests.md#shadermaterial-definitions); [publication and retry](../assets/asset-loading.md#publication-and-retry). | +| {cpp:class}`CE::Workers::TopologyProvider` | Workers hwloc provider | Optional native topology/affinity integration. Return owned eligible-CPU/core/cache observations, bind only the calling thread and read back its actual mask; support concurrent pool workers. [Topology providers](../runtime/worker-execution.md#topology-providers-and-capacity); [Workers module](../modules/workers.md). | + +Colored-geometry upload is another optional provider operation; unsupported +layouts reject rather than silently dropping color. Input State is always +available, while Events, Text and focus have explicit support queries. Optional +capabilities must remain observable to consumers; implementing the base interface +does not promise every extension. + +## Integrations alongside the runtime + +| Integration and obligations | +| --- | +| Audio
{cpp:class}`CE::Audio::System` and {cpp:class}`CE::Audio::Voice`: synchronized playback/control, an independent output clock, maintenance and safe closed handles. The application owns output lifetime. [Audio ownership](../runtime/audio.md); [miniaudio](../modules/miniaudio.md). | +| UI toolkit
Consume neutral input records/focus, copied dimensions, CPU font/image services and immutable resource/frame publication. Each toolkit owns its session and native authoring API; there is no common Engine widget/session base to implement. [UI bridge responsibilities](../development/ui-adapters.md#bridge-responsibilities); [TGUI](../modules/tgui.md), [RmlUi](../modules/rmlui.md). | +| Queued event delivery
{cpp:type}`CE::Events::EventBus::Delivery` and {cpp:type}`CE::Events::EventBus::ErrorHandler`: own accepted work without inline execution, preserve a listener's FIFO stream and report failure/cancellation. Supplied adapters target platform, simulation or a worker stream. [Queued delivery](../runtime/event-delivery.md#optional-queued-delivery); [worker cancellation](../runtime/event-delivery.md#worker-stream-acceptance-and-cancellation). | +| Startup/configuration
Compose compatible adapters and validate configuration before the runtime session. Startup integrates selected backends; worker JSON loading supplies configuration rather than another executor. [Startup](../modules/startup.md), [Workers](../modules/workers.md); [source/SDK composition](../development/consuming-engine.md). | +| Process output attachment
Supply the optional Debug terminal support callback and own its native viewer/process lifetime. This source-only attachment sits outside the context adapter graph. [Linux Debug terminal](../modules/debug-terminal-linux.md); [logging output](../runtime/logging.md#native-output-terminal). | + +## Replacing a platform integration + +For an SDL3 replacement, the platform work maps to `DisplaySystem`, `Window` and +`Input::System`. Keeping Cheryl's OpenGL renderer also requires an OpenGL +`Context` bridge and compatible resource lifetime. The current OpenGL target and +startup factory select Native GLFW explicitly; a new platform therefore needs +composition/build selection changes as well as interface implementations. +[Application composition](../development/consuming-engine.md#context-assembly), +the [OpenGL owner](../modules/opengl.md) and the [Startup guide](../modules/startup.md) +describe that current coupling. + +Keyboard, mouse-button and gamepad enums belong to Engine and are accepted +directly by bindings; see +[control identities and binding tokens](../runtime/input-state-model.md#control-identities-and-binding-tokens). +A replacement normalizes its native codes to those standard control IDs before +binding evaluation. It must also fulfill polling, capture, routing and native +failure contracts, and state which device capabilities it supports. Backend +selection still determines available devices and observations. + +## Implement and connect a replacement + +1. Select the responsibilities you are replacing in the catalogue. Keep the + portable interfaces in Engine and the concrete implementation in its module. +2. Use the implementation column above to identify cooperating owners. Replace + or bridge every affected domain before creating a runtime context. +3. Implement the required operations and report optional capabilities explicitly. + Unsupported requests reject instead of silently losing behavior. +4. Assemble the components through a factory, preserving their ownership and + initialization dependencies. Use the existing module's independent consumer + and the architecture validation procedures as conformance examples. + +[Source and SDK integration](../development/consuming-engine.md) explains application +factories and public targets; [Module authoring](../development/modules.md) covers +target registration and independent owners. Validate the replacement with +[independent consumers and header probes](../development/architecture-validation.md#independent-consumers-and-header-probes). +Runtime-owned and application-owned objects follow +[ownership and cleanup](ownership-cleanup.md). diff --git a/docs/site/architecture/ownership-cleanup.md b/docs/site/architecture/ownership-cleanup.md new file mode 100644 index 00000000..e3e628d1 --- /dev/null +++ b/docs/site/architecture/ownership-cleanup.md @@ -0,0 +1,90 @@ +# Ownership and Cleanup + +Decide what an application component owns, what it borrows, and which work must +finish before its dependencies can be destroyed. A C++ handle's lifetime and a +native backend's usable lifetime are separate obligations. + +## Choose what to retain + +| Relationship | Application responsibility | Cheryl examples | +| --- | --- | --- | +| Own an object | Control destruction and finish its callers before releasing it. | A Startup result owns its context; the application owns its game, GUI session and audio system. | +| Borrow an object | Keep its owner alive and obey the object's access rules. | A runtime borrows its context/game; context accessors and display-owned windows return borrowed access. | +| Retain an immutable result | Keep a strong handle for as long as that version is needed. | Assets, input poll snapshots, uploaded text and frame packets retain their selected data. | +| Hold a scoped request | Retain the lease while the request is wanted; release it when that consumer finishes. | Input Events/Text capture and keyboard focus. | +| Save a submission endpoint | Own callback inputs or keep borrowed captures alive through completion or cancellation. | Dispatcher submissions remain safe to call after closure, but reject new work and do not retain the runtime. | + +For example, two game objects can retain the same sprite while keeping independent +positions and animation cursors. Dropping one object's handle releases only that +owner. A manager cache or published frame can still retain the sprite's resources. + +```mermaid +flowchart LR + accTitle: Independent owners of shared appearance + accDescr: A cache, two actors and a published frame can all retain the same immutable appearance resources. Releasing one owner leaves the others intact. + cache["Asset cache"] -->|retains| appearance["Immutable appearance resources"] + first["First actor"] -->|retains| appearance + second["Second actor"] -->|retains| appearance + frame["Published frame packets"] -->|retain| appearance +``` + +The [resource guide](../resources/resource-residency.md#cache-ownership) explains +which owners to inspect before treating continued residency as a leak. + +## Use a lease for a temporary input request + +With a live input system and an application-selected nonzero `editor_target`: + +```cpp +auto events = input.capture(CE::Input::Capture::Channel::Events); +auto text = input.capture(CE::Input::Capture::Channel::Text); +auto focus = input.routing().focus(editor_target); +// Retain these handles while the editor receives input. +// During update, read tick.input.records_for(editor_target) in order. +focus.reset(); +text.reset(); +events.reset(); +``` + +Keep the handles as editor members when the request spans updates. Their +destructors also release the requests. An older focus lease cannot clear a newer +request. These leases retain request state, not an input adapter, window or widget; +capture and focus take effect at a subsequent poll. Toolkit sessions manage their +own leases through their focus methods. The +[input guide](../runtime/input-state-model.md#focus-routing-and-editing-controls) +defines poll epochs and already captured records. + +## Keep native domains alive for native use + +A retained draw packet keeps geometry, material and image generations alive. It +does not keep a destroyed graphics context usable. Likewise, an audio voice handle +can survive system closure but then reports `Closed`; controls reject. + +CPU release state can have a different lifetime. Pooled object handles retain +their return/construction contexts, allowing final release after a pool facade +dies. Their own callers still need to finish before that facade is destroyed. +Use [CPU memory lifetime](../resources/resource-lifetime.md) when allocating +application objects, and [native retirement and maintenance](../resources/resource-residency.md#native-retirement-and-maintenance) for GPU handles. + +## Shut down in dependency order + +The runtime provides the sequence below for context-owned work. Application-owned +services join that sequence through the game's hooks: + +| Boundary | What the application does | +| --- | --- | +| Request stop | Return from the current update when no further game work is needed. A stop request does not interrupt an executing callback. | +| Simulation stops and joins | Close simulation dispatch and call `deinit_simulation()` on its owner before it exits. Retain state still used by external producers or accepted work; transfer final UI teardown to platform. | +| `quiesce()` | Stop external producers and invalidate borrowed event listeners while their targets remain alive. Avoid waiting for work that still needs platform dispatch. | +| Accepted work settles | Keep worker inputs, upload dependencies and callback targets alive while platform continues servicing accepted dependencies. | +| Frames recycle and `deinit_platform()` runs | Release game resources and application-held toolkit objects; close GUI sessions after UI quiescence. Settle and close separately owned audio services. | +| Adapters close | Input detaches from a live window; native resources retire before graphics/window destruction. | + +Cleanup pairs with attempted initialization, so retain only successfully acquired +handles and make cleanup tolerate partial startup. For borrowed `[this]` event +callbacks, use `unregister_and_wait()` before destroying the target; ordinary +unregister prevents new entry but permits an already running callback to finish. + +The [runtime lifecycle](../runtime/runtime-frame-boundary.md#shutdown) owns the +exact shutdown and failure order. [Threads and Data Flow](threads-data-flow.md) +explains which dependencies require continued owner-thread progress. diff --git a/docs/site/architecture/threads-data-flow.md b/docs/site/architecture/threads-data-flow.md new file mode 100644 index 00000000..17dd6351 --- /dev/null +++ b/docs/site/architecture/threads-data-flow.md @@ -0,0 +1,107 @@ +# Threads and Data Flow + +Choose where work runs, give that work stable inputs, and publish owned results +at the next appropriate boundary. Sequential and concurrent runtime modes use the +same ownership rules; concurrent mode makes crossing them explicit. + +## Choose where work runs + +| Work | Execution owner | Transfer mechanism | +| --- | --- | --- | +| Poll native input, operate a window, upload, draw or present | Platform/graphics caller of `run()` | Platform dispatcher for requests from other owners. | +| Change authoritative game state or prepare a frame | Simulation; the calling thread in sequential mode or one dedicated thread in concurrent mode | Copied ticks, simulation dispatcher and completed result adoption. | +| Decode files, shape text or perform independent CPU preparation | Worker pool | A group submission with owned inputs and a future result. | +| Advance audio playback | Audio implementation's output clock | Synchronized audio controls; close it separately from the runtime adapters. | + +The dedicated simulation thread is separate from general worker capacity. A CPU +worker has no graphics affinity. Toolkit widgets and documents belong to their +simulation/UI owner; native callbacks collect observations instead of calling them. +The [Engine Systems overview](../engine-systems/index.md#execution) maps these +owners to their systems. + +## Choose how work is delivered + +Choose the required delivery behavior before choosing an executor: + +```mermaid +flowchart TB + accTitle: Choosing a work transfer + accDescr: Owner-affine work uses dispatch, independent CPU work uses workers, occurrences use events, and current-state publication uses immutable snapshots. + need{"What must cross the boundary?"} -->|operation on a particular owner| dispatch["Owner dispatcher"] + need -->|independent CPU preparation| worker["Worker-group job"] + need -->|an occurrence for subscribers| event["Event channel"] + need -->|the latest complete value| snapshot["Immutable snapshot"] +``` + +| Need | Use | Completion and ordering | +| --- | --- | --- | +| Perform an operation on its current owner | A direct call | It completes or throws before returning; the caller supplies valid access. | +| Reach the platform or simulation owner | A dispatcher submission | Always queued, with a future; one detached FIFO batch drains at an owner boundary. | +| Use independent CPU capacity | A worker-group job | A future reports its outcome; parallel jobs can finish out of submission order. | +| Notify registered consumers | A typed event channel | Immediate listeners run on the producer; queued listeners select delivery and an independent error sink. | +| Publish only the latest value | A synchronized snapshot or immutable generation | Readers retain a stable version; intermediate versions can be superseded. | + +Use an event when subscribers should react to an occurrence. Use a latest-value +snapshot when a consumer needs current state rather than every occurrence. An +empty queue or a diagnostic count does not prove that detached work has finished. + +## Prepare and publish a replacement + +Asset loading, text updates and UI publication share this sequence: + +```mermaid +sequenceDiagram + accTitle: Prepare and adopt a replacement + accDescr: Simulation keeps the current version while a worker prepares owned data and platform uploads it. A later simulation update adopts the ready candidate and publishes a retained frame. + participant Simulation + participant Worker + participant Platform + Simulation->>Worker: Submit owned replacement request + Worker->>Platform: Queue prepared CPU data for upload + Platform-->>Simulation: Complete candidate future + Simulation->>Simulation: Adopt ready candidate during update + Simulation->>Platform: Publish frame retaining selected resources + Platform->>Platform: Render and recycle frame +``` + +A worker-to-platform request carries CPU data and retained inputs, not a pointer +to mutable game state. The platform callback returns a candidate instead of +modifying simulation through a captured `this`. Simulation checks readiness before +calling `get()` and handles any future error according to the game's failure policy. +Bound outstanding replacements to avoid an ever-growing queue of obsolete work. + +For a worked implementation of this sequence, use +[retained text replacement](../getting-started/how-to/update-text.md). Its +`CE::Text::Rendering::Updater` owns preparation, upload and adoption; the game +provides new content and decides how to report or retry a failure. Old frames +retain their matching text geometry and atlas while a newer generation is adopted. For +[asset batches](../assets/asset-loading.md#publication-and-retry), upload can publish +entries incrementally; a failed batch does not roll back earlier publications. +Choose the application's adoption boundary separately from the cache's behavior. + +## Keep readers independent of live producers + +Input delivers immutable observations; the tick's borrowed views last for that +update. Copy owned data for longer use. Frame preparation copies camera matrices, +transforms and selected animation cells so rendering needs no live entity or cursor. +GUI recordings own pixels and triangles so upload needs no live widget tree. + +Immutable snapshots and resource generations solve the same lifetime problem: +consumers keep the version they were given while the producer prepares another. +They do not synchronize mutable objects that remain outside the snapshot. + +## Preserve progress during waits and shutdown + +Never block an owner on a future that requires that owner to complete it. A worker +can also strand its children by waiting on jobs in the same saturated pool. +Simulation dispatch waits for the next scheduled update; posting does not invent +an early tick. Worker FIFO selection alone does not guarantee callback completion +order; queued event delivery supplies a serial stream where that is required. + +During shutdown, stop producers while their targets remain alive and let accepted +dependencies settle before releasing them. Runtime continues pumping platform +dependencies while joining simulation and draining its tracked groups. Follow +[Ownership and Cleanup](ownership-cleanup.md#shut-down-in-dependency-order) for +application-owned services. The detailed [dispatch](../runtime/thread-dispatch.md), +[worker](../runtime/worker-execution.md) and [event](../runtime/event-delivery.md) +guides define cancellation, closure and delivery guarantees. diff --git a/docs/site/assets/asset-loading.md b/docs/site/assets/asset-loading.md new file mode 100644 index 00000000..6a7d9508 --- /dev/null +++ b/docs/site/assets/asset-loading.md @@ -0,0 +1,418 @@ +# Asset loading + +`CE::Assets::Manifests::Reader` parses graphics manifests 2.0 into CPU-only +sprite/tileset definitions and shader/material recipes. An owned +`CE::Assets::Loading::Loader` keeps one immutable root: +preparation selects indexed documents, decodes owned RGBA pixels and snapshots +shader source bytes without a provider; `upload()` constructs resources and +publishes retained metadata on the provider's loading thread. + +```mermaid +flowchart TB + accTitle: Asset preparation and publication + accDescr: CPU preparation reads selected source files into an owned batch. Upload consumes that batch on the provider owner and publishes cache entries before the final metadata snapshot. + prepare["CPU preparation"] --> batch["Owned batch"] + batch --> upload["Provider-owner upload"] + upload -->|per entry| caches["Asset caches"] + upload -->|batch succeeds| metadata["Submitted-definition snapshot"] +``` + +Preparation and upload have different failure boundaries: preparation publishes +no resources, while an upload can leave earlier cache entries published. The +last successful metadata snapshot remains available until another batch succeeds. + +## Runtime entry point + +Start with the self-contained [First Game asset root](../getting-started/first-game.md#init-load-assets-and-create-the-actor). +Here `asset_root` names your copy of that directory and `engine` is the initialized +runtime context. Include ``, +`` and ``. +During platform-owned game initialization, load the root with its provider: + +```cpp +CE::Assets::Loading::Loader loader(asset_root); +loader.publish_events_to(engine.events().publisher()); +loader.load_assets(engine.resources()); +auto actor = CE::Assets::Sprites::Manager::get().get_asset("first-game", "actor"); +if (!actor) + throw std::runtime_error("The actor sprite is required"); +auto documents = loader.manifests(); // shared_ptr> +``` + +The expected key is `first-game:actor`; the handle retains the image, cell geometry +and compatible default material. Continue with [sprite submission](../getting-started/first-game.md#prepare-a-frame-publish-the-actors-current-appearance) +to display it. Optional repository artwork needs the separate +[package installation](catalog.md) and [focused index selection](catalog.md#load-only-installed-packages) +described in the catalog; loading the entire repository root can select absent images. + +Each asset family owns its definitions, retained values and cache API. Sprite and +tileset managers expose `load()` for definitions; the image manager exposes +`load()` for paths and `upload()` for decoded pixels. Their inherited `get_asset()` +and `clear_assets()` retain the shared cache contract. The loader's +`load_assets()` remains the complete-root preparation/upload convenience path. +The [value guide](asset-values-and-playback.md#api-domains) maps the family names. + +The example routes optional [upload settlement notifications](#upload-settlement-notifications) +to the Context bus. The First Game, tutorial and demo loaders use this setup. +Standalone loaders select their own route, or leave notification routing empty. + +Each preparation scans again. Only exact `graphics-manifests.json` indexes select +documents; schemas and unlisted JSON are not parsed. Index, texture and shader +references resolve from the enclosing `graphics/` directory, and asset keys use +`namespace:name`. `prepare()` selects every discovered index and decodes referenced +images plus standalone PNGs below the root, deduplicated in sorted order. +`prepare_graphics(indexes)` selects explicit indexes relative to the Loader root +(or supplied absolute paths), decoding only their referenced images. A shader-only +selection decodes no images. Cross-document +IDs and every grid's bounds are checked against those exact decoded dimensions +before any upload. Upload consumes the owned pixels without reopening image files. +Pixels are four-channel RGBA in the decoder's default top-to-bottom row order. +OpenGL reverses those rows in transient upload storage to match the atlas +geometry's upward-positive UV coordinates; the supplied pixels remain unchanged. +The separate stb alpha-atlas path preserves its baked row/UV convention. + +Shader sources resolve through exact registered paths, including when basenames +repeat. Preparation owns the bytes, so upload does not reopen shader files either. +Program/material kinds, references and asset selections are checked within the +selected batch; an unrelated cached material cannot satisfy a missing selection. +`register_files()` performs discovery/publication of unmanaged paths only, without +opening JSON, decoding images or requiring a provider. + +Preparation can use a tracked context worker group. Own both the loader and its +`CE::Assets::Loading::Prepared` value through the platform handoff; simulation only retrieves ready +futures, and never blocks on CPU preparation or GPU upload: + +```cpp +auto workers = engine.make_worker_group(); +auto platform = engine.platform_dispatcher().submission(); +auto loader = std::make_shared("assets"); +loader->publish_events_to(engine.events().publisher()); +auto preparing = workers.submit([loader, platform] { + auto prepared = loader->prepare(); + return platform.submit( + [loader, prepared = std::move(prepared)](CE::Runtime::Context& owner) mutable { + loader->upload(std::move(prepared), owner.resources()); + return loader->manifests(); + } + ); +}); +// A later update: only after preparing.wait_for(0s) reports ready, get the +// returned upload future. Check that future's readiness before reading metadata. +``` + +Retain the preparation future as game state. Also keep the upload future and the +last accepted metadata snapshot between updates; include `` and ``: + +```cpp +using Metadata = decltype(loader->manifests()); +std::future uploading; +Metadata published_definitions = loader->manifests(); +``` + +During later `update()` calls: + +```cpp +if (preparing.valid() && preparing.wait_for(std::chrono::seconds{0}) == std::future_status::ready) + uploading = preparing.get(); +if (uploading.valid() && uploading.wait_for(std::chrono::seconds{0}) == std::future_status::ready) + published_definitions = uploading.get(); +``` + +The first `get()` transfers the platform-upload future; the second adopts the +successful metadata snapshot. Exceptions reach the update's selected failure +policy and leave the prior snapshot unchanged. Retrieve the required new asset +handles before making application state depend on that content. Ordinary loading +preserves existing keys; this sequence does not turn it into image/sprite hot reload. +Start another batch only after both futures are invalid, keeping one batch pending. + +An owned root or injected shared root supplies physical capacity; this group +belongs to the context shutdown domain. Accepted CPU jobs finish while the +platform dispatcher remains available. Any final unexecuted upload is cancelled +before resource teardown, so its future reports failure instead of hanging. +Use platform submission endpoints rather than borrowed dispatcher pointers. + +[Threads and Data Flow](../architecture/threads-data-flow.md#prepare-and-publish-a-replacement) +explains this handoff across systems. Keep CPU inputs and upload dependencies +alive through completion or cancellation instead of blocking their execution owner. + +## Publication and retry + +`CE::Assets::Loading::Prepared` owns decoded pixels, shader bytes and definitions, with no provider or native +handles. Preparation failure changes no caches or published metadata. Upload +consumes the supplied value on the provider's loading owner; moving it into a +request transfers that ownership. Retry needs a fresh preparation or a preserved +copy. Pending platform requests can cancel before execution under the +[dispatcher contract](../runtime/thread-dispatch.md); executing uploads have no +rollback or mid-batch cancellation contract. + +Each created cache entry can become visible before the next entry is created. +Upload failure preserves completed entries and the last successful metadata +snapshot. Allocation of that final snapshot can fail even after all entries exist. +Retry skips existing keys rather than replacing them, so published manifests +describe submitted definitions rather than an atomic view of current caches. +Changed files under the same keys do not provide hot reload. Readers retain old +metadata snapshots after another upload or loader destruction. + +Upload checks the optional provider-owned `shader_asset_builder()` capability, +then publishes programs/materials before images and sprite/tileset resources. +Providers without that capability reject selected shader recipes explicitly. +`CE::Assets::Shaders::Manager` retains each recipe together with its executable generation; +material construction uses the retained program generation even when an ordinary +load preserves an existing program. Asset selections retain their material handle. + +`upload(prepared, provider, true)` explicitly replaces shader generations only. +Each candidate is complete before publication; failure preserves that key's old +generation, while earlier successful replacements remain published. Existing +assets and frames keep their old handles rather than automatically rebinding. +`shader_manifests()` and `manifests()` describe the last successful submitted +definitions, not an atomic cache snapshot. `diagnostics()` reports the last attempt, +including partial publications/replacements. There is no watch-based reload or +whole-batch rollback. + +`load_assets(provider)` is the synchronous prepare/upload convenience path. +Construct separate loaders for separate roots. Legacy `Loader::get(root)` remains +available but rejects a different root after its first initialization; `get()` only +retrieves an already-initialized singleton. + +## Catalogue changes + +Use a manager's `catalogue()` source when a resource browser, dependency cache or +reload coordinator needs current keys and later committed changes. It supplies +CPU-only snapshots and optional seeded watches. Each catalogue has a checked +identity and revision; a key's generation is source-unique and increasing, so +generation numbers for one key can have gaps. + +The channel is typed by both asset namespace and exact key type: +`CE::Assets::Cache::asset_catalogue_changed_event` carries +`AssetCatalogueChanged`. Image/program/material caches use path +keys; indexed shader, sprite and tileset caches use their string identities. +Neither all managers nor all keys are flattened into one string namespace. + +| Change | Committed meaning | +| --- | --- | +| `Published` | An absent exact key acquired a complete generation. | +| `Replaced` | An existing key acquired a fresh complete generation; `previous` identifies the old one. | +| `Reset` | Explicit `clear_assets()` cleared the catalogue, including an already empty one. No key/current generation is supplied. | + +Cache hits are silent. Changes stage only after cache and derived metadata commit, +then notify outside cache locks after retired asset owners release. Each payload +owns its key and previous/current generation metadata, without owning the asset. +Listener failure cannot turn a successful reload into a claim that the old entry +was preserved. Earlier entries can still commit before a later batch failure; +there is no multi-key transaction or automatic relinking of old frames/materials. + +Runtime routes its resource provider to `engine.events()` before game loading. +For standalone use, call `provider.publish_events_to(bus.publisher())` before +manager loading. A retained catalogue source can explicitly override that route +with `source.publish_events_to(...)`. Already staged records keep their original +destination. Without an active route or local watch, commits maintain generation +metadata without preparing observation payloads. + +For a platform-owned loading tool, include +``. With the initialized +`engine` and `loader` from the entry point above: + +```cpp +auto& images = CE::Assets::Images::Manager::get(); +auto source = images.catalogue(); +std::uint64_t observed_revision = 0; +auto watch = source.watch( + [&observed_revision](const CE::Assets::Cache::CatalogueObservation< + CE::Rendering::Resources::Image, std::filesystem::path>& observation) { + observed_revision = observation.seed ? observation.seed->revision : observation.change->revision; + } +); +loader.load_assets(engine.resources()); +auto current = source.snapshot(); +// Inspect current.entries/revision; reconcile this snapshot if an observation was lost. +watch.reset(); // Before the borrowed counter goes away. +``` + +The seed contains current exact keys and generations before `watch()` returns; +delayed changes at or below its revision are filtered. Later callbacks run on the +source drainer, possibly another producer thread, serialized for that watch. +The move-only `Watch` detaches on reset/destruction and acknowledges an entered +callback, with safe self-reset. Seed failure propagates and removes the watch; +later observer failure is isolated. Use a bus listener with +[simulation delivery](../runtime/event-delivery.md#optional-queued-delivery) +when updating simulation-owned gameplay or UI from platform commits. + +`manager.asset_snapshot(exact_key)` selects a strong asset handle, optional +generation metadata and catalogue revision under the same lock. Missing keys +have no handle/generation. This is exact-key lookup rather than a manager's +convenience alias policy. Old selections retain old handles. A generation's +separate `graphics_domain` token describes its native provenance; an empty token +explicitly means unsupported/unbound provenance. Catalogue revision does not +authorize native use. See [graphics domains](../resources/resource-residency.md#graphics-domain-observation). + +### Bounded delivery and teardown + +Commits automatically start an ordered, nonrecursive drain. It retains at most +64 observations and attempts at most 64 per call. Reentrant commits can join the +current drain within that budget; call `source.publish_notifications()` or make +a later commit to service residual work. Overflow drops the oldest observation. +Payload-preparation failure also leaves the cache commit intact. Snapshots and +attempted events expose cumulative loss and first/last lost revisions; the range +can contain delivered revisions. A bus failure gets no automatic retry. + +Snapshots reconcile current keys/generations, not retired history. Store +destruction closes its retained source without a Reset callback. Provider +destruction silently clears its global caches and advances their revisions; +those manager sources can later serve another provider. Actual graphics-domain +invalidation uses the runtime's cache-eviction consumer and emits explicit Reset +observations. Destructors never invoke catalogue listeners. + +This per-key family is separate from the aggregate upload result below. A batch +of 10,000 assets still has one `AssetUploadSettled`; configured catalogue observers +can additionally receive its real new/replaced key commits. + +## Upload settlement notifications + +Subscribe to `CE::Assets::Loading::asset_upload_settled_event` to observe an +executed upload attempt. Its `CE::Assets::Loading::AssetUploadSettled` payload is +one owned aggregate: uploading a batch of 10,000 assets has one attempt outcome. +Use the existing loader header and `` for this platform-owned example, +with an initialized `engine` Context and the `asset_root` above: + +```cpp +CE::Assets::Loading::Loader loader(asset_root); +loader.publish_events_to(engine.events().publisher()); +std::optional latest_upload; +const auto upload_listener = engine.events().register_listener( + CE::Assets::Loading::asset_upload_settled_event, + [source = loader.source_id(), &latest_upload](const CE::Assets::Loading::AssetUploadSettled& event) { + if (event.source == source) + latest_upload = event; + } +); +try { + loader.load_assets(engine.resources()); +} catch (...) { + engine.events().unregister_and_wait(upload_listener); + throw; +} +engine.events().unregister_and_wait(upload_listener); +``` + +The source filter selects this loader when others share the bus. After successful +loading, `latest_upload` records `UploadOutcome::Succeeded` and its counts. +Both paths remove the listener before the borrowed variable goes away. Retain a +registration for longer-lived monitoring and remove it before its target dies. +Register before the attempts of interest; upload events have no replay. + +```mermaid +sequenceDiagram + accTitle: One notification follows an executed upload attempt + accDescr: Loader performs one upload attempt and finishes its counts. It notifies the selected bus once, then returns or rethrows the original upload failure. Listeners run immediately by default. + participant Caller + participant Loader + participant Bus as Selected EventBus + participant Listener + Caller->>Loader: upload(prepared, provider) + Loader->>Loader: Validate and attempt registry / cache publication + Loader->>Loader: Finish counts and retain outcome + Loader->>Bus: notify(channel, outcome) + Bus->>Listener: callback(outcome) + Listener-->>Bus: return + Bus-->>Loader: Notification attempts return + Loader-->>Caller: Return or original upload exception +``` + +Notification follows completion bookkeeping, outside loader/cache locks, on the +loading owner. Direct return, the original exception, or a platform request's +future remains authoritative. The enclosing request future may still be unready +inside this callback; inspect it later from update rather than waiting here. +For simulation-owned game/UI state, choose +[simulation delivery](../runtime/event-delivery.md#optional-queued-delivery) and +keep that target alive through listener removal. + +### Attempt identity and failed effects + +`source` is the loader's unique nonzero identity. `attempt` is a distinct nonzero +execution identity for every admitted `upload()` call; retrying a preserved +`Prepared` batch creates another attempt while keeping `counts.batch` as its +diagnostic correlation. `counts.provider` identifies the selected provider. +`observed_at` is the loading owner's steady-clock completion observation. +The payload owns its counts and original `std::exception_ptr failure`, which is +empty on success and can be rethrown to inspect a failed attempt. + +`UploadOutcome` describes the available knowledge of effects: + +| Outcome | Meaning | +| --- | --- | +| `Succeeded` | Upload and retained metadata publication completed. | +| `FailedBeforeEffects` | Failure preceded every operation that could publish registry/cache effects. | +| `Partial` | Valid publication/replacement counters prove that at least one cache effect committed before failure. | +| `EffectsUnknown` | A mutating operation was entered, but the observations cannot establish all committed effects. | + +`counts.images_completed` includes cache hits. `counts.publications` and +`counts.replacements` cover typed caches only, and their deltas require +`counts.publication_count_available`. File registration can change before an +image fails while these counts remain zero; that failure is `EffectsUnknown`. +None of the failed outcomes promises rollback or an unchanged file registry. +The [publication and retry contract](#publication-and-retry) still applies. + +Preparation, validation in `load_assets()` before it reaches `upload()`, and +requests cancelled before execution emit no settlement event. Rejected attempts +that cannot start also emit none. Observer failures use isolated `notify()` +reporting and do not turn successful uploads into failures or replace an original +upload exception. + +### Routing and reentrant publication + +Configure `publish_events_to()` on the loading owner while the loader is idle +and its pending outcomes are drained. Its +[weak publisher](../runtime/event-delivery.md#weak-publishing-handles) allows the +loader to outlive the bus; an empty or expired route discards observations. + +`upload()` automatically attempts `publish_pending_events()` after settling. +Each drain selects the outcomes pending at entry. If a listener performs another +upload on this owner, that new outcome waits for the next upload or explicit +drain, preserving fanout order without recursive notification. At most 64 +outcomes may be pending. Staging/overflow failures are independently reported +and preserve the direct upload result. Drain any wanted pending observations +before destroying the loader; destruction never invokes listeners. + +## Resources and application bootstrap + +The generic loader loads indexed shaders/materials, images, sprites and tilesets. +Fonts use the separate [configured font service](text-layout.md#configure-fonts-once), +whose default is embedded when the application supplies no configuration. The +[file/font discovery guide](file-and-font-discovery.md) defines candidate selection, +index freshness and the legacy font cache's default/collection-face policy. The demo selects +`graphics/shaders/graphics-manifests.json` and retrieves `main:text` and `main:images` +from `CE::Assets::Shaders::Manager::find_material()`, including with `--full-assets`. F5 prepares that same index +and requests explicit replacement. The demo adopts its pair only after both +replacements succeed; failed reload preserves its previous application pair even +if the shared catalogue has published an earlier candidate. Fonts remain outside +the graphics manifest contract. Manual material builders remain supported; the +[rendering guide](../rendering/pipelines-and-materials.md#manual-material-construction) +shows direct typed construction. TGUI and RmlUi use +[automatic provider-owned defaults](../rendering/pipelines-and-materials.md#default-ui-materials) +and accept compatible material handles from indexed recipes as optional overrides. + +`CE::Rendering::Resources::Provider::create_image()` accepts +`CE::Assets::Images::Decoded` RGBA pixels. +`upload_geometry(span, topology)` copies the transient view before +returning. The older shared-pointer/count overload keeps its CPU owner only through +that upload. Atlas cells use independent four-vertex strips; images and glyphs use +six-vertex triangle quads. Backend handles never retain these CPU buffers. OpenGL +checks dimension, byte-count, vertex-count, and draw-range limits before use. + +## Typed manifest data and consumers + +Engine definition types retain grid origins, frame sizes, spacing, pivots, views, +orientations, timed clips and facings, animated tile targets, Wang terrain metadata, +weighted variants, and neighbor-mask lookup tables. +`CE::Assets::Sprites::Asset::definition()` and +`CE::Assets::Tilesets::Asset::definition()` expose them after construction. Entity-owned +`CE::Assets::Sprites::Animation` already advances timed clips and publishes resolved cells; the +cached sprite has no mutable playback cursor. Tile-map neighbor selection and +application meanings for views/orientations remain gameplay work. +The [asset value and playback contract](asset-values-and-playback.md) defines direct +construction limits, borrowed metadata, lookup failure and submission ownership. + +Use `CE::Assets::Manifests::Reader::load(file)` or `parse(stream, source)` for document-only tools. +Use the owning focused runners or `all-tests` for regressions; +[architecture validation](../development/architecture-validation.md) describes +selection and coverage limits. diff --git a/docs/assets/asset-manifests.md b/docs/site/assets/asset-manifests.md similarity index 68% rename from docs/assets/asset-manifests.md rename to docs/site/assets/asset-manifests.md index 2f9aa732..f2ea17d6 100644 --- a/docs/assets/asset-manifests.md +++ b/docs/site/assets/asset-manifests.md @@ -1,12 +1,80 @@ # Graphics manifests 2.0 Graphics indexes and definition documents use version `2.0`. The authoritative -schemas are [graphics indexes](../../assets/schemas/graphics-manifest-index-2.0.schema.json), -[sprites/tilesets](../../assets/schemas/graphics/sprite-tileset-manifest-2.0.schema.json) -and [shaders/materials](../../assets/schemas/graphics/shader-manifest-2.0.schema.json). +schemas are [graphics indexes](../../../assets/schemas/graphics-manifest-index-2.0.schema.json), +[sprites/tilesets](../../../assets/schemas/graphics/sprite-tileset-manifest-2.0.schema.json) +and [shaders/materials](../../../assets/schemas/graphics/shader-manifest-2.0.schema.json). Earlier versions are rejected rather than implicitly upgraded. `$schema` is an editor hint; the runtime neither fetches it nor discovers schemas as assets. +## Add a sprite sheet + +Start with the maintained [First Game asset root](../../../projects/apps/first-game/assets). +Copy that directory into your application's assets so its image and sprite +definition stay together. Ordinary drawing uses a provider-supplied material; +its relevant layout is: + +```text +assets/ + graphics/ + graphics-manifests.json + definitions/sprites.json + textures/actor.png +``` + +The complete index selects the sprite definition: + +```{literalinclude} ../../../projects/apps/first-game/assets/graphics/graphics-manifests.json +:language: json +``` + +The complete sprite document describes the actor's two 16-by-16 cells: + +```{literalinclude} ../../../projects/apps/first-game/assets/graphics/definitions/sprites.json +:language: json +``` + +Both `definitions/sprites.json` and `textures/actor.png` resolve from `graphics/`, +even though the definition itself is in `graphics/definitions/`. The namespace +and sprite map key combine into `first-game:actor`: + +```mermaid +flowchart LR + accTitle: Sprite manifest references and identity + accDescr: The graphics index selects a sprite document. That document references an image relative to graphics and defines the qualified identity first-game:actor. + index["graphics-manifests.json"] -->|selects| document["definitions/sprites.json"] + document -->|texture path| image["textures/actor.png"] + document -->|namespace and name| identity["first-game:actor"] +``` + +During platform-owned game initialization, after the provider is ready: + +```cpp +CE::Assets::Loading::Loader loader(asset_root); +loader.load_assets(engine.resources()); +auto actor = CE::Assets::Sprites::Manager::get().get_asset("first-game", "actor"); +if (!actor) + throw std::runtime_error("The actor definition was not loaded"); +``` + +Include the loader and sprite-manager headers plus ``; `asset_root` +is the copied assets directory and `engine` is the initialized runtime context. +The [First Game sprite walkthrough](../getting-started/first-game.md#put-a-sprite-on-screen) supplies frame +submission using the asset's retained material. Its +[whole-image alternative](../getting-started/how-to/draw-sprite.md#draw-a-whole-image) +needs no sprite manifest. Cell zero selects the left frame and cell one +the right. The image must contain both cells; preparation rejects a grid whose +occupied bounds exceed its decoded image. + +To add your own sheet, copy a definition entry, choose a unique namespace/name, +and change its texture path, frame size, rows, columns, spacing and pivot. Add the +document to the index. If retrieval returns null, check the qualified identity and +index selection; unlisted JSON is not loaded. This asset root needs no shader +documents or shader source files: its sprite and uploaded text retain the +[provider's default materials](../rendering/pipelines-and-materials.md#default-asset-materials). +Add shader/material documents only when selecting custom recipes. The field +contracts below cover larger atlases, clips, autotiles and custom shaders. + ## Index selection and paths Only the exact basename `graphics-manifests.json` selects definition documents. @@ -23,8 +91,8 @@ registered names; absolute paths, parent traversal, backslashes and colons rejec The checked-in layout separates `graphics/definitions/`, `graphics/textures/`, `graphics/shaders/` and `graphics/ui/`. The main -[graphics index](../../assets/graphics/graphics-manifests.json) selects sprite/tileset -definitions; the [shader index](../../assets/graphics/shaders/graphics-manifests.json) +[graphics index](../../../assets/graphics/graphics-manifests.json) selects sprite/tileset +definitions; the [shader index](../../../assets/graphics/shaders/graphics-manifests.json) selects only the main demo recipes. The [asset package catalog](catalog.md) lists download sources and image placement @@ -37,8 +105,9 @@ for the packages referenced by the checked-in manifests. - An entry's `pivot` overrides `defaults.sprite.pivot` or `defaults.tileset.pivot` according to the containing map. - Sprite/tileset documents declare `asset_class: "sprite-tileset"`. An entry's optional `shader` overrides the document-level selection. It is a qualified - material ID, independent of the texture path; omitted selections use a supplied - draw-style material. An explicit draw-style material overrides an asset selection. + material ID, independent of the texture path. When omitted, sprite/tileset loading + obtains and retains the provider's compatible default material. An explicit + draw-style material overrides either that default or a manifest selection. - Shader and material are one asset class, declared as `asset_class: "shader"`. Their program and material names share qualified identity validation and cannot collide with another selected program/material identity. @@ -148,7 +217,7 @@ A shader document contains named `programs` and `materials`. Each program owns `stages.vertex` and `stages.fragment` source references. Each material names its qualified `program`, `vertex_layout`, `topology`, typed `parameters` and backend `bindings`; optional fields are `state`, literal `defaults` and FX `sampling`. -See [main.json](../../assets/graphics/shaders/main.json) for the complete text/image +See [main.json](../../../assets/graphics/shaders/main.json) for the complete text/image example: both materials share one program but use different draw topologies. Definitions contain no texture paths or default images. Textures contain no shader @@ -171,14 +240,18 @@ The [loading guide](asset-loading.md) defines preparation, publication and expli replacement. [Pipelines and materials](../rendering/pipelines-and-materials.md) defines manual builders, sampling and retained generation behavior. Only the main text/image materials are indexed; the other shaders remain manual, unverified -examples. TGUI and RmlUi still construct their materials explicitly. +examples. TGUI and RmlUi select +[provider-owned default materials](../rendering/pipelines-and-materials.md#default-ui-materials) +automatically; custom UI materials can use the same indexed recipes described here. +The [custom sprite material example](../rendering/pipelines-and-materials.md#give-one-object-a-custom-material) +supplies a complete ordinary-object recipe, shader sources and per-draw parameters. ## Recovered college definitions -[Invaders](../../assets/graphics/definitions/misc/invaders.json), -[HyperMaze](../../assets/graphics/definitions/misc/hypermaze.json), -[Rover](../../assets/graphics/definitions/misc/rover.json) and -[Tileset](../../assets/graphics/definitions/tilesets/tileset.json) preserve the legacy +[Invaders](../../../assets/graphics/definitions/misc/invaders.json), +[HyperMaze](../../../assets/graphics/definitions/misc/hypermaze.json), +[Rover](../../../assets/graphics/definitions/misc/rover.json) and +[Tileset](../../../assets/graphics/definitions/tilesets/tileset.json) preserve the legacy source ordering and pixel rectangles. Invaders has eleven sections containing nineteen frames; Rover uses four separate entries because its frame sizes differ. HyperMaze has five columns and six rows; Tileset has four cells. All are indexed. diff --git a/docs/assets/asset-values-and-playback.md b/docs/site/assets/asset-values-and-playback.md similarity index 73% rename from docs/assets/asset-values-and-playback.md rename to docs/site/assets/asset-values-and-playback.md index 517564dc..e1430968 100644 --- a/docs/assets/asset-values-and-playback.md +++ b/docs/site/assets/asset-values-and-playback.md @@ -21,7 +21,7 @@ Asset families use contextual names: qualify `Asset`, `Data`, `Definition` and | `CE::Assets::Tilesets` | `Definition` and `Clip` own metadata; `Asset`, `Tile` and `Animation` retain resources; `Selection` owns CPU rule sampling; `Manager` caches tilesets. | | `CE::Assets::Grids` | `Definition`, `View` and `CellIndex` describe addressing; `GeometryData` and `make_geometry()` provide CPU upload storage. | | `CE::Assets::Animation` | `TimedFrame` is shared clip metadata; `Cursor` supplies ordinal wrap/clamp playback. | -| `CE::Assets::Graphics` | `Asset` retains a whole-image quad and its image. | +| `CE::Assets::Graphics` | `Asset` retains a whole-image quad, its image and selected material. | Pixel coordinates, vertex shapes and the uploaded `Buffer2D` contract belong to `CE::Rendering::Geometry`. Image/program/material interfaces belong to @@ -53,9 +53,13 @@ texture coordinates. Pivot formulas and named views are defined by the ## Resources and caches `CE::Assets::Content2D` retains image, geometry and an optional selected material generation. -Submission uses an explicit draw-style material first, then the asset selection; -it rejects when neither supplies a usable material. Tile and animation handles -retain the same selection. Direct construction permits null +Sprite/tileset loading supplies a provider default when the definition names no +material; whole-image graphic creation does the same unless given an explicit +handle. These [ordinary defaults](../rendering/pipelines-and-materials.md#default-asset-materials) +match the existing triangle/strip geometry and need no application shaders. +Submission uses an explicit draw-style material first, then the retained asset +selection; it rejects when neither supplies a usable material. Tile and animation +handles retain the same selection. Direct construction permits null handles and does not prove matching layout, grid bounds or backend domains. Loader construction supplies validated definitions and uploaded resources; CPU submission validates its selected geometry/material/range. Read-only resource metadata can be @@ -65,9 +69,11 @@ or backend teardown, but cannot perform native operations through a closed domai See [resource ownership and retirement](../resources/resource-residency.md). `CE::Assets::Graphics::Asset::from_image()` rejects a null or zero-sized image, then uploads a six-vertex -whole-image quad on the provider owner. The caller must use a compatible image and +whole-image quad and retains its selected material on the provider owner. The caller must use a compatible image and provider domain. `load()` first decodes/uploads the image. Failure returns no graphic asset; these helpers do not publish a cache entry. +The [whole-image sequence](../getting-started/how-to/draw-sprite.md#draw-a-whole-image) +shows loading a background PNG, positioning it, submitting it and releasing it. Sprite and tileset managers use `namespace:name` IDs, require referenced images to be loaded, and retain existing IDs. `CE::Assets::Images::Manager` uses lexically normalized paths; @@ -80,6 +86,24 @@ strong handles under a lock; those handles survive clear/replacement independent ## Sprite playback +Playback cursors share immutable CPU clip data while retaining independent time +and frame selection. The sprite separately retains the uploaded appearance. + +```mermaid +flowchart LR + accTitle: Independent sprite playback over shared clip data + accDescr: A sprite and two animation cursors retain the same CPU definition allocation. Each cursor selects its own cell; the cursors do not retain the sprite's GPU handles. + sprite["Sprite asset"] -->|retains| clip["CPU clip data"] + first["Cursor A"] -->|retains| clip + second["Cursor B"] -->|retains| clip + first -->|selects| cell_a["Cell A"] + second -->|selects| cell_b["Cell B"] +``` + +For a first animated actor, [Play sprite animation](../getting-started/how-to/animate-sprite.md) +adds a timed clip to First Game, retains its cursor, advances it during update +and submits its selected cell. The rules below define lookup and playback limits. + `CE::Assets::Sprites::Asset` retains one immutable definition snapshot and indexes clips by name/facing. Construction checks a nonempty grid and nonempty clips with positive millisecond durations and in-range cells, rejecting duplicate clip keys. It does not revalidate @@ -135,6 +159,86 @@ after choosing the original target. ## Tile selection +### Sample an application map + +This CPU example maps a small game-owned terrain grid to a four-neighbor rule. +Rows increase southward and columns eastward; rendering placement is a separate +game choice. Terrain `0` is empty and `1` is the terrain being connected. Include +``, ``, ``, +`` and ``: + +```cpp +namespace Tiles = CE::Assets::Tilesets; +namespace Pick = CE::Assets::Tilesets::Selection; +using Cell = CE::Assets::Grids::CellIndex; + +const std::array, 3> map{{ + {{0, 1, 0}}, + {{1, 1, 0}}, + {{0, 0, 0}} +}}; +Tiles::BitmaskRule rule; +rule.type = Tiles::BitmaskType::FourNeighbor; +rule.bit_order = {Tiles::Direction::North, Tiles::Direction::East, + Tiles::Direction::South, Tiles::Direction::West}; +rule.cases = {{0, 0}, {9, 1}, {15, 2}}; + +const int column = 1; +const int row = 1; +const Pick::Sampler sampler = [&](Pick::Site site) -> Pick::Sample { + int x = column; + int y = row; + switch (site.direction) { + case Tiles::Direction::North: --y; break; + case Tiles::Direction::East: ++x; break; + case Tiles::Direction::South: ++y; break; + case Tiles::Direction::West: --x; break; + default: return {Pick::SampleState::Unknown, 0}; + } + if (x < 0 || x >= 3 || y < 0 || y >= 3) + return {Pick::SampleState::Outside, 0}; + return {Pick::SampleState::Known, map[y][x]}; +}; +Pick::Options options; +options.terrain = map[row][column]; +options.outside = Pick::Fallback::Empty; +const auto result = Pick::select(rule, sampler, options); +std::optional visible_cell; +if (const auto* cell = std::get_if(&result)) + visible_cell = *cell; +``` + +For the center tile, North and West connect: bits zero and three produce mask +`9`, selecting cell `1`. This example deliberately declares only three masks. +Selecting the top-middle tile produces mask `4`, absent from `cases`, and returns +`MissingRule`; `visible_cell` stays empty and the game skips that tile. Add the +remaining authored cases for a complete terrain set. If a required neighbor is +temporarily unavailable, return `Unknown` rather than `Outside`; the default +unknown policy produces `IncompleteNeighborhood` instead of inventing empty land. + +Keep this rule and map on their simulation owner. Save the selected cell in the +game's visible state; its sampler borrows the map only during selection. A matching +loaded tileset must have the selected cell ranges and artwork. For that retained +`tileset`, a nonnegative simulation time in milliseconds named `elapsed`, and the +`pass`, `style` and `context` from ordinary frame submission: + +```cpp +if (visible_cell) { + const auto cell = tileset.cell_at(*visible_cell, elapsed); + pass.add(CE::Rendering::Submission::resolve_tile(tileset, cell, style, context)); +} +``` + +`cell_at()` substitutes the selected target's animation once. Advance `elapsed` +during update, so skipped frame preparation does not pause tile animation. The +packet retains resources and copied draw values; it carries no map or sampler. +The [Rendering system](../engine-systems/rendering.md#integration) supplies the +pass/submission setup, and [Threads and Data Flow](../architecture/threads-data-flow.md) +explains publication to rendering. The contracts below cover Wang sites, +deterministic variants and direct-construction limits. + +### Selection contracts + The CPU-only `CE::Assets::Tilesets::Selection::select(rule, sampler, options)` overloads accept Wang, bitmask or variant definitions and return a `CE::Assets::Tilesets::Selection::Result`: either a `CE::Assets::Grids::CellIndex` or a `CE::Assets::Tilesets::Selection::Failure`. `MissingRule` means the derived signature/mask has no @@ -158,7 +262,7 @@ retained. Unused directions are not sampled. Read-only queries can run concurren when the supplied samplers and world support that access. `Known` samples carry an ID; zero means empty. `Outside` and `Unknown` samples ignore -their ID and have independent fallback policies: `Empty`, `CE::Rendering::Geometry::Center` or `Unresolved`. +their ID and have independent `Selection::Fallback` policies: `Empty`, `Center` or `Unresolved`. Defaults treat outside-world sites as empty and unknown sites as unresolved. A known Wang label absent from its terrain catalogue uses the unknown policy. A Wang center fallback requires `options.terrain` to be declared or zero. Bitmasks have no terrain @@ -238,16 +342,21 @@ callback effects intact. Alternate-bank layout is rejected. BitmapFont retains a The separate [Unicode text service](text-layout.md) shapes accented Latin/Cyrillic, resolves paragraph bidi and optional local-width wrapping, selects whole-grapheme fallback and prepares immutable glyph pages. Its `CE::Text::Rendering::Generation` overload submits -already uploaded placements and matching page handles; supply `context.image` for -page selection. Legacy Font implementations retain their existing APIs/metrics. +already uploaded placements and matching page handles, with a provider default +material retained during upload. AsciiFont loading also retains a default; +explicit upload/load material handles and per-draw materials remain available. +Legacy Font implementations retain their existing metrics. Deprecated BitmapFont +keeps its explicit material-selection path. `CE::Rendering::Submission::resolve_sprite`, `CE::Rendering::Submission::resolve_tile`, `CE::Rendering::Submission::resolve_graphic` and `CE::Rendering::Submission::resolve_text` perform CPU submission without binding resources or mutating playback. Keep their inputs stable for the call. They copy style/pass values and retain geometry, material and any -resolved image bindings. An `CE::Rendering::Submission::ImageParameter2D` inserts the asset image as a draw-layer -sampler using a public contract key and zero-based unit. An empty key, null image or -duplicate draw-layer key throws; omitting it leaves image selection to the other -parameter layers. +resolved image bindings. A default `Context2D` inserts the asset image as the +draw-layer `image` sampler at unit zero. Set `context.image` to an +`ImageParameter2D` for another public key/unit. An empty key, null image or duplicate +draw-layer key throws. Set it to `std::nullopt` to leave image selection to other +parameter layers for sprites, tiles, graphics and legacy fonts. Unicode page +submission requires an image key so each glyph/run selects its matching page. Sprite/tileset cell overloads check logical grid bounds; selected tile and tile-animation values instead rely on their resource range and clip indexing. diff --git a/docs/assets/catalog.md b/docs/site/assets/catalog.md similarity index 66% rename from docs/assets/catalog.md rename to docs/site/assets/catalog.md index d4fa8a4f..b5fdabbb 100644 --- a/docs/assets/catalog.md +++ b/docs/site/assets/catalog.md @@ -2,7 +2,7 @@ The `assets/` Git submodule contains graphics definitions, shaders, UI fixtures and recovered college assets. Initialize its pinned revision with the checkout's -[submodule setup](../../README.md#setup). This catalog lists optional external +[submodule setup](../../../README.md#setup). This catalog lists optional external artwork packages and their image placement; those package PNGs are supplied separately. Definitions live under `graphics/definitions/`, images under `graphics/textures/`. The [manifest guide](asset-manifests.md) owns index selection @@ -12,11 +12,11 @@ and graphics-relative paths. | Package and author page | Download | Tracked manifests | | --- | --- | --- | -| [Mini World Sprites — Shade, octoshrimpy](https://merchant-shade.itch.io/16x16-mini-world-sprites) | `MiniWorldSprites.zip` | [atlas.json](../../assets/graphics/definitions/MiniWorldSprites/atlas.json), [buildings-colored.json](../../assets/graphics/definitions/MiniWorldSprites/buildings-colored.json), [buildings-orc.json](../../assets/graphics/definitions/MiniWorldSprites/Buildings/Enemy/Orc/buildings-orc.json) | -| [MiniWorld Character Customizer — Shade](https://merchant-shade.itch.io/16x16-mini-world-sprites) | `Character-Customizer.zip` | [character-customizer.json](../../assets/graphics/definitions/char-customizer/character-customizer.json) | -| [Puny World — Shade](https://merchant-shade.itch.io/16x16-puny-world) | `punyworld-overworld-tileset.png` or `PUNY_WORLD_v1.zip` | [punyworld-overworld.json](../../assets/graphics/definitions/tilesets/punyworld-overworld.json) | -| [Mage City Arcanos — Hyptosis](https://opengameart.org/content/mage-city-arcanos) | `magecity.png` | [magecity.json](../../assets/graphics/definitions/tilesets/magecity.json) | -| [Dungeon tileset — Buch, with contributions from surt](https://opengameart.org/content/dungeon-tileset) | `dungeon_tiles.png` | [dungeon_tiles.json](../../assets/graphics/definitions/tilesets/dungeon_tiles.json) | +| [Mini World Sprites — Shade, octoshrimpy](https://merchant-shade.itch.io/16x16-mini-world-sprites) | `MiniWorldSprites.zip` | [atlas.json](../../../assets/graphics/definitions/MiniWorldSprites/atlas.json), [buildings-colored.json](../../../assets/graphics/definitions/MiniWorldSprites/buildings-colored.json), [buildings-orc.json](../../../assets/graphics/definitions/MiniWorldSprites/Buildings/Enemy/Orc/buildings-orc.json) | +| [MiniWorld Character Customizer — Shade](https://merchant-shade.itch.io/16x16-mini-world-sprites) | `Character-Customizer.zip` | [character-customizer.json](../../../assets/graphics/definitions/char-customizer/character-customizer.json) | +| [Puny World — Shade](https://merchant-shade.itch.io/16x16-puny-world) | `punyworld-overworld-tileset.png` or `PUNY_WORLD_v1.zip` | [punyworld-overworld.json](../../../assets/graphics/definitions/tilesets/punyworld-overworld.json) | +| [Mage City Arcanos — Hyptosis](https://opengameart.org/content/mage-city-arcanos) | `magecity.png` | [magecity.json](../../../assets/graphics/definitions/tilesets/magecity.json) | +| [Dungeon tileset — Buch, with contributions from surt](https://opengameart.org/content/dungeon-tileset) | `dungeon_tiles.png` | [dungeon_tiles.json](../../../assets/graphics/definitions/tilesets/dungeon_tiles.json) | The three MiniWorld sprite/building manifests share one archive. Character Customizer is a separate archive on the same page. That page also offers `MiniworldGuide.docx` @@ -72,6 +72,46 @@ is 368×384 pixels, with twenty-three columns and twenty-four rows of 16-pixel c This source PNG matches the existing sheet. The separate 0x72 packages below have their own layouts and future manifests. +## Load only installed packages + +The repository's full graphics index references artwork from several packages. +Checking out its manifests alone does not supply those PNGs. After placing one +package, select only its installed definitions instead of calling `load_assets()` +over that entire root. + +For example, after installing Mage City, create +`assets/graphics/selected/graphics-manifests.json`: + +```json +{ + "version": "2.0", + "manifests": ["definitions/tilesets/magecity.json"] +} +``` + +References still resolve from `assets/graphics/`, not the new `selected/` +directory. In platform-owned initialization, with `asset_root` naming `assets` +and `engine` the initialized runtime context: + +```cpp +CE::Assets::Loading::Loader loader(asset_root); +auto prepared = loader.prepare_graphics({"graphics/selected/graphics-manifests.json"}); +loader.upload(std::move(prepared), engine.resources()); +auto tileset = CE::Assets::Tilesets::Manager::get().get_asset("magecity:mage_city"); +if (!tileset) + throw std::runtime_error("The selected Mage City tileset is required"); +``` + +Include ``, +``, `` and ``. +The [loading guide](asset-loading.md#runtime-entry-point) owns the preparation and +upload contract. This selection decodes only images referenced by that index. +Preparation rejects absent images or grids that exceed their decoded dimensions; +check the listed path, filename case and matching artwork revision before retrying. +A nonnull handle confirms the selected identity was published. It is ready for +[tile submission](asset-values-and-playback.md#sample-an-application-map). +For an introductory sprite with included artwork, use [First Game](../getting-started/first-game.md). + ## Packages awaiting manifests These packages need separate manifests for their layouts. Keep original downloads @@ -86,7 +126,7 @@ downloads and provenance records are not included in Git. | [16x16 DungeonTileset II — 0x72](https://0x72.itch.io/dungeontileset-ii) | `dungeontileset-ii/` | `0x72_DungeonTilesetII_v1.7.zip` | Unpacking these packages and writing a manifest for each belongs to the -[long-term asset task](../planning/long-term/README.md#additional-dungeon-asset-manifests). +[long-term asset task](../../planning/long-term/README.md#additional-dungeon-asset-manifests). The extension and remix links on their author pages are separate candidate packages. ## Recovered college assets @@ -98,5 +138,5 @@ under `graphics/fonts/`. The legacy sprite/tile PNGs follow the same ignored loc image policy as external packages; checking out definitions does not supply them. These are recovered project assets, not newly sourced or licensed packages. Their original numeric definitions remain beside the JSON conversions. The submodule's -[provenance notice](../../assets/README.md) still applies where credits or sources +[provenance notice](../../../assets/README.md) still applies where credits or sources are unknown. Conversion alone does not establish native rendering acceptance. diff --git a/docs/assets/file-and-font-discovery.md b/docs/site/assets/file-and-font-discovery.md similarity index 63% rename from docs/assets/file-and-font-discovery.md rename to docs/site/assets/file-and-font-discovery.md index 345a60a8..75ba90dd 100644 --- a/docs/assets/file-and-font-discovery.md +++ b/docs/site/assets/file-and-font-discovery.md @@ -2,8 +2,9 @@ File indexing, font discovery and asset loading have separate policies. The [asset loader](asset-loading.md) constructs a fresh `CE::Files::FileIndex` for each -preparation. Applications choose fonts explicitly -before loading them through their resource provider. +preparation. Unicode font selection and text preparation run on the CPU; text +upload uses the resource provider separately. Legacy fonts retain their +provider-owned loading path. Automatic graphics preparation registers JSON, WAV and shader paths in a fresh private `CE::Assets::Loading::FileRegistry`; only exact `graphics-manifests.json` indexes select documents. @@ -14,7 +15,23 @@ enclosing `graphics/` directory for index, texture and source references. ## File index -[CE::Files::FileIndex](../../projects/engine/include/cheryl/core/resources/fileio/file-mgr.h) +To inspect application files without loading assets, include +`` and ``. With `asset_root` +naming the content directory: + +```cpp +CE::Files::FileIndex files(asset_root); +const auto& pngs = files.get_files_of_type(".png"); +for (const auto& path : pngs) + std::cout << path << '\n'; +``` + +This prints discovered paths; it neither decodes images nor uploads resources. +`pngs` borrows this index. Finish traversal before appending another directory, +then obtain the bucket again if needed. Construct a new `FileIndex` to observe +files added beneath an already scanned directory. + +[CE::Files::FileIndex](../../../projects/engine/include/cheryl/core/resources/fileio/file-mgr.h) is an independent, incremental index. Construction scans the supplied root; `search_directory()` appends an unvisited directory tree. Missing and non-directory roots add nothing. Directories encountered during a scan are marked visited, so @@ -44,9 +61,44 @@ index and visited-directory markers, including the root recorded before traversa reconstruct the index to retry that root. There is no atomic discovery snapshot or automatic recovery from changed files. +## Font names and application assets + +Games configure fonts once through `Context::fonts()` rather than repeating +discovery for text and each GUI adapter. The default is embedded when nothing is +configured. `FontConfiguration` registers stable application references and +retains available style variants; groups contain alternative references for +explicit selection. `FontService::configure(FontSelection)` also preserves an +existing ordered default/fallback policy. See +[catalogue configuration and saved choices](text-layout.md#configure-fonts-once). + +[`CE::Text::FontSelection::preferred`](text-layout.md#font-selection) accepts an +ordered list of names such as `{"Arial", "Noto Sans", "DejaVuSans.ttf"}`. +Family names come from font metadata. Filename aliases, including their extension, +also select application fonts, regardless of where they are nested under an asset +root. Both forms compare ASCII letters without case. Application discovery scans +`asset_directories` recursively, defaulting to `{"assets"}` relative to the working +directory. Games using a different asset root set that list once; preferred names +contain no directories. + +Names resolve against application fonts before system fonts, so a matching asset +font overrides an installed copy. The engine discovers system roots automatically; +system files match family names rather than filename aliases. Missing names and +unusable discovery candidates are skipped. `FontFile` remains available for direct +path/face selection and `SystemFontFamily` for an explicitly system-only choice. +The [text guide](text-layout.md#font-selection) defines ordering, style ranking, +failure behavior and the embedded fallback. The lower-level functions below return +candidate paths; their filename heuristic is separate from this name resolution. + +Configuration retains immutable font bytes. Adapters use those snapshots and +collection indices instead of reopening discovered paths. Missing or unloadable +registered families are unavailable for that catalogue; saved-choice restoration +checks its current group and available members. Ordinary lookup does not rescan +or detect changed files. Reconfigure explicitly when another loading boundary +needs a fresh snapshot; already retained fonts remain valid. + ## System-font candidates -[fonts-system.h](../../projects/engine/include/cheryl/core/resources/fileio/fonts-system.h) +[fonts-system.h](../../../projects/engine/include/cheryl/core/resources/fileio/fonts-system.h) returns owned paths and performs no provider work. `CE::Text::system_font_directories()` lists `/usr/share/fonts`, `/usr/local/share/fonts`, `/Library/Fonts` and `/System/Library/Fonts`, then appends paths derived from nonempty environment values: @@ -85,7 +137,7 @@ inspection and does not try another candidate if later loading fails. ## Legacy font cache -[CE::Text::Legacy::Manager](../../projects/engine/include/cheryl/core/resources/asset-management/font-mgr.h) +[CE::Text::Legacy::Manager](../../../projects/engine/include/cheryl/core/resources/asset-management/font-mgr.h) loads on the active provider's loading owner under the [cache ownership contract](../resources/resource-residency.md#cache-ownership). It uses lexically normalized paths as cache keys, processes the supplied order and retains diff --git a/docs/site/assets/index.md b/docs/site/assets/index.md new file mode 100644 index 00000000..4773fa36 --- /dev/null +++ b/docs/site/assets/index.md @@ -0,0 +1,26 @@ +(assets)= +# Assets and animation + +Load application content, author graphics manifests, and keep animation or tile +selection in game-owned state. The [Assets system](../engine-systems/assets.md) +introduces the public types. [First Game's sprite walkthrough](../getting-started/first-game.md#put-a-sprite-on-screen) +covers loading, placement and submission; [Play sprite animation](../getting-started/how-to/animate-sprite.md) +adds a per-actor playback cursor. +For a background PNG without a sprite definition, use the +[whole-image path](../getting-started/how-to/draw-sprite.md#draw-a-whole-image). + +```{toctree} +:maxdepth: 1 +:titlesonly: + +Load and publish assets +Author graphics manifests +Play sprite animation <../getting-started/how-to/animate-sprite> +Animate sprites and select tiles +Discover files and font candidates +Install optional artwork packages +``` + +For uploads during play, use the ownership transfers in +[Threads and Data Flow](../architecture/threads-data-flow.md#prepare-and-publish-a-replacement). +Font selection and shaped labels have a separate [Text](../text/index.md) path. diff --git a/docs/assets/text-encoding.md b/docs/site/assets/text-encoding.md similarity index 97% rename from docs/assets/text-encoding.md rename to docs/site/assets/text-encoding.md index 98fa422a..140c6d6b 100644 --- a/docs/assets/text-encoding.md +++ b/docs/site/assets/text-encoding.md @@ -1,5 +1,7 @@ # Text encoding and source indices +Include `` for the decoder and its owned scalar records. + `CE::Text::decode_utf8_scalar(text, offset)` reads one scalar from a complete UTF-8 view. `CE::Text::decode_utf8(text)` returns owned `CE::Text::Utf8Scalar` records for the entire view. Each record stores the scalar, original byte offset/count and a `replaced` flag; diff --git a/docs/site/assets/text-layout.md b/docs/site/assets/text-layout.md new file mode 100644 index 00000000..eac69381 --- /dev/null +++ b/docs/site/assets/text-layout.md @@ -0,0 +1,501 @@ +# Unicode text layout and font resources + +## Choose a text path + +| Content | Use | Working sequence | +| --- | --- | --- | +| A game-owned label or HUD message | Engine's configured default or a registered font, retained text updater | [Display and update text](../getting-started/how-to/update-text.md): supply content/options, advance replacement, and submit its current generation in a frame. | +| Text inside a native widget or RML document | The selected toolkit's authoring interfaces with Engine fonts | A Context-aware UI session installs Engine's default. Use the [TGUI](../modules/tgui.md#fonts) or [RmlUi](../modules/rmlui.md#fonts) bridge to select a registered font for an element. | +| Existing ASCII or bitmap-atlas consumers | The legacy Font interfaces and their metrics | Keep their atlas, scale and bank compatibility contract during migration. | + +The [Text system](../engine-systems/text.md) identifies the public types, while +[Threads and Data Flow](../architecture/threads-data-flow.md#prepare-and-publish-a-replacement) +explains replacing a message without changing already published frames. + +## Supported rendering scope + +The initial rendering scope is accented Latin (including English, French and German), +Russian/Cyrillic, mixed paragraph direction and optional width-constrained wrapping. +Glyph coverage and shaping are font/script concerns rather than a separate renderer +for every language. Color emoji is a planned +[next text batch](../../planning/mid-term/README.md#color-emoji); +wider CJK/script acceptance remains in the +[long-term plan](../../planning/long-term/README.md#other-engine-extensions). +Display source maps do not establish caret movement, selection or IME/preedit. + +The service pairs face-qualified glyph placements with complete immutable page +generations. Its public stages have separate owners: + +| Namespace | Ownership | +| --- | --- | +| `CE::Text` | Immutable font collections, UTF-8/source records and shaped layouts; no uploaded resources. | +| `CE::Text::Rendering` | `Prepared` owns CPU layout/raster pages; `Generation` retains their uploaded geometry/atlas handles. | +| `CE::Rendering::Submission` | `resolve_text()` and `resolve_text_runs()` copy draw values into retained packets. | + +`CE::Text::Legacy::Font` exposes one atlas/geometry pair, which cannot represent +that fallback model. Its `AsciiFont`/`BitmapFont` implementations and toolkit-owned +text retain their separate roles. + +Engine also owns the opaque +[`CE::Text::FontRasterizer`](../../../projects/engine/include/cheryl/text/font-rasterizer.h) +CPU service for loading copied font bytes, properties, metrics, glyph pixels and +kerning. Copies retain and serialize native face state; returned data owns its +storage and needs no provider/context. TGUI and RmlUi adapters use this shared +implementation while retaining their own toolkit layout/atlas/effect logic. +FreeType rasterization, HarfBuzz shaping and ICU Unicode processing remain +distinct responsibilities, all implemented inside Engine. Toolkit color-glyph +handling does not establish color-emoji acceptance for this Engine layout path. + +Those native libraries use shared linkage. Linux uses distribution ICU libraries; +Windows 10 version 1903+ uses the operating system's ICU C API. Applications use +the same Cheryl text interfaces on both platforms; see +[dependency settings](../development/building.md#dependency-and-toolchain-settings) +for build and runtime requirements. + +## Configure fonts once + +Include `` for catalogue configuration and retained +fonts, and `` when laying out direct Engine text. + +`engine.fonts()` owns a `CE::Text::FontService`. With no configuration, +`default_font()` returns Engine's embedded DejaVu Sans face; displaying a label +does not require a font file or installed system font: + +```cpp +auto font = engine.fonts().default_font(); +auto layout = CE::Text::layout_text(font, "Score: 0", {.pixel_height = 24}); +``` + +Configure application fonts at startup, before creating UI sessions or other +font consumers. Existing consumers retain their selected font snapshots. + +### Set an ordered application default + +Use `FontSelection` when the game has an ordered list of preferred default fonts: + +```cpp +CE::Text::FontSelection selection; +selection.asset_directories = {asset_root}; +selection.automatic_system_fonts = true; +selection.preferred = { + "ProggyVector", "Cantarell", "FreeSans", + "Adwaita Mono", "FreeSerif", "DejaVu Sans" +}; +engine.fonts().configure(selection); +auto font = engine.fonts().default_font(); +``` + +Keep the game's existing `asset_root` and preference list. Missing names are +skipped. The first available face becomes primary; later faces provide glyph +fallback in preference order, followed by automatic choices when enabled and the +embedded safety face. Each name resolves application assets before installed fonts. + +For code previously calling `FontCollection::load(selection)`, configure the +service with that same selection to establish Engine and context-aware GUI +defaults. New consumers can retain the returned `Font`; collection-based callers +can use `font.collection()`. The standalone loader remains useful for independent +CPU font processing. + +This overload keeps the stable reference `application-default`. Use +`configure(selection, "interface")` to give the ordered policy an application +reference. Selected source faces retain path/collection-index identities, +independent of preference positions; the embedded face uses `cheryl:embedded`. +The chosen reference cannot collide with embedded or selected source references. +See [Font selection](#font-selection) for source forms and discovery details. + +### Register named families and groups + +Use `FontConfiguration` when the application needs separately named font choices, +style variants, or selection groups. A `FontFamily` gives a font a stable application reference. +Its sources supply the family's regular, bold and italic faces. A named group +contains alternative family references to choose from; it is separate from +style variants and glyph fallback. + +```cpp +CE::Text::FontConfiguration configuration; +configuration.asset_directories = {asset_root}; +configuration.families = { + {"interface", {"Interface.ttf"}}, + {"book-serif", {"Book Serif"}}, + {"handwriting", {"Handwriting.ttf"}} +}; +configuration.default_font = "interface"; +configuration.groups = {{"books", {"book-serif", "handwriting"}}}; +configuration.fallbacks = {"interface"}; +engine.fonts().configure(configuration); +``` + +The filenames and family names above are application choices. Sources use the +same `FontSource` forms and discovery roots as [Font selection](#font-selection). +Named-family registration retains available style variants, including explicit +collection-face indices. Explicit files can list separate regular/bold/italic +sources under one reference. Registering a specialty font does not add it to +other fonts' fallback chains; `fallbacks` selects that common chain explicitly. +The embedded face remains its terminal fallback. +Adapters obtain the complete ordered chain through `FontService::fallbacks()`; +each returned handle contributes its primary family, including when the default +family also appears in the configured fallback list. + +`default_font()` returns the configured default when available, otherwise the +embedded font. `default_font` selects one family reference; the registration order +and `fallbacks` list do not select another default when that family is unavailable. +The common fallback list supplies missing glyphs for available fonts. +`get("handwriting")` selects that registered font; an unknown or +unavailable reference resolves to the default. `all()` lists the available +registered handles and the embedded handle. References are case-sensitive +application identifiers, independent of registration order and physical family +names. Empty references, embedded NUL and the reserved `cheryl:embedded` reference +are invalid for application registrations. Duplicate family references reject +configuration. + +Context-aware TGUI and RmlUi sessions obtain the configured fonts directly. +Their default inherits through the toolkit's widgets/documents. For a specific +element, use TGUI's `font()`/`set_font()` or RmlUi's `set_font()` bridge with an Engine +`Font`, or the RmlUi alias returned by `font_family()`. Native widget authoring remains in +the selected toolkit; see [GUI integration](../engine-systems/gui.md) for ownership +and the [TGUI](../modules/tgui.md#fonts) or [RmlUi font contract](../modules/rmlui.md#fonts) +for native style and alias rules. + +### Retained font handles and style + +`CE::Text::Font` is a copyable, resolved CPU handle. It retains font bytes, style +metadata, collection indices and its ordered fallback families. It remains usable +after the service or Context is destroyed. `layout_text(font, text, options)` +does not need a current Engine context. Select a style through layout options: + +```cpp +auto book_font = engine.fonts().get("book-serif"); +auto heading = CE::Text::layout_text( + book_font, "Chapter One", + {.pixel_height = 32, .font_style = {.weight = 700, .italic = false}} +); +``` + +`font.collection(style)` selects the closest available style from each retained +family for the existing collection-based APIs. Selection prefers matching italic +state, then the closest numeric weight. Adapter-facing `faces()` exposes borrowed +bytes and face/style metadata; retain the `Font` while using those views. +`FontRasterizer::load(font, style)` opens the selected primary style, while +`load(face)` opens an exposed face directly. A single rasterizer does not perform +the collection's fallback selection. + +### Choose and save a font + +Random choice is explicit. `choose_random("books")` returns a resolved `Font`; +ordinary font lookup and rendering do not choose again. To restore a saved choice, +pass its reference to `restore_or_choose()`: + +```cpp +// These two save functions belong to the application. +const std::string saved_reference = load_book_font(book_definition_id); +auto book_font = engine.fonts().restore_or_choose("books", saved_reference); +save_book_font(book_definition_id, book_font.reference()); + +auto page = CE::Text::layout_text(book_font, contents, {.pixel_height = 24}); +``` + +Run this at the application's shared book initialization boundary. Keying saved +choices by book definition makes other copies of the same book reuse the choice; +an instance key permits individually styled copies. The book keeps the returned +font handle. The application owns these identities and its save format. + +Restoration keeps a reference only while it is still in the selected group and +its font loaded successfully. Otherwise it chooses among the group's available +members. A missing/empty group or one with no available members uses the +application default, then the embedded fallback. Save the returned `reference()` +to persist replacements. Group changes do not reroll an existing choice that +remains available and belongs to the group. + +The simple overload uses the service's random generator. Overloads taking a +caller-owned `std::mt19937_64&` support controlled selection; restoring an available +group member does not advance it. Save the chosen reference, not the random seed, +runtime handle, or font bytes. Changing group contents or random call order cannot +reinterpret an already saved reference. + +### Loading and availability + +Configuration loads CPU snapshots and publishes the complete catalogue together. +Missing or unloadable optional families are unavailable for this configuration; +invalid source syntax and allocation failures propagate. Failed configuration +leaves the previous catalogue usable. Catalogue operations are synchronized, +and existing font handles remain valid when configuration is replaced. Configure +before consumers: replacement does not update existing widgets or font handles. + +Lookup does not rescan files or detect source replacement. Removing a source file +after configuration does not invalidate its retained bytes. A later explicit +configuration evaluates availability again. References identify the application's +font choices; they do not identify a particular revision of a font file. + +For an ordered default policy, use the [selection overload](#set-an-ordered-application-default). +Its selected bytes are also retained for toolkit consumers without reopening paths. + +## Font selection + +`CE::Text::FontCollection::load(selection)` creates an immutable CPU snapshot of font +bytes. Put font names in `FontSelection::preferred`, in the order the game should +try them. Each name searches application fonts first, then installed system fonts; +the engine discovers system directories without application setup. For example: + +```cpp +CE::Text::FontSelection font_selection; +font_selection.preferred = {"Arial", "Noto Sans", "DejaVuSans.ttf"}; +font_selection.automatic_system_fonts = false; +auto fonts = CE::Text::FontCollection::load(font_selection); +``` + +Here `Arial` and `Noto Sans` are family names read from font metadata, independent +of their filenames. `DejaVuSans.ttf` is a filename alias for an application font; +it can be nested anywhere under the application asset roots. Names compare ASCII +letters without case. System fonts resolve by family name; filename aliases apply +only to application fonts. A missing name is skipped, so other preferences still +load. Empty names and names containing NUL are invalid. The embedded fallback +remains available even if none of these names resolve. + +`asset_directories` defaults to `{"assets"}`, relative to the process's working +directory. Font discovery recursively scans these roots for `.ttf`, `.otf`, `.ttc` +and `.otc` files, without requiring a `fonts/` subdirectory or graphics manifest. +If the game already has a selected asset root, set it once: + +```cpp +font_selection.asset_directories = {asset_root}; +``` + +Set that before calling `load()`. An empty list disables application-font discovery. +When both an application font and a system font match a name, the application +version wins. Within the chosen inventory, selection first prefers faces that are +neither bold-flagged nor heavier than Medium (weight 500), then upright faces, then +the declared OpenType weight closest to Regular (400). When numeric weight metadata +is absent, the bold flag supplies a 700/400 estimate. Sorted path/face order breaks +ties, including repeated application filenames. Discovery skips missing, unreadable +and unsupported candidates; allocation failures propagate. + +For explicit control, the same `preferred` list also accepts +`CE::Text::FontFile{path, face_index}` and `CE::Text::SystemFontFamily{name}`. +`FontFile` loads that path directly; a relative path uses the working directory, +not the asset roots. Its zero-based face index selects an ordinary collection face +with a variable font's default coordinates, rather than a named variation instance. +Missing explicit files are skipped; existing unreadable, corrupt, nonscalable or +non-Unicode faces fail the whole load. `SystemFontFamily` restricts lookup to the +installed inventory, bypassing application overrides; unavailable families are +skipped. Nothing is published on failure, and earlier font collections remain usable. + +System discovery uses the existing +[root traversal](file-and-font-discovery.md#system-font-candidates). Leave +`system_directories` unset for automatic system roots; an explicit list replaces +them, and an empty list disables installed-font search. Keep environment/files +stable during loading. A snapshot no longer needs those files afterward. + +With `automatic_system_fonts`, append available Arial, Segoe UI, Helvetica, Noto Sans, +DejaVu Sans and Liberation Sans families in that order, preferring application +fonts over system fonts and skipping a family if its selected face is bold-flagged +or heavier than 500. A heavy asset override therefore skips that automatic family +even when a lighter system copy exists. Disabling it leaves explicitly preferred +names, files and families active. Preferred sources remain unrestricted by this automatic-weight +limit; name/family lookup can select a heavy face when no lighter one exists. +These preferences are not guaranteed to exist across operating systems or regions. +Repeated path/face entries are deduplicated. The terminal fallback is always an +[embedded, licensed DejaVu Sans](../../../assets/fonts/README.md) face independent of the +application's fonts, UI modules and installed inventory. The fallback covers the +initial alphabets and contains a visible U+FFFD replacement; it cannot supply the +original glyph for every Unicode character. + +`faces()` returns metadata in selection order. Its indices identify faces only +within that retained collection. `covers(scalar)` checks whether any selected cmap +contains the scalar; it does not prove whole-grapheme coverage or successful shaping. +Each query opens local FreeType state, so immutable collections do not share mutable +face/size state between CPU workers. Library-native types stay out of public headers. + +## Shaping, direction and width + +`CE::Text::layout_text(fonts, utf8, options)` returns immutable `CE::Text::ShapedText`: original +[decoded scalar records](text-encoding.md), face-qualified glyph IDs/positions, +shaping-cluster byte/scalar ranges and line metrics. It retains its font collection +and copied options but no borrowed text, provider or uploaded resource. The font +size is an em size in local pixels, with Y-up baseline coordinates. It is independent +of camera/screen units and does not change the legacy AsciiFont/BitmapFont metric contract. + +[HarfBuzz](https://harfbuzz.github.io/what-is-harfbuzz.html) handles OpenType shaping, +kerning and ligatures. A precomposed accent and its combining sequence can produce +the same glyph while retaining different source ranges. Multiple glyphs may share a +cluster; a ligature can cover multiple scalars/graphemes. Cluster ranges describe +display associations, not selectable caret stops. Language is an optional BCP 47 +hint (default `und`); it does not filter scripts or translate text. Invalid tags fail. + +ICU resolves [paragraph bidi](https://unicode-org.github.io/icu/userguide/transforms/bidi.html) +before visual run shaping, and applies line direction after breaking. Direction is +automatic (first strong character, otherwise LTR), explicit LTR or explicit RTL. +Latin/digits retain their natural run direction in an RTL paragraph. Unicode overrides, +isolates and default-ignorable formatting participate in bidi without drawing visible +replacement boxes. ICU resolves bidi controls before shaping; those controls stay +in the owned scalar records and line ranges but are omitted from the shaping buffer +so their removal cannot replace a visible glyph's source index. Joiners and variation +selectors remain available to HarfBuzz. With a width, RTL paragraphs align to its +right edge; without a width, visual lines start at local X=0. CR/LF/CRLF and Unicode +paragraph separators start paragraphs; U+2028 forces a line within its existing paragraph. + +ICU [grapheme boundaries](https://unicode-org.github.io/icu/userguide/boundaryanalysis/) +define fallback units. Select the first face covering all visible scalars of each +grapheme, keeping combining marks with their base. An uncovered grapheme becomes +one U+FFFD from the embedded face, with `ShapedGlyph::missing` set and its original +source range retained. A notdef returned by shaping also becomes a visible embedded +replacement. Literal U+FFFD and malformed UTF-8 remain distinguishable through scalar +records; encoding replacement and missing font coverage are separate properties. +Embedded NUL is a scalar requiring fallback, rather than an end marker. + +An optional positive finite `maximum_width` enables greedy Unicode line breaking. +The UI/application derives that local width from its container and text scale; layout +does not query a window or UI object. Chosen lines are shaped with their actual line +boundaries and accepted by measured advance, preserving paragraph bidi across wraps. +Oversized words break at grapheme boundaries. A single too-wide grapheme is admitted +on its own line and marks `overflow`; clipping/scaling remains a caller decision. +Tabs expand to four spaces. Trailing ASCII spaces/tabs and line separators have no +visual advance, while line source ranges retain their consumed bytes. Other spaces +follow font/Unicode break rules. No automatic hyphenation, justification or vertical +writing is supplied. + +Text byte/UTF-16 and font size limits are checked before narrowing into library +indices/metrics. Invalid options fail before publication, allocation/library failures +propagate, and previous layouts are unchanged. Each layout uses local FreeType, +HarfBuzz and ICU state, allowing concurrent queries against one font collection. +Line fitting currently measures candidate prefixes; very long constrained paragraphs +can require repeated shaping. Optimize that only with representative measurements, +while retaining the actual accepted-line width and source/bidi contracts. + +## Preparation, upload and retained submission + +The CPU and provider-owner stages produce complete values. Frame preparation +then resolves packets from the selected uploaded generation. + +```mermaid +flowchart LR + accTitle: From shaped text to retained draw packets + accDescr: A shaped layout is prepared into owned CPU glyph pages and geometry, uploaded into a complete generation, then resolved into packets retaining matching page resources. + layout["Shaped layout"] -->|prepare| pages["CPU pages and quads"] + pages -->|upload| generation["Uploaded generation"] + generation -->|resolve| packets["Retained packets"] +``` + +`CE::Text::Rendering::prepare(shaped, raster_options)` owns the layout and prepares +grayscale alpha pages and six-vertex glyph quads on the CPU. It rasterizes each +distinct face-qualified glyph once for that message, retaining glyph bearings and +shaped placement offsets. Spaces/empty outlines advance without drawing. Pages use +one transparent border pixel, are cropped to used padded bounds and preserve the +font provider's row/UV order. The configured maximum page extent is 4..4096 pixels +(default 1024); a glyph too large for one padded page fails before a bitmap allocation. +There is no shared glyph cache, in-place repack, eviction or residency budget. + +`CE::Text::Rendering::upload(prepared, provider)` copies CPU buffers into fresh immutable page +geometry/atlas handles on the provider's upload owner. A nonempty generation also +retains the provider's default textured-triangle material; an optional third +material argument selects an explicit handle instead. No application shader +files or material factory are required for the default path. Empty text needs no +built-in material capability. Passing a copy preserves CPU +preparation for retry; moving it into an owned +[dispatcher request](../runtime/thread-dispatch.md) transfers that +value. A successful call returns one `CE::Text::Rendering::Generation` with its matching +placements and resources. A failure returns no generation and publishes no replacement. +Already created transient resources follow backend retirement; this is not an atomic +native-resource transaction. Callers publish a complete candidate only after success. + +`CE::Rendering::Submission::resolve_text(rendered, style, context)` performs CPU packet resolution alongside +the legacy font overload. An absent `style.material` uses the generation's retained +material. A default `Context2D` supplies the public `image` key at unit zero so +each glyph selects its own generation page. Custom contexts can choose another +nonempty key/unit; that key must be absent from draw parameters. Glyph offsets are scaled and transformed by the supplied model exactly +as in legacy submission. Packets copy values and retain both page handles. Replacing +or releasing the text/font/preparation cannot pair old quad ranges with a newer atlas. +Old submitted frames retain their resources, subject to the original backend domain +remaining alive for native use. Submission performs no shaping, discovery, rasterization, +upload, binding or drawing. + +### Retained text updates + +`CE::Text::Rendering::Updater` in `` owns the +replacement sequence for a changing label. Construct it with a `Context` during +platform initialization, then call `request()` and nonblocking `update()` on the +simulation owner. The application supplies a value-owned `Request`: font, UTF-8 +text, layout/raster options and optional material. An empty font selects the +default captured at construction. A synchronous initial-request constructor +provides `current()` immediately; the constructor without a request starts empty. + +Preparation uses a context-tracked worker group; upload uses its saved platform +submission endpoint. Jobs retain owned values and never borrow the updater or +game. Runtime's existing worker/dispatcher shutdown remains responsible for +settling or cancelling accepted work. Destroying the updater does not wait for +jobs. Serialize updater calls and stop simulation use before platform cleanup. + +One active request and the newest desired request bound outstanding work. +Equal requests compare all layout/raster fields, material identity and retained +font-snapshot identity; they submit no duplicate work. A completed active result +is adopted even if a newer request is waiting, then that newest request starts. +Intermediate desired requests are coalesced. Older frames retain their resources. + +Asynchronous submission/execution failures leave the previous `current()` intact. +`failure()` retains the preparation/upload stage, exception and failed revision +for application reporting. A distinct request clears that report; retrying a +settled failed request clears it too. An older active request can subsequently +fail while a newer desired request proceeds. Unchanged failed work stays settled +until explicit retry. +Initial synchronous construction failures propagate. No automatic retry loop or +background mutation of the game's current generation occurs. + +The [text task](../getting-started/how-to/update-text.md) gives the complete +initialization/update/submission/cleanup sequence. Direct `layout_text()`, +`prepare()` and `upload()` remain available for application-owned scheduling. + +### Positioned text runs + +Set `CE::Text::Rendering::RasterOptions::positioned_runs = true` during `CE::Text::Rendering::prepare()` to retain +additional geometry with glyph placements baked into its vertices. Ordinary page +geometry remains available for per-glyph submission; opting in trades preparation, +upload and geometry storage for fewer per-frame packets. The extra geometry repeats +quads in authored glyph order and records each contiguous atlas-page range. A page +sequence A/B/A remains three runs rather than grouping both A ranges together. + +After upload, call `CE::Rendering::Submission::resolve_text_runs(rendered, style, context)` to emit one packet +per run. Preparation must have opted in; an ordinary generation rejects this call. +The image key/unit, immutable sampling, clipping, material and whole-text model/scale +remain those selected by the caller. Packets retain the positioned geometry and +matching atlas generation through replacement/release; empty text emits no draws. + +The default material supports this overload. Custom materials require a shader applying the common scale/model transform to the +positioned vertices, without per-glyph or draw-identity semantics. The API cannot +infer that contract from arbitrary shaders. Use `CE::Rendering::Submission::resolve_text()` for those shaders +or when the original per-glyph raster evaluation is required: regrouping floating-point +transforms can change coverage of exact boundary pixels. The default preparation +and submission remain per glyph. This facility does not merge arbitrary frame +packets, reorder draws or change either UI adapter's batching policy. + +The [demo](../../../projects/apps/demo/README.md#builtin-unicode-text) submits immutable +HUD/camera text and uses the retained updater for changing HUD content. Its preview supports builtin-only, automatic, explicit file/ +family and paragraph-direction choices. The demo guide preserves +[native preview procedures](../../../projects/apps/demo/README.md#unicode-preview-validation). +Its `--text-runs` option selects positioned submission for builtin text and sample +labels; toolkit-owned text keeps its separate path. + +## Native acceptance scope + +Linux/GLFW/X11/OpenGL sequential/concurrent preview appearance, wrapping, paragraph +alignment and resource replacement/shutdown are accepted for the initial grayscale +scope. Similar output across runtime/font variants is compatible with the same +effective glyph faces and placements. Right-aligned Latin under explicit RTL and +unchanged Hebrew-only output between automatic/RTL modes match the direction +contract above; this native report does not independently confirm Hebrew glyph order. +The accepted [CPU bidi case](../../../projects/engine/tests/unit/src/text-layout.cpp) +checks Hebrew/mixed visual source order and preserves Latin order under explicit RTL. +Alignment alone is not proof of every bidi sequence. + +Linux OpenGL run acceptance compares the test application's 64×64 backbuffers with +`glReadPixels`, drawing the same retained generation through per-glyph and run paths. +It covers nearest/linear sampling, clipping, signed scale/rotation, page transitions +and replacement lifetime. It does not capture the desktop or other applications. +These fixture comparisons and CPU submission measurements do not establish every +boundary-pixel identity, physical-GPU timing or other-platform appearance. + +CPU and adapter fixtures cover registered weight/style selection and collection +face indices. Automatic discovery's numeric-weight boundaries remain in the +[font-selection plan](../../planning/short-term/README.md#font-selection-regressions). These fixtures do +not establish native visual correctness of distinct authored style faces. Color/CJK rendering, +grapheme/bidi editing, IME and additional platforms retain separate scope. Reusable +[CPU checks](../development/architecture-validation.md#engine-asset-and-text-regressions) +and these Linux observations do not establish other-platform coverage. diff --git a/docs/site/audio/index.md b/docs/site/audio/index.md new file mode 100644 index 00000000..a455c070 --- /dev/null +++ b/docs/site/audio/index.md @@ -0,0 +1,16 @@ +# Audio + +Prepare clips or streams, retain voices that need control, and close output after +its producers settle. Audio uses its own playback clock and application-owned +lifetime. The [Audio system](../engine-systems/audio.md) introduces the types. + +```{toctree} +:maxdepth: 1 +:titlesonly: + +Control playback and ownership <../runtime/audio> +miniaudio reference <../modules/miniaudio> +``` + +The [miniaudio module](../modules/miniaudio.md#preparation-and-output) supplies the +concrete setup example and independent device/offline procedures. diff --git a/docs/development/architecture-validation.md b/docs/site/development/architecture-validation.md similarity index 76% rename from docs/development/architecture-validation.md rename to docs/site/development/architecture-validation.md index ab439c27..3b92bf8d 100644 --- a/docs/development/architecture-validation.md +++ b/docs/site/development/architecture-validation.md @@ -1,11 +1,32 @@ -# Architecture validation +(architecture-validation)= +# Component and SDK checks This guide describes reusable checks for the selected Engine, Native GLFW and -OpenGL assembly. [The module guide](modules.md) defines target ownership, public -usage requirements, selection options and test runners. Use these procedures when +OpenGL assembly. [Module authoring](modules.md) defines target boundaries and +independent owners; [Source and SDK integration](consuming-engine.md) owns application +selection, public usage requirements and deployment. Use these procedures when changes affect isolation, composition or consumer contracts; coverage limits remain separate from source completion. +## Start with the changed boundary + +Use [Testing and validation](testing.md#choose-checks-for-a-change) for runner +selection. This page owns the deeper composition and acceptance procedures: + +| Change | Procedure | +| --- | --- | +| Exported header, public target or standalone owner | [Independent consumers](#independent-consumers-and-header-probes) and [component configurations](#configure-component-combinations). | +| SDK export, relocation or native dependency packaging | [Installed SDK consumers](#installed-sdk-consumers). | +| Asset/text replacement or partial-publication behavior | [Engine asset and text regressions](#engine-asset-and-text-regressions). | +| Worker placement, JSON or topology | [Worker topology and configuration](#worker-topology-and-configuration). | +| Typed event delivery and closure | [Typed events](#typed-events). | +| Native rendering, sampling or retained text | [Sampling and positioned text](#sampling-and-positioned-text), with [native/fixture prerequisites](#native-and-fixture-checks). | +| Logging/signal driver behavior | [Manual acceptance drivers](#manual-acceptance-drivers). | + +Start with the relevant procedure and expand only when the changed contract +crosses another boundary. [Evidence limits](#evidence-limits) distinguish controlled +checks from native execution and physical observation. + ## Coverage and selection | Selection | Required checks | @@ -15,18 +36,60 @@ separate from source completion. | Engine + Native GLFW + OpenGL | OpenGL consumer/probes, module implementation cases and selected owner/combined runner registration are correct. Native startup, presentation, retained-resource cleanup, input backpressure and concurrent shutdown require their explicit opt-ins. | | Standalone and supplied targets | Each module reuses one Engine; OpenGL reuses one Native GLFW. Dependency bootstrapping does not recursively enable root modules, tests or demo. Consumers link their actual owners and CTest discovers module-local cases. | | Crash bootstrap | An Engine-only consumer referencing no Engine entry point receives the automatic bootstrap object. Check the consumer's actual `NDEBUG` scope independently of exception/explicit traces or standalone Backward tests. | -| Startup support | Explicitly compile `cengine_startup` in Engine-only composition and `module_opengl_startup` through the native demo. Installed SDK checks compile both support headers independently. Plain Engine/OpenGL links remain free of CLI11 usage requirements; dedicated parser/factory regressions remain pending. | +| Startup module | Compile `module_startup` with OpenGL integration omitted for neutral composition and enabled for native composition. Check the generic and compatibility headers/factories plus selected-backend options. Plain Engine/OpenGL links remain free of CLI11 usage requirements. | | Worker support | Engine-only/JSON-only selection omits hwloc discovery/linkage. Controlled fixtures check owned snapshots, sparse/asymmetric topology, placement, fairness/caps and failure safety; native hwloc checks additionally require real eligible CPUs and binding support. | -| Installed SDK | Extract into a different prefix, reject producer-path leakage, compile public/Startup header probes and run consumers linked only to installed Cheryl libraries. Windows must use bundled dependency DLLs; source-tree consumers alone do not prove this path. | +| Installed SDK | Extract into another prefix, reject producer-path leakage, compile public/Startup/native-toolkit probes and link only installed owners. Check packaged and system shared runtimes with both Cheryl linkages. Static consumers also resolve embedded archives and native final-link requirements. Source-tree consumers alone do not prove relocation. | Controlled Engine tests establish runtime, publication and failure behavior against -predictable dependencies. Implementing modules own checks of their actual behavior +predictable dependencies. In-memory display/window, input, presentation, renderer +and resource-provider adapters exercise both runtime modes without OpenGL/GLFW +headers. Implementing modules own checks of their actual behavior and contract compliance; passing with a dummy does not establish module conformance. Broader Engine cases cover dispatch/cancellation, events, workers, timing, caches, retained frames and failure paths. Owner runners and the combined runner reuse case objects, so choose one runner or CTest prefix for each scenario rather than executing the same cases through every aggregate. +## Independent consumers and header probes + +[Engine consumer](../../../projects/engine/tests/consumer/CMakeLists.txt) links only +Engine and exercises multiple translation units, events, workers and CPU resources. +Its first-include probes include the neutral umbrellas and clipping contract and reject GL/GLFW +header leakage. Standalone bootstrapping selects Engine only in a local scope. + +[Native GLFW consumer](../../../projects/modules/platform/native-glfw/tests/consumer/CMakeLists.txt) +and [OpenGL consumer](../../../projects/modules/graphics/opengl/tests/consumer/CMakeLists.txt) +link their actual module with first-include probes. They +reference real implementation symbols without requiring a display at execution. +`CHERYL_BUILD_CONSUMER_TESTS=ON` adds consumers for the selected root assembly. + +The Engine-only consumer entry point is: + +```sh +( + set -e + cd "$(git rev-parse --show-toplevel)" + cmake -S projects/engine/tests/consumer -B build-consumer-engine -G Ninja \ + -DCMAKE_BUILD_TYPE=Release -DCHERYL_REPOSITORY_ROOT="$PWD" + cmake --build build-consumer-engine --target consumer-cengine --parallel + ./build-consumer-engine/cheryl-consumer +) +``` + +Standalone consumer entry points locate their enclosing checkout automatically; +`CHERYL_REPOSITORY_ROOT=/path/to/cheryl-engine` explicitly selects another checkout +for shared helpers and bootstrapping. +Engine, Native GLFW and OpenGL consumer bootstraps suppress both UI selections in +a local variable scope, including inherited `ON` choices, without changing the +enclosing configuration's cache. +Normal Native GLFW consumption selects no Cheryl OpenGL/GLAD dependency; the +OpenGL consumer selects both integration owners. CMake 4 hosts may need +`CMAKE_POLICY_VERSION_MINIMUM=3.5` for pinned legacy dependency projects. Standalone +module entry points and explicit null-platform selection are documented separately +in [Module authoring](modules.md). The procedures below cover independent graphs, +selected runners and native acceptance; source consumers alone do not establish +installed SDK relocation. + ## Repeating validation Use the toolchain and selected-owner prerequisites in the [build guide](building.md). @@ -41,9 +104,31 @@ avoids C++ language-module scanning; Cheryl integration modules are libraries. build execution. [Runner selection](testing.md) explains focused and aggregate registration. +### Configure component combinations + +After [preparing an optional shared dependency prefix](building.md#dependency-and-toolchain-settings), +the configure-only matrix checks fresh Engine-only, neutral/native Startup, +Native GLFW, OpenGL, TGUI, RmlUi, audio, JSON-only/native Workers and supplied-Engine +compositions: + +```sh +( + set -e + cd "$(git rev-parse --show-toplevel)" + python3 .github/ci/configure_components.py \ + --vcpkg-root build/tools/vcpkg --installed-dir build/dependencies \ + --build-root build/component-validation +) +``` + +It selects Ninja, creates fresh `sdk-config-` directories and refuses to +overwrite existing ones. Use `--case NAME` to select a case and `--triplet` to +select another prepared target. The matrix performs no source builds; it checks +configuration and composition without establishing symbol linkage or execution. + ### Engine only -A fresh Release configuration provides a neutral graph without sandbox mode: +A fresh Release configuration provides a neutral graph: ```sh ( @@ -58,9 +143,11 @@ A fresh Release configuration provides a neutral graph without sandbox mode: -DCHERYL_BUILD_DEBUG_TERMINAL_LINUX=OFF \ -DCHERYL_BUILD_UI_TGUI=OFF -DCHERYL_BUILD_UI_RMLUI=OFF \ -DCHERYL_BUILD_AUDIO_MINIAUDIO=OFF \ + -DCHERYL_BUILD_WORKER_TOPOLOGY_HWLOC=OFF -DCHERYL_BUILD_WORKER_TOPOLOGY_JSON=OFF \ + -DCHERYL_BUILD_DOCS=OFF -DCHERYL_BUILD_SDK=OFF \ -DCHERYL_BUILD_TESTS=ON -DCHERYL_BUILD_CONSUMER_TESTS=ON \ -DCHERYL_BUILD_ALL_TESTS=OFF -DCHERYL_BUILD_ACCEPTANCE_TESTS=OFF \ - -DCHERYL_BUILD_DEMO=OFF -DCHERYL_SANDBOX_BUILD=OFF + -DCHERYL_BUILD_DEMO=OFF cmake --build build-validation-release --parallel --target \ consumer-cengine tests-engine tests-logging \ acceptance-logging acceptance-signal @@ -95,7 +182,7 @@ probes while omitting optional topology helpers, modules, tests and SDK exports: -DCHERYL_BUILD_SDK=OFF -DCHERYL_BUILD_TESTS=OFF \ -DCHERYL_BUILD_CONSUMER_TESTS=ON cmake --build build/testing-engine-isolation --parallel --target \ - cengine_startup consumer-cengine + module_startup consumer-cengine ./build/testing-engine-isolation/cheryl-consumer ninja -C build/testing-engine-isolation -t commands consumer-cengine ) @@ -130,16 +217,18 @@ caller's working directory: -DCMAKE_BUILD_TYPE=Release -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_SCAN_FOR_MODULES=OFF \ -DCMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE=PRE_TEST \ - -DCHERYL_LOG_PROFILE=developer -DCHERYL_SANDBOX_BUILD=OFF \ + -DCHERYL_LOG_PROFILE=developer \ -DCHERYL_BUILD_NATIVE_GLFW=OFF -DCHERYL_BUILD_OPENGL=OFF \ -DCHERYL_BUILD_DEBUG_TERMINAL_LINUX=OFF \ -DCHERYL_BUILD_UI_TGUI=OFF -DCHERYL_BUILD_UI_RMLUI=OFF \ -DCHERYL_BUILD_AUDIO_MINIAUDIO=OFF \ + -DCHERYL_BUILD_WORKER_TOPOLOGY_HWLOC=OFF -DCHERYL_BUILD_WORKER_TOPOLOGY_JSON=OFF \ + -DCHERYL_BUILD_DOCS=OFF -DCHERYL_BUILD_SDK=OFF \ -DCHERYL_BUILD_TESTS=ON -DCHERYL_BUILD_CONSUMER_TESTS=ON \ -DCHERYL_BUILD_ALL_TESTS=OFF -DCHERYL_BUILD_ACCEPTANCE_TESTS=ON \ -DCHERYL_BUILD_DEMO=OFF cmake --build build/testing-engine --parallel "$(nproc)" --target \ - cengine_startup tests-engine consumer-cengine + module_startup tests-engine consumer-cengine ./build/testing-engine/cheryl-consumer ctest --test-dir build/testing-engine --parallel "$(nproc)" --output-on-failure \ --no-tests=error \ @@ -152,9 +241,9 @@ Engine-only selection; startup support and the independent consumer also compile The existing `font_selection.families` fixture establishes basic discovery and ordering; it does not cover declared-weight ranking or heavy-only automatic exclusion. Those fixture/case gaps remain in the -[Unicode plan](../planning/short-term/unicode-text.md#progress). Startup and UI +[font-selection plan](../../planning/short-term/README.md#font-selection-regressions). Startup and UI callback/placeholder regression gaps are in the -[roadmap checklist](../planning/develop-review-and-development-plan.md#startup-and-ui-follow-up). +[startup/UI checklist](../../planning/short-term/README.md#startup-and-ui-follow-up). Graphics 2.0, indexed shader/sampling and positioned-text CPU checks are accepted in the selected Linux compositions. The @@ -215,7 +304,7 @@ Correct masks/progress do not establish cache residency or a workload speedup. ### Installed SDK consumers -The [CI producer profile](building.md#github-ci-and-sdk-artifacts) configures Ninja +The [CI producer profile](ci.md) configures Ninja with the selected modules/helpers, explicit owner targets and `CHERYL_BUILD_SDK=ON`. Use a clean pinned checkout and the producer's dependencies/toolchain. Supply the font fixtures for complete allocation coverage; missing CFF coverage is reported @@ -234,15 +323,18 @@ separately. On Linux, a local equivalent is: Reuse `build/ci` only when its recorded source/toolchain/configuration/features match; omit the producer step when the matching build/checks are already accepted. The -consumer directory and its sibling `sdk-consumer-relocated-sdk` must be fresh. -The verifier checks SHA256, archive contents, producer paths and seven selected -consumer programs plus first-include and Startup probes. It uses checkout test -fixtures but imports installed Cheryl libraries, rebuilding none of their source. -Windows CI uses the same steps under its MSVC Developer PowerShell with the -manifest's vcpkg toolchain and DLL prefix. The selected hosted Ubuntu and Windows -producer/relocated-consumer paths are accepted within the -[CI coverage limits](building.md#github-ci-and-sdk-artifacts). The first main-push -artifact collection remains [TR20](../testing-requests.md#tr20-automated-main-push-sdk-collection). +consumer directory and its sibling `sdk-consumer-relocated-sdk` must be fresh; +the verifier also creates sibling hermetic and standalone-source directories. +It checks SHA256, archive contents, producer paths, independent consumers and +first-include/Startup/native-toolkit probes. Its hermetic composition clears +ambient package discovery and builds a standalone teaching application against +the relocated SDK. Test fixtures come from the checkout; Cheryl implementation +sources are not rebuilt. A JUnit report records verifier results. +The Linux producer uses distribution development packages. Windows CI uses the +same steps under its MSVC Developer PowerShell with the prepared shared dependency +toolchain. Platform acceptance remains subject to the +[CI coverage limits](ci.md). The first main-push +artifact collection remains [TR20](../../testing-requests.md#tr20-automated-main-push-sdk-collection). This reusable procedure does not assert acceptance of unexecuted new cases. ### Typed events @@ -263,9 +355,9 @@ Run the focused unit and acceptance selections: ) ``` -The [unit cases](../../projects/engine/tests/unit/src/typed-events.cpp) cover exact +The [unit cases](../../../projects/engine/tests/unit/src/typed-events.cpp) cover exact identity, type constraints, owned payloads, removal, nested dispatch and immediate -errors. The [acceptance cases](../../projects/engine/tests/acceptance/src/typed-events.cpp) +errors. The [acceptance cases](../../../projects/engine/tests/acceptance/src/typed-events.cpp) cover deferred copies, cancellation, error-sink ownership, waits and worker FIFO. `event_delivery.runtime_owner_threads` exercises typed resize payloads on platform and simulation owners in sequential and concurrent runtime modes without borrowing @@ -287,7 +379,7 @@ These cases invoke the actual registered C callback to check unchanged-size suppression, saved observations across nested resize delivery, legacy-before-typed offers and native failure consumption. They do not establish compositor-generated resize delivery. Skipped native cases leave that callback coverage pending. Windows execution -is shelved in the [platform plan](../planning/long-term/platform-acceptance.md). +is shelved in the [platform plan](../../planning/long-term/platform-acceptance.md). ### Native, OpenGL and standalone composition @@ -320,9 +412,15 @@ enable_testing() set(CHERYL_BUILD_ROOT "${CMAKE_CURRENT_BINARY_DIR}") block(SCOPE_FOR VARIABLES) set(CHERYL_BUILD_NATIVE_GLFW OFF) + set(CHERYL_BUILD_DEBUG_TERMINAL_LINUX OFF) set(CHERYL_BUILD_OPENGL OFF) set(CHERYL_BUILD_UI_TGUI OFF) set(CHERYL_BUILD_UI_RMLUI OFF) + set(CHERYL_BUILD_AUDIO_MINIAUDIO OFF) + set(CHERYL_BUILD_WORKER_TOPOLOGY_HWLOC OFF) + set(CHERYL_BUILD_WORKER_TOPOLOGY_JSON OFF) + set(CHERYL_BUILD_DOCS OFF) + set(CHERYL_BUILD_SDK OFF) set(CHERYL_BUILD_TESTS OFF) set(CHERYL_BUILD_CONSUMER_TESTS OFF) set(CHERYL_BUILD_DEMO OFF) @@ -429,7 +527,7 @@ Native OpenGL cases require `CHERYL_NATIVE_GL_TESTS=1` and a usable display: ) ``` -[Native cases](../../projects/modules/graphics/opengl/tests/acceptance/src/native-opengl.cpp) +[Native cases](../../../projects/modules/graphics/opengl/tests/acceptance/src/native-opengl.cpp) cover resource retirement, context restoration, retained-frame reload, failed construction/reflection cleanup, texture row conventions, timing/backpressure and composed runtime failure cleanup. Include the replacement-poll/platform-drain @@ -445,7 +543,7 @@ If current demo evidence is unavailable, run finite sequential/concurrent sessio with `--max-updates`; interactive checks are in [native-desktop-checks.md](native-desktop-checks.md). -[Real-font allocation checks](../../projects/engine/tests/all-tests/src/resources/fonts.cpp) +[Real-font allocation checks](../../../projects/engine/tests/all-tests/src/resources/fonts.cpp) need `CHERYL_STB_ALLOCATION_TTF` and `CHERYL_STB_ALLOCATION_CFF` to name suitable TrueType and CFF OpenType faces, each with a glyph wider than 64 pixels at size 128. DejaVuSans.ttf and NimbusSans-Regular.otf are example fixtures. Export both paths @@ -454,7 +552,7 @@ combined runner. These cases sweep bake scratch allocation failures and nested scopes, requiring released scratch and no provider upload on failure. Missing fixture paths skip; unusable supplied paths fail. -[Linux worker checks](../../projects/engine/tests/all-tests/src/core/worker-pool.cpp) +[Linux worker checks](../../../projects/engine/tests/all-tests/src/core/worker-pool.cpp) exercise inherited-mask discovery/restoration and native kernel rejection. They can skip when required CPUs/capabilities are unavailable; controlled policy/thread-start faults provide different coverage. Identify configuration, opt-ins and skips when @@ -502,5 +600,5 @@ Sanitizers supplement selected checks; TSan requires a configuration without the Debug ASan/UBSan combination and cannot be inferred from those runs. Keep source completion distinct from executable acceptance and unavailable-host -coverage. Unresolved work remains in the [planning catalogue](../planning/README.md); +coverage. Unresolved work remains in the [planning catalogue](../../planning/README.md); this guide is a reusable procedure, not an execution journal. diff --git a/docs/site/development/building.md b/docs/site/development/building.md new file mode 100644 index 00000000..33e84799 --- /dev/null +++ b/docs/site/development/building.md @@ -0,0 +1,471 @@ +(configuring-and-building-cheryl)= +# Build engine and SDKs + +Use CMake 3.28 or newer, Ninja and a C++23 compiler/standard library with +`std::format`. Initialize the pinned submodules as described in +[setup](../../../README.md#setup). Build-tree consumers use the same dependencies; +link Cheryl targets instead of copying include and link settings. Prebuilt consumers +use the [SDK import targets](consuming-engine.md#prebuilt-development-sdks). + +## Choose the artifact + +Configuration selects available targets; an explicit build, install or packaging +command produces the artifact you need. A library build leaves headers in their +source directories. SDK installation collects them into one redistributable +`include/` tree alongside the compiled libraries and CMake import targets. + +For SDK production, building, installing and archiving are separate operations: + +```mermaid +flowchart TB + accTitle: From configured targets to SDK artifacts + accDescr: Configuration selects targets. Building cheryl-sdk produces build-tree libraries; installing the CherylSDK component collects an SDK directory, while package.py stages an archive with identity and checksums. + configure["Configure SDK targets"] --> build["Build cheryl-sdk"] + build --> libraries["Build-tree libraries"] + libraries -->|install component| prefix["SDK directory"] + libraries -->|package.py| archive["SDK archive and identity"] +``` + +| Artifact | Trigger | Result | +| --- | --- | --- | +| HTML manual and C++ reference | Follow [Documentation tooling](documentation.md) for direct generation or the CMake documentation targets. | `docs/site/generation/generated/html/index.html` and linked pages. | +| Engine library | Build `cengine`; see [engine-only configuration](#engine-only-configuration). | Versioned Engine library in the build root, plus required dependency artifacts; see [library linkage](#library-linkage). | +| Selected SDK libraries | Enable `CHERYL_BUILD_SDK`, then build `cheryl-sdk`. | Engine, selected modules/support and their owned dependencies in the build tree. | +| Redistributable SDK directory | Build `cheryl-sdk`, then install the `CherylSDK` component. | One prefix containing `include/`, libraries, CMake package files and licenses. | +| SDK archive with manifest/checksum | Run `.github/ci/package.py` after building `cheryl-sdk`. | Linux `.tar.gz` or Windows `.zip`, `SDK-MANIFEST.json` and `SHA256SUMS`. | + +The [SDK commands](#development-sdk-builds) below cover building, installing and +archiving separately. Source consumers do not need to produce an SDK first; linking +`Cheryl::Cheryl` supplies the configured assembly's public include directories and +dependencies for ``. Leave `CHERYL_BUILD_SDK=OFF` when building an +application from source unless you also want to produce an SDK. This interface +target links the existing libraries; there is no additional combined library +archive. See +[Source and SDK integration](consuming-engine.md) for source and prebuilt integration. + +### Build documentation + +[Documentation tooling](documentation.md) owns generation commands, prerequisites, +tool selection and output options. Its [CMake entry points](documentation.md#cmake-entry-points) +provide `docs` for incremental generation and `docs-clean` for full regeneration +without compiling the engine. Enable `CHERYL_BUILD_DOCS` to expose these targets +in an engine build, or use the documentation-only project to avoid engine +configuration and dependencies. + +## Configuration options + +Pass settings as `-DNAME=value`. These defaults apply to a fresh root configuration; +existing build directories and IDE profiles retain their cache. All Cheryl-owned +option definitions and defaults live in +[`cmake/CherylOptions.cmake`](../../../cmake/CherylOptions.cmake). Component and +standalone entry points select which options to declare; optional backend and +dependency-source settings may appear only when their component is selected. + +| Option | Default | Effect | +| --- | --- | --- | +| `CHERYL_BUILD_STARTUP` | `ON` | Selects the public application-bootstrap module. | +| `CHERYL_STARTUP_OPENGL` | Follows selected OpenGL availability | Includes private configured OpenGL integration in Startup; does not create another startup binary. | +| `CHERYL_BUILD_WORKERS` | `ON` | Makes the optional worker configuration/topology module available when a feature is selected. | +| `CHERYL_BUILD_NATIVE_GLFW` | `ON` | Selects GLFW display/windows and optional input. | +| `CHERYL_BUILD_NATIVE_SDL3` | `OFF` | Adds the source-only [SDL3 scaffold](modules.md#sdl3-scaffold); no runtime adapter or SDK component. | +| `CHERYL_BUILD_DEBUG_TERMINAL_LINUX` | Single-configuration Linux GNU/Clang with `CMAKE_BUILD_TYPE=Debug`: `ON`; otherwise: `OFF` | Includes the optional Linux output terminal in consuming applications/test runners. Explicit `ON` includes it in every configuration built from that directory. | +| `CHERYL_DEBUG_TERMINAL_EMULATOR` | `auto` | Linux module launcher selection: `auto`, `konsole` or `xterm`. | +| `CHERYL_BUILD_OPENGL` | `ON` | Selects the complete OpenGL backend; requires Native GLFW. | +| `CHERYL_BUILD_UI_TGUI` | `ON` | Selects TGUI independently of platform/graphics. | +| `CHERYL_BUILD_UI_RMLUI` | `ON` | Selects RmlUi independently; can coexist with TGUI. | +| `CHERYL_BUILD_AUDIO_MINIAUDIO` | `ON` | Selects miniaudio decoding, output and streaming. | +| `CHERYL_BUILD_DEMO` | `OFF` | Adds `demo` with Native GLFW, OpenGL and native input selected. | +| `CHERYL_BUILD_FIRST_GAME` | `OFF` | Adds the maintained `first-game` walkthrough application with Native GLFW, OpenGL and native input selected. | +| `CHERYL_BUILD_TUTORIAL_GAME` | `OFF` | Adds `tutorial-game` with reusable controllers, Characters, player join/leave, patrol and a TGUI HUD; requires Startup/OpenGL, native input and TGUI. | +| `CHERYL_BUILD_DOCS` | `ON` | Adds the optional `docs-tools`, `docs` and `docs-clean` targets; tools are selected when these targets run. | +| `CHERYL_BUILD_TESTS` | `OFF` | Adds focused tests and explicitly buildable acceptance/aggregate runners. | +| `CHERYL_BUILD_ALL_TESTS` | `OFF` | Includes aggregates in the default build and CTest; requires tests. | +| `CHERYL_BUILD_ACCEPTANCE_TESTS` | `OFF` | Includes acceptance runners in the default build; requires tests. | +| `CHERYL_BUILD_CONSUMER_TESTS` | `OFF` | Adds selected owners' consumers and first-include probes independently of GoogleTest. | +| `CHERYL_BUILD_WORKER_TOPOLOGY_HWLOC` | `ON` | Selects optional runtime topology discovery and thread binding through hwloc. | +| `CHERYL_BUILD_WORKER_TOPOLOGY_JSON` | `ON` | Selects the versioned worker settings loader independently of hwloc. | +| `CHERYL_BUILD_SDK` | `ON` | Adds the `cheryl-sdk` build target and `CherylSDK` install/export component for selected libraries, headers and dependencies. | +| `CHERYL_BUILD_SHARED_LIBS` | `ON` | Builds Engine, Startup and selected compiled modules as separate shared libraries; external dependency linkage follows the per-library policy below. | +| `CHERYL_NATIVE_INPUT` | `ON` | Includes Gainput-backed input in Native GLFW. | +| `CHERYL_NATIVE_NULL_PLATFORM` | `OFF` | Configures an owned GLFW without X11/Wayland. Disable native input to omit Gainput/X11. | +| `CHERYL_LOG_PROFILE` | `auto` | Selects `developer`, `support` or `release`; `auto` follows the build configuration. | +| `CHERYL_LOG_COMPILED_MASK` | Empty | Overrides compiled severity bits (`0`–`63`); see [logging policy](../runtime/logging.md#compile-policy). | +| `WARN` | `OFF` | Adds `-Wall`. | + +A supplied GLFW target keeps its host's settings. Null-platform OpenGL still needs +OpenGL development/link libraries and does not supply the windowed demo. +Cheryl disables documentation generation for its owned GLFW dependency, so ordinary +engine configuration does not probe Doxygen for GLFW's manual. +Standalone modules default their local `CHERYL_BUILD_TESTS` to `OFF`; the +[module guide](modules.md#standalone-modules) covers their composition. + +### Build configurations + +Keep different configurations in separate directories. This block builds Debug; +substitute `RelWithDebInfo`/`build/support` or `Release`/`build/release` as needed: + +```sh +( + set -e + cd "$(git rev-parse --show-toplevel)" + cmake -S . -B build/debug -G Ninja -DCMAKE_BUILD_TYPE=Debug \ + -DCHERYL_BUILD_DEMO=ON + cmake --build build/debug --target demo --parallel +) +``` + +This Linux example uses installed shared development packages. Windows uses the +prepared dependency arguments below. Non-MSVC Debug enables AddressSanitizer and +UndefinedBehaviorSanitizer. Automatic +logging selects developer in Debug, support in RelWithDebInfo and release otherwise. +All translation units sharing Cheryl headers must use the same compiled logging +definitions; runtime levels cannot restore stripped severities. + +Executables and archives use the configured build root. Windows adds `.exe` to +executables. Multi-configuration generators require `--config Release` for builds +and `-C Release` for CTest, with executables normally in `Release/`. +Target and artifact declarations are centralized in +[CherylTargets.cmake](../../../cmake/CherylTargets.cmake) and +[CherylOutputs.cmake](../../../cmake/CherylOutputs.cmake). + +### Library linkage + +Cheryl Engine, Startup and compiled modules default to separate shared libraries. Set +`-DCHERYL_BUILD_SHARED_LIBS=OFF` during configuration for static libraries; +the same public CMake targets work with either selection. `BUILD_SHARED_LIBS` alone +does not select Cheryl's linkage. Keep static/shared builds and SDK prefixes separate. +External libraries keep the same linkage in ordinary builds and SDK builds, +including when Cheryl itself is static; see the per-library policy below. +Private support contributes to its owner rather than creating a public runtime +binary. The Linux terminal's `main` wrapper and argument/startup attachment remain +static; its viewer is an executable. + +Artifact names use the component versions in +[CherylVersions.cmake](../../../cmake/CherylVersions.cmake). For Engine 4.0.0: + +| Platform | Static | Shared | +| --- | --- | --- | +| Linux | `libcheryl-engine-4.0.0.a` | `libcheryl-engine.so.4.0.0`, with SONAME/linker symlinks. | +| Windows/MSVC | `cheryl-engine-4.0.0.lib` | `cheryl-engine-4.0.0.dll` and its `.lib` import library. | + +Link CMake targets to obtain the correct artifact and import/export definitions. +Shared applications also need the matching libraries at runtime; see +[SDK runtime paths](consuming-engine.md#prebuilt-development-sdks). + +### Engine-only configuration + +This omits GLFW, Gainput, OpenGL, GLAD, UI toolkits and miniaudio. Engine still needs +its text dependencies: + +```sh +( + set -e + cd "$(git rev-parse --show-toplevel)" + cmake -S . -B build/engine-only -G Ninja -DCMAKE_BUILD_TYPE=Release \ + -DCHERYL_BUILD_STARTUP=OFF -DCHERYL_STARTUP_OPENGL=OFF -DCHERYL_BUILD_WORKERS=OFF \ + -DCHERYL_BUILD_NATIVE_GLFW=OFF -DCHERYL_BUILD_OPENGL=OFF \ + -DCHERYL_BUILD_DEBUG_TERMINAL_LINUX=OFF \ + -DCHERYL_BUILD_UI_TGUI=OFF -DCHERYL_BUILD_UI_RMLUI=OFF \ + -DCHERYL_BUILD_AUDIO_MINIAUDIO=OFF \ + -DCHERYL_BUILD_WORKER_TOPOLOGY_HWLOC=OFF -DCHERYL_BUILD_WORKER_TOPOLOGY_JSON=OFF \ + -DCHERYL_BUILD_DEMO=OFF -DCHERYL_BUILD_TESTS=OFF \ + -DCHERYL_BUILD_DOCS=OFF -DCHERYL_BUILD_SDK=OFF + cmake --build build/engine-only --target cengine --parallel +) +``` + +## Dependency and toolchain settings + +Dependency linkage is selected per library, independently of SDK installation and +`CHERYL_BUILD_SHARED_LIBS`. Supplied targets retain their host settings and must +satisfy the same policy; incompatible targets fail configuration. + +| Library | Linkage | Ownership and deployment | +| --- | --- | --- | +| FreeType, HarfBuzz | Shared | Private Engine implementation. Source builds use Linux development packages or prepared Windows DLLs/import libraries; both SDK linkages package the selected native runtimes/imports. | +| ICU | Shared; system-provided on Windows | Linux source builds use `uc`, `i18n` and `data`; SDKs package those selected libraries. Windows uses the OS C API from `icu.dll`; Windows 10 version 1903 or later and a Windows SDK containing `icu.h`/`icu.lib` are required. | +| GLFW | Shared | Native GLFW owns its integration; the owned source build supplies one shared GLFW runtime. | +| HIDAPI | Shared when HID is enabled | Reuse a compatible package or build the pinned source. Gainput remains embedded in Native GLFW. | +| TGUI, RmlUi Core | Shared | Reviewed custom builds expose native authoring APIs. Private callbacks reach Engine services through their Cheryl adapters; the toolkit libraries do not link Engine. | +| hwloc 2.5+ (2.x) | Shared when selected | Private Workers implementation; disable `CHERYL_BUILD_WORKER_TOPOLOGY_HWLOC` to omit discovery and linkage. | +| OpenGL and native platform libraries | System shared | Use the host's graphics driver and OS libraries. Generated GLAD is embedded in the Cheryl OpenGL owner. | +| spdlog, Gainput, miniaudio, GLAD, stb | Embedded | Each compiled implementation has one Cheryl owner; shared owners require position-independent archive inputs. | +| Backward support | Embedded headers/bootstrap, selected shared diagnostics | Engine owns crash initialization. SDKs package selected non-system Linux diagnostic/compression dependencies; Windows dbghelp/psapi remain OS requirements. | +| GLM, CTTI, CLI11 | Headers/templates | Public target usage requirements remain explicit; CLI11 belongs to Startup. JSON stays private to its consuming implementations. | + +Microsoft documents the [Windows ICU C API and combined 1903+ runtime](https://learn.microsoft.com/en-us/windows/win32/intl/international-components-for-unicode--icu-). +Cheryl uses that C API directly; the OS supplies `icu.dll` and the Windows SDK +supplies `icu.h` and `icu.lib`. + +Linux development can configure directly against installed shared development +packages; no vcpkg toolchain is required. Windows does not provide FreeType, +HarfBuzz, GLFW, HIDAPI, UI toolkits or hwloc as OS libraries. Prepare the external +font libraries and optional hwloc/HIDAPI with the helper below. The same helper +also provides an optional pinned Linux dependency prefix: + +```sh +( + set -e + cd "$(git rev-parse --show-toplevel)" + python3 tools/dependencies/prepare.py --native \ + --vcpkg-root "$PWD/build/tools/vcpkg" --installed-dir "$PWD/build/dependencies" +) +``` + +Use `--without-hwloc` when omitting that feature. Omit `--native` when the selected +native input does not need HIDAPI. `--jobs` is optional; user-run preparation +otherwise uses normal available parallelism. The helper checks the pinned vcpkg +revision without rewriting a supplied checkout and records the dependency +identity in its installed root. It prints the selected shared prefix and toolchain; +use those with CMake. This optional prefix example selects Linux x64: + +```sh +( + set -e + cd "$(git rev-parse --show-toplevel)" + cmake -S . -B build/release -G Ninja -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_TOOLCHAIN_FILE="$PWD/build/tools/vcpkg/scripts/buildsystems/vcpkg.cmake" \ + -DVCPKG_INSTALLED_DIR="$PWD/build/dependencies" -DVCPKG_TARGET_TRIPLET=x64-linux-dynamic \ + -DVCPKG_MANIFEST_DIR="$PWD/.github/ci" -DVCPKG_MANIFEST_INSTALL=OFF \ + -DCMAKE_PROJECT_INCLUDE="$PWD/tools/dependencies/prepared.cmake" +) +``` + +Run that configuration after preparation. The toolchain +resolves the prepared shared font/layout packages; +`prepared.cmake` supplies prepared hwloc and Windows HID targets when selected. +Existing compatible targets take precedence. For Windows x64, replace the triplet +with `x64-windows` and add +`-DCMAKE_MSVC_RUNTIME_LIBRARY='MultiThreaded$<$:Debug>DLL'`. +Use a matching prepared target triplet for other architectures and cross builds. + +| Setting | Purpose | +| --- | --- | +| `GLFW_BUILD_WAYLAND=OFF` | Uses bundled GLFW's X11 backend on Linux without Wayland development tools. | +| `GAINPUT_ENABLE_HID=OFF` | Omits Gainput's HID support and HIDAPI discovery/fetch on Linux; this is the Linux default. Windows forces that backend and uses shared HIDAPI. Use the [current Linux joystick path](../modules/native-glfw.md#controller-backend-limits) while HID integration remains unresolved. | +| `Python_EXECUTABLE=/path/to/python` | Selects GLAD's interpreter; Jinja2 must be installed in it. | +| `CMAKE_POLICY_VERSION_MINIMUM=3.5` | Allows pinned legacy dependency files to configure with CMake 4 when needed. | +| `CHERYL_REPOSITORY_ROOT=/path/to/cheryl-engine` | Explicitly selects the checkout providing shared helpers and Engine bootstrapping for standalone modules/consumers; entry points otherwise locate their enclosing checkout. | +| `CHERYL_NATIVE_GLFW_SOURCE=/path/to/native-glfw` | Supplies Native GLFW to standalone OpenGL when its target is absent. | +| `CHERYL_TGUI_SOURCE`, `TGUI_DIR` | Selects a TGUI 1.13.0 source tree or verified shared package with custom Engine font/image services and the private decoder ABI. | +| `CHERYL_RMLUI_SOURCE`, `RmlUi_DIR` | Selects a RmlUi 6.3 source tree or verified shared package using Engine font services, the private rasterizer ABI and the required [placeholder correction](../modules/rmlui.md#placeholder-dependency-contract). | +| `CHERYL_MINIAUDIO_SOURCE` | Overrides pinned miniaudio source; a supplied miniaudio target takes precedence. | +| `CHERYL_RMLUI_TEST_FONT=/path/to/font.ttf` | Supplies a real font fixture when the selected SDK's sample font is unavailable. | +| `CHERYL_HWLOC_TARGET=existing_target` | Supplies a compatible hwloc 2.5+ (2.x) target when worker discovery/binding is selected; otherwise discovery checks supported config targets and pkg-config. | + +Modules reuse compatible supplied dependency targets where supported. Their owner +guides document selection order and feature requirements. Owned source dependencies +are pinned in [.gitmodules](../../../.gitmodules); initialize the recorded revisions. +HID-enabled selection first reuses a target or installed package; Linux also +checks pkg-config, preferring hidraw unless configured otherwise. If none is +available, configuration fetches HIDAPI 0.15.0 and needs network access or a +prepared FetchContent cache. UI dependency selection performs no download. + +The selected Startup module reuses `CLI11::CLI11` or adds pinned `extern/cli11`. +Its private OpenGL integration is selected by `CHERYL_STARTUP_OPENGL`; +`Cheryl::OpenGL::Startup` remains a compatibility interface over the same binary. +Plain Engine/OpenGL targets do not acquire CLI11 usage requirements. See +[application startup](consuming-engine.md#command-line-startup). + +The terminal module adds a separate optional startup target and linker entry hook; +plain Engine links neither. Its default is calculated when the option first enters +the CMake cache; changing `CMAKE_BUILD_TYPE` later retains the cached selection. +Set `CHERYL_BUILD_DEBUG_TERMINAL_LINUX=ON` or `OFF` explicitly when changing it. +Multi-configuration generators default to `OFF`; explicit `ON` includes the terminal +in every configuration. Standalone terminal-module consumers also set this option +explicitly. + +Included Debug builds display automatically; other configurations require +`--debug-terminal`. `--no-debug-terminal` disables display. Display requires a +desktop and supported emulator; missing prerequisites fall back to inherited output. +Headless Debug and test discovery create no viewer. See the +[Linux terminal guide](../modules/debug-terminal-linux.md) +for output/report separation and lifetime policy. `ON` is a build inclusion option, +not a request to display a Release window automatically. + +## System dependencies + +Install development headers and link libraries as well as runtime libraries. +Package names vary by operating system. + +| Requirement | Selection | Notes | +| --- | --- | --- | +| Threads | Engine | Discovered through `Threads::Threads`. | +| Python 3.11+ | Documentation targets (enabled by default) | Required during configuration; Doxygen/Sphinx are selected when documentation targets run. | +| Shared hwloc 2.5+ (2.x) | Workers hwloc feature (selected by default) | Runtime CPU/core/cache discovery and binding; omitted configurations do not discover or link it. | +| Shared FreeType and HarfBuzz | Engine | Font inspection/rasterization and shaping; required with UI disabled too. | +| ICU | Engine | Linux shared `uc`/`i18n`/`data` development packages; Windows 10 1903+ supplies the runtime and the Windows SDK supplies the C API headers/import library. | +| X11 and extensions | GLFW X11 / native Linux input | GLFW checks XRandR, Xinerama, XKB, Xcursor, XInput and X Shape headers. Gainput input also needs X11. | +| Wayland, xkbcommon, `wayland-scanner` | Bundled GLFW Wayland | Linux GLFW enables X11 and Wayland by default. Disable Wayland for an X11-only build. | +| OpenGL | OpenGL module | Headers/link libraries; native execution also needs a usable driver/display. | +| Python and Jinja2 | GLAD generation | Jinja2 must be available in CMake's selected interpreter. | +| Engine font/image services | TGUI / RmlUi | Toolkit adapters share Engine implementation instead of embedding another font/decoder copy. | +| libudev / libusb / Iconv | Linux hidapi | Source requirements depend on the selected HID backend. SDKs package selected non-system runtimes; libudev remains a host requirement. | +| Konsole or xterm | Linux output terminal at runtime | A desktop is needed for display; neither is an Engine build dependency. | +| libdw, libbfd, or libdwarf/libelf | Backward, optional | Improve trace source/symbol resolution. Selected non-system diagnostic libraries and their runtime closure are packaged in SDKs. | +| `patchelf` | Linux SDK installation/packaging | Gives each packaged native shared library an origin-relative runtime path; an ordinary source-library build does not require it. | + +Engine source dependencies include GLM, CTTI, spdlog, Backward, STB and JSON. +Integration SDKs belong to their modules; GoogleTest is test-only. See +[consumer usage requirements](consuming-engine.md#dependencies-and-headers) and the +[module index](../../../projects/modules/README.md) for dependency ownership. + +## Troubleshooting + +| Symptom | Next step | +| --- | --- | +| Selected dependency directory is empty | Initialize the pinned submodules. | +| A selected target is missing in CLion | Change the cached option and reload CMake. | +| An old profile requires static FreeType/HarfBuzz | Reload with the current dependency policy and use a fresh build directory for a profile retaining a static toolchain/triplet. Ordinary Linux shared packages are supported. | +| Python cannot import `jinja2` | Install it in the selected interpreter or set `Python_EXECUTABLE`. | +| GLFW cannot find Wayland tools/libraries | Install the requirements or set `GLFW_BUILD_WAYLAND=OFF`. | +| CMake 4 rejects an old dependency policy | Set `CMAKE_POLICY_VERSION_MINIMUM=3.5`. | +| Supplied RmlUi Core lacks the placeholder-correction declaration | Use Cheryl-owned source or declare the correction on the verified dependency's Core target according to the [dependency contract](../modules/rmlui.md#placeholder-dependency-contract). | +| Standard C++ headers cannot find `math.h` or `stdlib.h` | Inspect stale compiler discovery before editing includes; see [recovery](#standard-headers-in-an-existing-build). | + +Successful configuration/build does not establish native platform coverage. The +[Testing and validation](testing.md) and [Component and SDK checks](architecture-validation.md) +describe the relevant checks and prerequisites. + +Documentation has separate [tooling](documentation.md) that +does not configure or compile the engine. The docs-only CMake project exposes +the same targets without graphics or native development dependencies. + +### Standard headers in an existing build + +An existing CMake/CLion profile can retain failed C++ compiler ABI discovery. Check +whether generated `CMakeCXXCompiler.cmake` records +`CMAKE_CXX_ABI_COMPILED FALSE` and empty implicit include directories. This can turn +the X11 dependency's `/usr/include` into an explicit `-isystem` argument; +GCC can then fail to resolve `math.h` or `stdlib.h` through `#include_next` in +C++ standard headers. + +Refresh compiler discovery and regenerate the build files while preserving the +profile/toolchain settings. CMake normally filters its +[detected implicit include directories](https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_IMPLICIT_INCLUDE_DIRECTORIES.html) +from explicit compiler arguments. Reload CMake in CLion after repairing an existing +build externally. Preserve intended profile/toolchain/cache settings when resetting +generated discovery metadata or creating a fresh build directory. + +## Optional worker support + +`CHERYL_BUILD_WORKERS=ON` with a selected feature adds `module_workers` +(`Cheryl::Workers`). `CHERYL_BUILD_WORKER_TOPOLOGY_HWLOC` and +`CHERYL_BUILD_WORKER_TOPOLOGY_JSON` independently select its capabilities; both +default to `ON`. The capability-specific legacy target names refer to that same +implementation. A plain Engine consumer can omit Workers. Selecting both features +also registers the explicitly buildable `example_worker_topology` shared-pool composition example. +The [worker guide](../runtime/worker-execution.md) owns provider lifetime, placement +policies, capacity and configuration semantics. + +hwloc selection reuses an explicit or compatible existing target, then an installed +config package or pkg-config SDK. Missing/incompatible selection fails clearly; +there is no implicit SDK download/install. Cross builds require a supplied target +SDK or target-rooted pkg-config settings (`PKG_CONFIG_LIBDIR` and +`PKG_CONFIG_SYSROOT_DIR`); do not resolve host libraries accidentally. Discovery +occurs when the application creates its provider, never through `lstopo` at configure +time. Builds do not bake host CPU/cache layout into artifacts or add `-march=native`. +CPU vendor is not a CMake selection. + +## Development SDK builds + +SDK export is enabled by default and packages the same libraries used in ordinary +development. Select the modules and optional features to distribute with the +normal build options; omitting hwloc, for example, needs no separate SDK mode. +Embedded dependencies remain inside shared Cheryl owners; static Cheryl SDKs +retain their embedded archive closure for final linking. Both linkages package +selected non-system shared runtimes and their transitive runtime dependencies. +These include FreeType, HarfBuzz, Linux ICU, selected hwloc/HIDAPI, GLFW and the +reviewed UI toolkits. Windows also supplies the corresponding native import +libraries; Windows ICU remains an operating-system dependency. + +Linux installation requires `patchelf`: copied native libraries use `$ORIGIN` to +find their packaged neighbors, and SONAME aliases stay within the SDK. Platform +and compiler runtimes remain external; see the +[consumer deployment requirements](consuming-engine.md#prebuilt-development-sdks). +`CHERYL_SDK_NATIVE_PREFIX` can supply dependency license notices; the vcpkg +toolchain derives it automatically. Use a clean checkout with submodules at their +recorded revisions for reproducible archives. The packager records the source +commit and verifies recursive submodule HEADs against its gitlinks. + +Use a single-configuration Ninja build and select the modules/helpers to distribute. +With [shared development packages](#dependency-and-toolchain-settings), this Linux +configuration builds the default modules, including Startup and Workers, +without building the demo or tests: + +```sh +( + set -e + cd "$(git rev-parse --show-toplevel)" + cmake -S . -B build/sdk -G Ninja \ + -DCMAKE_BUILD_TYPE=Release -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ + -DCMAKE_INSTALL_LIBDIR=lib -DCHERYL_BUILD_SDK=ON \ + -DCHERYL_BUILD_DEMO=OFF -DCHERYL_BUILD_TESTS=OFF \ + -DCHERYL_BUILD_DEBUG_TERMINAL_LINUX=OFF \ + -DGAINPUT_ENABLE_HID=OFF -DGLFW_BUILD_WAYLAND=OFF + cmake --build build/sdk --target cheryl-sdk --parallel +) +``` + +`cheryl-sdk` builds the selected libraries and their dependencies. It does not +copy headers, install a directory or create an archive. HID-enabled native input +embeds Gainput and links shared HIDAPI. SDK installation packages selected +non-system HID dependencies, including libusb when required; libudev remains a +host requirement. Static Cheryl SDKs carry the same native imports plus their +embedded archive closure. This packaging support does not change the +[Linux controller limitations](../modules/native-glfw.md#controller-backend-limits). +To collect the libraries and redistributable headers into a local SDK prefix after +that build: + +```sh +( + set -e + cd "$(git rev-parse --show-toplevel)" + cmake --install build/sdk --component CherylSDK --prefix "$PWD/build/sdk-prefix" +) +``` + +The prefix contains `include/`, `lib/`, `lib/cmake/Cheryl/` and `share/licenses/` +with the configuration above. Windows may also install runtime libraries under +`bin/`. The install command does not compile missing libraries; run the build step +first. Consumers use `find_package(Cheryl 4 CONFIG REQUIRED)` with this prefix in +`CMAKE_PREFIX_PATH`; see [SDK consumption](consuming-engine.md#prebuilt-development-sdks). + +For a complete distributable archive, use Python 3.12 or newer: + +```sh +( + set -e + cd "$(git rev-parse --show-toplevel)" + python3 .github/ci/package.py --build-dir build/sdk --output build/packages +) +``` + +`package.py` performs its own temporary install of only the `CherylSDK` component, +creates a Linux `.tar.gz` or Windows `.zip` under `build/packages/`, and writes +`SHA256SUMS` plus `SDK-MANIFEST.json` there. You can skip the direct install command +when you only need an archive. SDK installation records each shared runtime and +whether the SDK supplies it or the platform must supply it. A static Cheryl SDK +still needs the shared native libraries selected by the same per-library policy. +Archive names include the Cheryl version from +[CherylVersions.cmake](../../../cmake/CherylVersions.cmake), platform/distribution, +architecture, compiler/version, configuration and commit ID. +They also include `static` or `shared`; `SDK-MANIFEST.json` records that linkage, +enabled component versions and the exact Engine artifact paths. Linux archives +preserve relative SONAME symlinks within the SDK; broken or escaping links are rejected. + +CI uses Linux distribution development packages and prepares Windows shared native +dependencies with `x64-windows` and the DLL CRT. `CHERYL_SDK_NATIVE_PREFIX` can select +a prepared prefix for license notices during configuration. Debug dependency trees, tools and applications +are omitted. Platform +system libraries remain external. Neither package contains the demo or C/C++ implementation translation +units; public and inline/template headers remain included. The selected libraries +are already compiled. See [SDK consumption](consuming-engine.md#prebuilt-development-sdks) +for ABI, logging and runtime-library requirements. + +## GitHub CI and SDK artifacts + +[CI and SDK collection](ci.md) describes the hosted build matrix, installed-consumer +checks, artifact collection, cache maintenance and Windows coverage limits. Use +this build guide for local configuration and SDK production; use the CI guide to +operate or investigate those workflows. diff --git a/docs/site/development/ci.md b/docs/site/development/ci.md new file mode 100644 index 00000000..6dbf9c74 --- /dev/null +++ b/docs/site/development/ci.md @@ -0,0 +1,225 @@ +(ci-and-sdk-artifacts)= +# CI and SDK collection + +Use the hosted workflows to validate selected compositions, collect prebuilt SDKs, +publish eligible releases and maintain build caches. [Build engine and SDKs](building.md) owns local configuration +and SDK production; [Testing and validation](testing.md) owns runner and fixture choices. + +## GitHub CI and SDK artifacts + +Successful validation, SDK collection and release publication have separate gates: + +```mermaid +flowchart TB + accTitle: Validation artifacts and gated publication + accDescr: Successful main-push validation supplies four platform and linkage SDK candidates. Collection verifies a six-file distribution set; publication requires the additional release eligibility gates. + validation["Validation jobs"] -->|successful main-push gates| candidates["Four SDK candidates"] + candidates -->|verify and collect| collection["Six-file SDK set"] + collection -->|release gates pass| release["Published release"] +``` + +[ci.yml](../../../.github/workflows/ci.yml) validates pushes to `develop`/`main`, pull +requests targeting either branch and manual dispatches. Each job checks out recorded +recursive submodules. Actions use commit pins; Windows uses the manifest's pinned +vcpkg baseline. Builds use Ninja and available CPU parallelism. + +| Job | Host/compiler/configuration | Selection | +| --- | --- | --- | +| `linux-gcc-hwloc` | Ubuntu 24.04 x64, GCC 14 Release | Headless Engine, hwloc and JSON. | +| `linux-gcc-neutral` | Ubuntu 24.04 x64, GCC 14 Release | Headless Engine and JSON, hwloc omitted. | +| `linux-clang-debug` | Ubuntu 24.04 x64, Clang 18 Debug | Headless Engine, hwloc/JSON, ASan/UBSan. | +| `windows-msvc-hwloc` | Windows 2025 x64, MSVC RelWithDebInfo | Headless Engine and hwloc/JSON with vcpkg native libraries. | +| `linux-modules` | Ubuntu 24.04 x64, GCC 14 Release | Native GLFW/input, OpenGL, TGUI, RmlUi, miniaudio, hwloc/JSON and SDK. | +| `windows-modules` | Windows 2025 x64, MSVC RelWithDebInfo | The same selected modules/helpers and SDK, with bundled native dependencies. | +| `linux-modules-shared` | Ubuntu 24.04 x64, GCC 14 Release | The modules/SDK composition with shared Cheryl libraries and owned dependencies. | +| `windows-modules-shared` | Windows 2025 x64, MSVC RelWithDebInfo | The shared modules/SDK composition, including DLL runtime discovery. | + +The `windows-dependencies` job prepares one pinned vcpkg installation for the +Windows jobs and passes it through a one-day workflow artifact. The downstream +headless, static-module and shared-module jobs consume that same preparation. + +[run.py](../../../.github/ci/run.py) configures explicit owners/options/build targets, +runs each focused owner prefix once and executes independent consumers. CI runs +its build and test phases separately so compilation and execution have distinct +results. Modules +jobs compile the demo for composition coverage but never launch or package it. +Linux jobs provide TrueType/CFF allocation fixtures. Missing fixtures and +platform-specific skips are reported as unaccepted coverage. Native OpenGL, native +hwloc binding acceptance, actual audio devices, desktop/controller QA and the +Debug terminal remain separate from this focused CI selection. + +Modules jobs package and verify an extracted SDK on every validation run. +[verify_sdk.py](../../../.github/ci/verify_sdk.py) checks the checksum and absence of +producer paths, then builds independent consumers/header probes using only installed +Cheryl libraries. Test fixture sources/fonts come from the checkout; Cheryl and +module implementation sources are not rebuilt. Use a fresh consumer build/extraction +directory for each verification. See +[repeating SDK checks](architecture-validation.md#installed-sdk-consumers). + +Successful main-push modules jobs retain four static/shared Linux/Windows SDK +candidates for three days. After the headless, Linux-module and Windows jobs +succeed, the `sdk` job verifies those candidates and assembles this set: + +| Output | Contents | +| --- | --- | +| `cheryl-SDK-v-linux.tar.gz` | Shared Linux SDK. | +| `cheryl-SDK-v-windows.zip` | Shared Windows SDK. | +| `cheryl-SDK-v-static.tar.gz` | Static Linux and Windows SDKs under `linux/` and `windows/`, with a platform-selecting CMake entry point. | +| `cheryl-src-v.tar.gz` | Source and recorded recursive submodules, with `SOURCE-MANIFEST.json`. | +| `SDK-MANIFESTS.json` | Maps the four SDK candidates to their archive, prefix and original manifest, and records source identity. | +| `SHA256SUMS` | Checksums for the four archives and aggregate manifest. | + +The six files are retained as `cheryl-development-sdks-` for 90 days. +Test/configuration reports are retained for 14 days. There is no demo package. +SDK collection is skipped on PR, `develop` and manual runs. Its first hosted +main-push acceptance remains tracked by +[TR20](../../testing-requests.md#tr20-automated-main-push-sdk-collection); the +implemented workflow alone does not establish that hosted acceptance. + +### Release publication + +The separate `release` job consumes the checked six-file artifact. Publication is +enabled only for a `main` push in `cppcooper/cheryl-engine`, after SDK assembly. +[release.py](../../../.github/ci/release.py) requires all of these gates: + +1. The project version is explicitly `x.y.0` in CMake. Exactly one merged PR from + this repository's `develop` to `main` has title `Release x.y.0` matching it. +2. The checked revision is that PR's ordinary two-parent merge commit; its second + parent is the PR's develop head. Squash/rebase merges do not qualify. +3. The revision remains current `main`, and the latest documentation workflow + attempt for the same revision completes **Documentation checks** successfully. + The publisher waits up to ten minutes; Read the Docs publication is a separate + workflow and is not this gate. +4. All archives, source/submodule identities, manifests and checksums validate. + Locally marked validation packages are not publishable release inputs. + +The publisher creates `v` only when absent or already pointing to the +same revision. It creates a draft stable release with generated notes, uploads +and verifies the complete asset set, rechecks `main`, then publishes. An already +published exact match is unchanged. Conflicting tags or assets fail rather than +being moved or overwritten. Workflow concurrency serializes release publication. +These are current automation rules; a successful local assembly does not prove +GitHub permissions, artifact collection or hosted publication. + +### Actions cache retention + +Compiler and dependency caches speed up subsequent CI runs. They are separate +from the SDK and diagnostic artifacts above, and from each other: + +| Cache | Key family | Contents and ownership | +| --- | --- | --- | +| Compiler results | `ccache-v3` | Compiler-family main, develop and PR layers, described below. | +| X: build dependencies | `build-deps-v2` | Separate main/develop submodule Git data and Windows vcpkg package archives/downloads. PRs read their base branch's X. | +| Y: Actions environment | `actions-env-cache-v2` | Shared tools, Linux package downloads, Python wheels/downloads and prepared documentation tools, published by main. | + +Compiler caches are pooled by compatible compiler family: `linux-gcc14` shares +headless/module/static/shared results, `linux-clang18` owns the Clang configuration, +and `windows-msvc` shares the Windows profiles. OS and architecture also identify +the family. Each family has these logical layers: + +| Build | Readable layers | Published destination | +| --- | --- | --- | +| `main` | Main baseline A | A | +| `develop` | A + develop additions B | B | +| PR targeting `develop` | A + B + PR additions C | C | +| PR targeting `main` | A + C | C | + +[restore-compiler-cache](../../../.github/actions/restore-compiler-cache/action.yml) +restores available layers as separate read-only ccache storage. +[compiler_cache.py](../../../.github/ci/compiler_cache.py) gives each build job a +fresh writable delta, checks its restored current layer and then its parents, +and keeps statistics outside archived data. Parent hits stay in their original +layers. Each non-cancelled initialized worker can retain a validated, deduplicated +delta as a one-day artifact even if a later build or test step fails; retaining +cache results does not accept that failed job. + +The separate `compiler-cache` job restores the latest family layers and merges +all available worker deltas into the current layer. It validates incoming +archives before merging, preserves distinct lookup metadata, removes compiled +results already present in parents and trims the current layer to 1 GiB. Only a +changed layer is saved, using a new run-specific key because Actions caches are +immutable. An empty-layer marker records an initialized empty result. This +single publisher avoids competing matrix jobs replacing one another's additions. +When a family has no current v3 snapshot, it can import the earlier per-profile +v2 layers once; the initialized v3 snapshot supersedes that migration path. + +This hierarchy requires `main` to be GitHub's default branch. A PR can read its +own cache, its base branch's cache and the default branch's cache; a PR targeting +`main` cannot read `develop`'s cache. Manual feature-branch builds use their own +writable layer and the main baseline. These are +[GitHub cache scope restrictions](https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching#restrictions-for-accessing-a-cache). +Missing parents cause normal cache misses until their builds populate them. + +Cache X's source key hashes `.gitmodules` and the recorded submodule commits. +Updating a submodule selects a new source snapshot automatically; an older X can +seed Git objects before checkout fetches the requested commits. The binary +component follows the vcpkg manifest and runner image, with vcpkg validating each +package's binary compatibility when using a fallback snapshot. Vendored libraries +remain ordinary CMake targets and reuse their compiler results through ccache. +Only main/develop push and manual runs publish X; PRs fetch or build missing +dependencies locally. + +To update Y, change package/tool selections in +[environment.json](../../../.github/ci/environment.json), generator packages in +[requirements-modules.txt](../../../.github/ci/requirements-modules.txt), or the +documentation tool manifests. Bump `cache_version` from `2` to `3` when revising +the Actions environment or forcing a refresh; this changes the family to +`actions-env-cache-v3`. Environment inputs are also hashed so package changes +cannot silently reuse a mismatched key. Y has separate immutable components for +each OS/architecture, runner image and environment composition. Resolved Linux +package versions, Python ABI and tool manifests further identify their payloads; +submodule commits and run IDs do not affect Y. + +Every branch restores Y, while only trusted main push/manual runs publish it. +Develop and PR changes to the environment install normally on a cache miss; +main publishes the updated Y after those changes reach it. Package installers +still run on each fresh runner; cached downloads avoid fetching the same packages +again. Documentation tooling is prepared and cached before document generation, +so a later documentation failure does not discard the prepared environment. + +[cache-maintenance.yml](../../../.github/workflows/cache-maintenance.yml) runs after +the build or documentation workflow finishes and daily at 04:23 UTC. It removes +caches belonging to closed pull requests, keeps the newest mutable compiler and +dependency snapshot per branch, configuration and cache version, and trims older caches +toward an 8 GiB repository budget. Active workflow refs and caches created or +accessed within the last 15 minutes are protected. After removing redundant +snapshots, budget eviction removes ordinary old caches first, then the latest +open-PR compiler/dependency snapshots, then develop snapshots, and finally main +snapshots. Known default-branch tooling caches have the same priority as develop +layers. Each tier uses oldest access first. The newest X source snapshot per +branch and runner receives that branch's priority; older submodule pins remain +ordinary eviction candidates. Old-format compiler snapshots lose their branch +priority once the same branch and compiler family has a v3 replacement. +These priorities preserve useful branch caches where space allows; the budget +can still require evicting a current layer. The budget is a target: concurrent +uploads and protected caches can temporarily keep usage above it. +Cleanup deletes cached build inputs, without deleting SDK artifacts or changing +build results; an evicted cache is regenerated when next needed. + +To inspect or apply cleanup, open **Actions → Maintain Actions caches → Run +workflow** after this workflow has reached the repository's default branch. +Leave **dry_run** enabled to preview cache IDs, keys, refs, sizes and deletion +reasons. Optionally enter a closed PR number in **closed_pr** to restrict the +operation to that PR's caches. Review the run's report, then dispatch again with +**dry_run** disabled to apply the current plan. Automatic runs apply cleanup; +they execute the default branch's script without loading PR code, artifacts or +cached executables. + +GitHub also [expires unused caches and evicts old entries at its storage +limit](https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching#usage-limits-and-eviction-policy). +The maintenance policy removes redundant snapshots before that limit causes +frequent replacement of useful caches. + +### Hosted Windows coverage limits + +The Windows profiles leave these focused cases unaccepted: + +| Cases | Missing prerequisite and next action | +| --- | --- | +| `font_bake.real_nested_allocation_scope`, `font_bake.real_cff_alloc_failure` | The workflow supplies the bundled TrueType face but no CFF OpenType fixture on Windows. Supply `CHERYL_STB_ALLOCATION_CFF` with a suitable face, retain the TrueType fixture for nested scopes, and rerun these cases on hosted Windows. | +| `logging.config_reopen` | The test needs a temporary directory on the checkout's filesystem root so it can use a relative log path. Configure the hosted Windows temporary directory on that drive, then rerun the case. | + +The [font fixture requirements](architecture-validation.md#native-and-fixture-checks) +define suitable faces. A green job does not accept these skipped cases; successful +local MSVC/Wine checks do not replace the missing hosted execution. These limits +are separate from SDK collection and desktop/GPU/device acceptance. diff --git a/docs/development/code-style.md b/docs/site/development/code-style.md similarity index 100% rename from docs/development/code-style.md rename to docs/site/development/code-style.md diff --git a/docs/site/development/consuming-engine.md b/docs/site/development/consuming-engine.md new file mode 100644 index 00000000..34ef3ea2 --- /dev/null +++ b/docs/site/development/consuming-engine.md @@ -0,0 +1,539 @@ +(consuming-the-engine)= +# Source and SDK integration + +Start with [project setup](../getting-started/project-setup.md) to choose source +or SDK integration, then follow the [first-game walkthrough](../getting-started/first-game.md). +Use this guide for public target/header selection, context composition and the +complete source/SDK deployment requirements. The [module catalogue](../modules/index.md) +helps choose implementations; [Integration contracts](../architecture/interfaces-adapters.md) +defines the obligations when implementing a replacement. + +Applications can compose source targets from a checkout or link a prebuilt +[development SDK](#prebuilt-development-sdks). Link `Cheryl::Cheryl` to use the +complete configured assembly through one public include: + +```cpp +#include +``` + +That interface target forwards the usage requirements of Engine, configured modules +and available support libraries; it creates no combined binary. +Its include directories and feature definitions make the matching headers visible +to the compiler and the IDE. The same include works with source and SDK targets. +Individual targets remain available when the application needs a smaller dependency +set; `Cheryl::Engine` is the neutral library backed by build target `cengine`. +Its artifact uses the Engine component version; CMake selects the matching filename. + +## Source checkout + +A relative path to the Cheryl checkout through `add_subdirectory` is supported. +For example, with the root's default module and worker-support selections: + +```cmake +cmake_minimum_required(VERSION 3.28) +project(my_game LANGUAGES CXX) +set(CHERYL_BUILD_TESTS OFF) +set(CHERYL_BUILD_DEMO OFF) +set(CHERYL_BUILD_SDK OFF) +set(CHERYL_BUILD_DEBUG_TERMINAL_LINUX OFF) +add_subdirectory(path/to/cheryl-engine cheryl) +add_executable(my_game main.cpp) +target_link_libraries(my_game PRIVATE Cheryl::Cheryl) +``` + +Replace `path/to/cheryl-engine` with the checkout path relative to your application's +`CMakeLists.txt`, or an absolute path. CMake builds the selected libraries and +propagates their public include paths and dependency settings. Reload the CMake +project in the IDE after changing selections so completion uses those settings. +Install shared development packages on Linux or prepare the Windows dependencies +with the toolchain/helper arguments in +[project setup](../getting-started/project-setup.md#build-your-game-with-a-source-checkout). +Keep `CHERYL_BUILD_SDK=OFF` for source application builds; aggregate headers and IDE +completion do not require SDK export or installation. Enable it only when also +producing a separate SDK. The [SDK build](building.md#development-sdk-builds) +collects headers and compiled libraries into a separate prefix for consumers that +want a prebuilt distribution. + +The source facade also links the configured Linux Debug terminal and its entry +attachment when that module is enabled. The example disables it; use the granular +targets below when you want to choose support and diagnostic behavior individually. + +### Windows source-build runtime + +Shared libraries must be available when the game starts, including with static +Cheryl. Source composition stages selected package DLLs into component and +executable output directories. It also stages each executable's CMake-known +runtime DLLs, so a game in the parent build directory can find Cheryl and its +owned toolkit libraries from a nested `add_subdirectory(... cheryl)` build. +This staging runs independently of SDK installation. + +The staging helper follows imported DLL dependencies, including optional native +package dependencies, and reports missing or conflicting runtime files. OS and +MSVC runtime DLLs remain platform prerequisites. + +Keep the staged DLLs beside the executable when launching from an IDE or copying +the build to another directory. The host needs the matching MSVC runtime and +Windows 10 version 1903 or later; ICU comes from the operating system. CMake +targets for supplied shared libraries must identify their runtime DLL and import +library so staging can locate the runtime artifacts. + +### Granular composition + +Link selected targets individually to control the application's dependency set. +This example selects neutral Engine alone: + +```cmake +set(CHERYL_BUILD_TESTS OFF) +set(CHERYL_BUILD_DEMO OFF) +set(CHERYL_BUILD_NATIVE_GLFW OFF) +set(CHERYL_BUILD_STARTUP OFF) +set(CHERYL_STARTUP_OPENGL OFF) +set(CHERYL_BUILD_WORKERS OFF) +set(CHERYL_BUILD_DEBUG_TERMINAL_LINUX OFF) +set(CHERYL_BUILD_OPENGL OFF) +set(CHERYL_BUILD_UI_TGUI OFF) +set(CHERYL_BUILD_UI_RMLUI OFF) +set(CHERYL_BUILD_AUDIO_MINIAUDIO OFF) +set(CHERYL_BUILD_WORKER_TOPOLOGY_HWLOC OFF) +set(CHERYL_BUILD_WORKER_TOPOLOGY_JSON OFF) +set(CHERYL_BUILD_DOCS OFF) +set(CHERYL_BUILD_SDK OFF) +add_subdirectory(path/to/cheryl-engine cheryl) +add_executable(application main.cpp) +target_link_libraries(application PRIVATE Cheryl::Engine) +``` + +For native graphics, enable Native GLFW, OpenGL and native input, then link +`Cheryl::Engine`, `Cheryl::NativeGLFW` and `Cheryl::OpenGL`. Select and link +`Cheryl::UI::TGUI`, `Cheryl::UI::RmlUi` or `Cheryl::Audio::Miniaudio` when needed. +For command-line startup, select `CHERYL_BUILD_STARTUP` and link `Cheryl::Startup`; +`CHERYL_STARTUP_OPENGL` chooses its private configured native graphics integration. +The compatibility `Cheryl::OpenGL::Startup` target also supplies the native/graphics +public usage requirements. +Consumers obtain C++23, includes, logging policy and SDK requirements through +targets rather than compiling Engine sources or repeating dependency lists. +The [module catalogue](../modules/index.md) describes available implementations. +[Module authoring](modules.md) covers standalone owners and extension rules. + +## Prebuilt development SDKs + +Extract an SDK and select its root through `CMAKE_PREFIX_PATH` or +`Cheryl_DIR=/lib/cmake/Cheryl`. No Cheryl implementation checkout is needed +to consume it. Link the same public aliases as source consumers: + +```cmake +cmake_minimum_required(VERSION 3.28) +project(my_game LANGUAGES CXX) +find_package(Cheryl 4 CONFIG REQUIRED) +add_executable(my_game main.cpp) +target_link_libraries(my_game PRIVATE Cheryl::Cheryl) +# Or link Cheryl::Engine and individual module/support targets for a smaller set. +``` + +For a Linux Release SDK, configure the application's directory as follows: + +```sh +( + set -e + cd /path/to/my_game + cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH=/absolute/path/to/extracted-sdk + cmake --build build --parallel +) +``` + +The SDK contains compiled Engine, Startup and selected module libraries, +their required public dependency headers, the Engine crash bootstrap, public headers, +CMake import/version files and licenses. `.h`/`.hpp` and required `.inl`/`.ipp`/`.tpp` +files remain included. C/C++ implementation translation units, tests, artwork, +applications and the demo are excluded. There is no separate demo package. +Embedded implementations such as miniaudio, Gainput, GLAD and spdlog do not +become application header or runtime-target requirements. GLFW remains a shared +runtime used through Native GLFW. Public header/template +frontends such as CTTI, GLM and CLI11, plus native toolkit authoring headers, +retain their usage requirements. Static SDKs also package the private archive +closure required by final executable linkage. +Its `Cheryl::Cheryl` target selects the components distributed in that SDK; +`` exposes their public interfaces. Installed headers share the +`include/cheryl/` namespace, including the module and support umbrellas described +[below](#dependencies-and-headers). +The [build guide](building.md#development-sdk-builds) describes production; +`SDK-MANIFEST.json` records selected features, source/submodule revisions, OS, +architecture, compiler/version, configuration, logging profile, native versions, +enabled component versions and static/shared linkage. + +Use a package compatible with the target OS/distribution, architecture, compiler, +standard-library ABI and configuration. Windows CI SDKs use MSVC x64 RelWithDebInfo +and the DLL CRT; Linux CI SDKs use Ubuntu 24.04 x64 GCC 14 Release. A locally built +SDK's identity describes that local toolchain, not those hosted configurations. +Imported logging definitions are fixed to the producer's compiled profile/mask; +the application must not select contradictory definitions. Match MSVC runtime and +configuration rather than mixing incompatible Debug/Release C++ binaries. + +Use Engine and modules from the same matched SDK/binary set. Shared builds give +built-in Engine/Native GLFW singletons, cache/provider state and logging state one +owning library. Application-defined `CE::State::Singleton` and generic allocator +template statics can still have separate instances in different DLLs. Share such +application state through an explicit owner. + +Retained object/resource handles keep their captured release contexts; release +through their deleters. For manual destruction, call +`CE::Memory::Objects::Constructor::destroy(...)` in the allocating library, or +retain and use the original construction context. A direct static call from another +DLL can consult a different tracker. Keep the owning libraries loaded while their +objects and deleters remain in use. Plugin hot unloading and arbitrary module-version +mixing are outside this SDK contract. + +| Native dependencies | Consumer requirements | +| --- | --- | +| Linux | Deploy the packaged Cheryl and selected non-system native libraries together. The SDK includes FreeType, HarfBuzz, ICU, selected hwloc/HIDAPI, GLFW/UI libraries and their non-system runtime closure. The host supplies the loader/glibc, compatible C++ runtime, selected graphics/display libraries, audio service clients and libudev when selected. | +| Windows | Deploy the SDK's selected Cheryl, toolkit and external native DLLs, and install the matching MSVC runtime. Native dependencies keep shared linkage with static Cheryl too. Windows 10 version 1903+ supplies ICU; no separate ICU DLL is distributed. | + +Both static and shared SDKs import their packaged native libraries without +rediscovering private FreeType, HarfBuzz, ICU or hwloc development packages. +Both package configurations discover Threads. Static SDKs additionally supply +embedded archives for final linking and discover selected platform link +requirements: OpenGL, X11 for Native GLFW on Linux, and pkg-config/libudev when +the selected HID backend needs it. Shared SDK configurations do not perform +those additional platform package discoveries. These static final-link +development requirements differ from the packaged private dependencies. Packaging does not resolve the +[Linux controller limitations](../modules/native-glfw.md#controller-backend-limits). +The package exposes `Cheryl_RUNTIME_DIRECTORIES` for its packaged shared libraries: +`lib/` on Linux and `bin/` on Windows. This can be nonempty for static Cheryl SDKs. +Use those paths for application runtime setup or deploy the required libraries +beside your executable. Linux shared libraries find their packaged neighbors +through an origin-relative runtime path. SDK import configuration does not copy +DLLs beside the application's executable automatically. +OpenGL still needs the host's graphics runtime. +SDK relocation does not establish compatibility with another ABI or distribution. + +Use installed-target consumers and first-include probes to check each selected +SDK composition, including Startup and native toolkit authoring. The +[CI guide](ci.md) retains hosted coverage limits. +Desktop, GPU and physical-device coverage remain separate. The SDK does not export +the Linux Debug terminal; that module retains its source-composition path. + +## Context assembly + +The standard startup result owns the context; the game and runtime borrow it. +Declare the result before those borrowers so destruction releases them first: + +```mermaid +flowchart TB + accTitle: Keep the startup result alive through its borrowers + accDescr: StartupResult owns Context. The game borrows Context, and GameRuntime borrows both Context and the game for its session. + result["StartupResult"] -->|owns| context["Context"] + game["Game"] -->|borrows| context + runtime["GameRuntime"] -->|borrows| context + runtime -->|borrows| game +``` + +Use `CE::Application::make_startup()` for the standard configured assembly; the +[complete application below](#minimal-native-application) shows its entry point. +For a custom assembly, supply `StartupBackend::create` to `Startup` or construct +`CE::Runtime::Context` directly. The required +`create(const CE::Runtime::ExecutionOptions&)` callback returns an owned, nonnull +context; optional `arguments` and `validate` callbacks register and check backend +options before construction. A factory returns owned components with compatible +platform and graphics domains. Header or target selection alone does not establish +that compatibility. The [Startup module](../modules/startup.md) owns its configured +backend choices, optional services and module-specific deployment requirements. + +The context owns display, presentation, renderer and resource-provider adapters, +and either owns input or explicitly borrows it. The display owns the active window; +`engine.display()`, `engine.window()`, `engine.surface()`, `engine.renderer()`, +`engine.resources()` and `engine.input()` return borrowed access without moving +work to another thread. The borrowed-input factory overload +`make_startup(input, description, options)` requires that input to survive context +cleanup/destruction and detach while its window remains live. With +`CHERYL_NATIVE_INPUT=OFF`, the standard GLFW/OpenGL composition requires +application-supplied input. Use [Integration contracts](../architecture/interfaces-adapters.md) +when supplying new implementations. + +`GameRuntime` borrows the context and game for one session. Its `run()` initializes +the adapters before calling game `init_platform()` and closes them after game cleanup; +stopped adapters are not restarted by another session. Create graphics resources +through `engine.resources()` during platform-owned initialization or owner dispatch, +then retain their immutable generations in game state and frame packets. A retained +handle does not keep its graphics domain usable after backend closure. +[Ownership and cleanup](../architecture/ownership-cleanup.md) explains dependency +lifetimes, and [Threads and data flow](../architecture/threads-data-flow.md) explains +execution owners and result publication. + +CPU asset definitions, shader recipes, vertex data and decoded pixels are independent +of a graphics context. Construct an owned `CE::Assets::Loading::Loader(root)` for +each root; preparation needs no provider, while upload/publication uses the selected +provider on its owner. `manifests()` retains an immutable snapshot. The singleton +loader compatibility entry rejects a different root after its first initialization. +See [asset loading](../assets/asset-loading.md) for preparation/publication and +[rendering/materials](../rendering/pipelines-and-materials.md) for packet construction, +optional provider capabilities and native retirement. GUI sessions and +[audio](../runtime/audio.md) have separate application-owned lifetimes alongside +the runtime adapters. + +## Command-line startup + +[`CE::Application::Startup`](../../../projects/modules/runtime/startup/include/cheryl/application/startup.h) +owns CLI11 parsing and validates execution, input polling and simulation timing +before invoking a selected backend factory. Link `Cheryl::Startup` and include +`` for the standard +`CE::Application::make_startup(description, ApplicationOptions)` path. The +[Startup module](../modules/startup.md) owns backend selection, supplied services, +compatibility entry points and its deployment requirements. Startup exposes CLI11 +in its public option frontend; linking Engine or OpenGL alone retains direct +context/runtime construction without that public dependency. + +Create startup on the platform owner, register application options with +`add_application_options`, then call `initialize(argc, argv)` once. Option-definition +callbacks run immediately; any values bound to the parser must remain at a stable +address through initialization. Startup itself cannot be copied or moved because +its callbacks bind its configuration. + +Help and parse/configuration errors produce a `CE::Application::StartupResult` with no context. +Return its `exit_code` when `should_start()` is false; help succeeds and invalid +arguments fail before native context construction. A successful result owns the +`CE::Runtime::Context` and the parsed `CE::Application::RuntimeOptions`. Construct the game from that +context, then call `result.make_runtime(game)` to apply its run mode, polling and +timing together. Keep the result and game alive through runtime destruction; +`make_runtime` requires an lvalue result. Keep Startup alive through the context +and game, or retain an outer `CE::Logging::ProcessLifetime`, as required by its +[logging lifetime](../modules/startup.md#lifecycle-and-execution). Backend construction +failures propagate, and `CE::Runtime::GameRuntime::run()` retains the runtime's +initialization/cleanup contract. + +`CE::Application::ApplicationOptions` supplies initial window/presentation, +`execution` and `runtime` defaults for the generic factory. The lower-level +`Startup(StartupBackend, ..., StartupOptions)` constructor remains available for +custom composition. Common Engine options override their runtime defaults: + +| Argument | Effect | +| --- | --- | +| `--concurrent` | Selects a separate simulation owner thread. | +| `--fixed` | Selects fixed-step simulation. | +| `--fixed-step-ms=N` | Selects fixed simulation and sets its positive step in milliseconds. | +| `--variable-interval-ms=N` | Sets variable-update spacing; zero permits unpaced updates. | +| `--max-fixed-updates=N` | Sets a positive limit on ordinary fixed updates per scheduler turn. | +| `--variable-catch-up` | Selects fixed simulation with bounded variable catch-up recovery. | +| `--recovery-prefix=N` | Sets the fixed-update prefix before recovery, at most `max_fixed_updates`. | +| `--recovery-cap-ms=N` | Caps the recovery duration; zero removes the cap. | +| `--input-unlimited` | Selects unlimited completed polls between simulation updates. | +| `--input-capacity=N` | Selects finite input polling with a positive completed-poll capacity. | +| `--input-spacing-ms=N` | Sets minimum spacing between completed polls; zero removes the delay. | +| `--worker-count=N` | Sets capacity for the lazily created Engine CPU pool, separate from the simulation thread. Owned capacity must be positive; an injected shared pool retains its own capacity. | + +Numeric values are nonnegative and must satisfy the linked +[timing](../runtime/simulation-timing.md) and +[polling](../runtime/input-state-model.md#polling-backlog-and-scheduling) contracts. +Option callbacks apply in parse order: `--input-unlimited --input-capacity=4` +selects finite polling; reversing them selects unlimited polling. Without overrides, +the default configuration is sequential variable simulation, lockstep polling and +one lazy CPU worker. + +The [context assembly path](#context-assembly) supports application-supplied +factories. Standard startup selects its compiled integration rather than discovering +runtime plugins; [the Startup module](../modules/startup.md#selection-and-dependency) +defines available selections and their linked runtime requirements. +[The demo option helper](../../../projects/apps/demo/src/demo-options.cpp) +shows application-specific registration without duplicating the engine parser. + +## Game hooks and ownership + +Implement `CE::Game::AbstractGame` and lend it, with an `CE::Runtime::Context`, to +`CE::Runtime::GameRuntime`. Keep both alive through `CE::Runtime::GameRuntime::run()` returning or throwing. The selected +factory assembles display, window, input, presentation, renderer and resources; +`CE::Runtime::GameRuntime::run()` initializes those adapters before calling the game. + +| Hook | Owner | Responsibility | +| --- | --- | --- | +| `init_platform()` | Platform/graphics | Bind actions and create initial resources after adapter startup. | +| `init_simulation()` | Simulation | Construct simulation objects and attach view/step services after the initial view is ready, before Ready and timing start. | +| `update(tick)` | Simulation | Advance game state using `delta_seconds` and the tick's input/copy of window sizes. | +| `prepare_render_frame(writer) const` | Simulation | Resolve complete ordered passes/packets; publish no borrowed game state. | +| `deinit_simulation()` | Simulation before its owner exits | Release state requiring this owner after simulation dispatch closes; retain dependencies still used by producers or accepted CPU work. | +| `quiesce()` | Platform after simulation joins | Stop external producers and invalidate borrowed listeners; keep their dependencies alive. | +| `deinit_platform()` | Platform/graphics | Release application resources after accepted work settles and frames recycle. | + +Initialization and deinitialization hooks default to no-ops. Use the constructor +for setup with no runtime/thread dependencies. Sequential mode runs all hooks on +the calling thread. Concurrent mode gives simulation initialization, updates, +frame preparation and simulation cleanup to one worker; platform polling, uploads +and presentation remain on the caller. Cleanup pairs with attempted initialization, +so hooks must tolerate partial startup. The [frame boundary](../runtime/runtime-frame-boundary.md) +defines shutdown and failure ordering. + +### Minimal native application + +With Native GLFW, OpenGL and native input enabled, this opens a window and stops +when Escape is pressed. It submits no draws yet: + +```cpp +#include +#include + +class Game final : public CE::Game::AbstractGame { + CE::Runtime::Context& engine_; + static constexpr CE::Input::Actions::Id quit_{1}; + CE::Input::Actions::BindingId quit_binding_ = 0; + +public: + explicit Game(CE::Runtime::Context& engine) : engine_(engine) {} + void init_platform() override { + auto& input = engine_.input(); + quit_binding_ = input.bindings().bind_button( + {input.keyboard_id(), CE::Input::KeyboardKey::Escape}, quit_); + } + void update(const CE::Game::TickContext& tick) override { + if (tick.input.button(quit_).pressed()) + tick.request_stop(); + } + void prepare_render_frame(CE::Rendering::FrameWriter&) const override {} + void deinit_platform() override { + if (quit_binding_ != 0) { + static_cast(engine_.input().bindings().unbind(quit_binding_)); + quit_binding_ = 0; + } + } +}; + +int main(int argc, char** argv) { + auto startup = CE::Application::make_startup("My game", {.title = "My game"}); + auto result = startup.initialize(argc, argv); + if (!result.should_start()) + return result.exit_code; + Game game(*result.context); + auto runtime = result.make_runtime(game); + runtime.run(); + return result.exit_code; +} +``` + +Link this example to `Cheryl::Cheryl` with OpenGL selected, or to +`Cheryl::Startup` with OpenGL integration for granular composition. Pass `--concurrent` to select the +simulation worker, or use the common timing/polling options above. Applications +that construct `CE::Runtime::GameRuntime` directly can still pass `RunMode::Concurrent` as its +third argument. +Keep native binding IDs inside bootstrap; gameplay reads semantic `CE::Input::Actions::Id`s. +Edit direct bindings only on the input polling owner and query live windows on +the platform owner. For runtime join/leave from concurrent `update()`, use the +queued [action-context requests](../runtime/input-state-model.md#action-contexts). + +To add presentation, follow [asset loading](../assets/asset-loading.md) and +[pipeline/material construction](../rendering/pipelines-and-materials.md#native-bootstrap-and-binding) +in `init_platform()`, then use CPU submission helpers in frame preparation. For later +replacements, prepare owned data on [workers](../runtime/worker-execution.md), +[submit upload](../runtime/thread-dispatch.md) to platform and adopt only a ready +complete result during update. Keep the prior generation while work is pending. +The [demo implementation](../../../projects/apps/demo/src/main.cpp) shows that flow. + +Use [input actions and focus](../runtime/input-state-model.md) for controls, +[simulation timing](../runtime/simulation-timing.md) for fixed/variable policies, +and [asset playback](../assets/asset-values-and-playback.md) for per-entity cursors. +The [Unicode service](../assets/text-layout.md) prepares retained text; UI adapters +keep their toolkit authoring APIs. [Audio](../runtime/audio.md) is separately +application-owned and needs its producers settled before closure. World storage, +entities, collision and mechanics belong to the game or its selected modules. + +## Dependencies and headers + +All public Cheryl includes share the `cheryl/` prefix in both source and SDK +consumption. The files remain with their owning projects in the checkout; target +usage requirements present those public roots together to the compiler and IDE. +SDK installation collects the roots into one physical `include/cheryl/` tree. +CMake targets propagate the complete selected link requirements. Applications +linking raw archives directly must supply that link closure themselves. + +| Include | Public scope | +| --- | --- | +| `` | Engine plus the modules and support selected by linked targets. | +| `` | All neutral Engine public headers. | +| `` | Modules selected by linked targets. | +| `` | Compatibility support umbrellas selected by linked targets. | +| `` and other `cheryl/modules/` umbrellas | One module's public headers. | +| `cheryl/support/` umbrellas | One support library's public headers. | + +The umbrellas list public Cheryl headers. The SDK also retains `internals/` +support files used transitively by those declarations. The explicitly documented +`internals/exceptions.h` supplies public failure types at its historical path; +other internal support headers are not independent application interfaces. +Including an umbrella does not enable or link a module: CMake target selection supplies its +dependencies and the feature definitions used by the group umbrellas. Link +`Cheryl::Cheryl` for all configured components, or link granular public targets +and use the same umbrellas for that narrower selection. Additional includes such +as native TGUI/RmlUi headers remain available for their deliberately public +authoring APIs; private GLFW/Gainput/GLAD/spdlog headers are not consumer requirements. + +| Requirement | Target scope and reason | +| --- | --- | +| C++23, Threads, logging configuration | Engine public requirements used by templates/runtime consumers. | +| CTTI, GLM | Explicit Engine public header/template requirements; legacy `glm.hpp` spelling remains supported. | +| spdlog and Backward | Engine private logging/diagnostic implementation; callback/severity types belong to Cheryl and native sink access is private. | +| STB and JSON | Engine private implementation include directories. | +| FreeType, HarfBuzz, ICU | Engine private shared font/layout implementation and link requirements; Windows uses OS ICU. No library-native types appear in public headers. | +| CLI11 | Startup support's public parsing API; reuse `CLI11::CLI11` or initialize the pinned `extern/cli11` source. Plain Engine/OpenGL consumers do not inherit CLI11 headers or linkage. | +| GLFW | Shared runtime used by Native GLFW; OpenGL calls exported native operations. | +| Gainput/HID | Gainput is embedded in Native GLFW; HIDAPI is shared when selected. Public input types, binding mappings and device snapshots belong to Cheryl. | +| X11 | Native input's Linux dependency; OpenGL acceptance also uses it for explicitly selected X11 scenarios. | +| GLAD and OpenGL::GL | GLAD is private compiled OpenGL implementation; the graphics driver/system runtime remains explicit. | +| GoogleTest | Test-only; disabled owners do not discover their integration SDKs. | +| miniaudio | Optional audio module's private implementation/link requirement; public Engine and module audio headers expose no SDK types. | +| hwloc 2.5+ (2.x) | Workers module private topology implementation; its JSON feature remains independently selectable. | +| TGUI / RmlUi | Shared toolkit libraries expose native authoring APIs. Their Cheryl adapters supply Engine font/image services through private callbacks, preserving one Engine implementation even with static Cheryl. Matching toolkit headers/ABI settings are public requirements. | + +The Engine umbrellas and granular contracts are neutral. Concrete window/input +headers belong to Native GLFW; `backends/opengl` headers belong to OpenGL. Existing +`cheryl/...` and legacy `core/...`/`assets/...` include spellings are preserved through +each target's public roots, including in the installed SDK. + +`` and `` expose no Glad types; concrete OpenGL resources are +opt-in through ``. Portable asset values and primitives live in +`assets/types/`, with shared 2D bases under `assets/types/2d/base/`. CPU geometry +builders live in `assets/geometry/`, parsed grids/clips/manifests in +`assets/definitions/`, and neutral upload/resource interfaces in `assets/resources/`. +`core/rendering/` owns renderer contracts, frame submissions and resolved packets. +Common submission uses public parameter keys; the +[backend mapping](../rendering/pipelines-and-materials.md#native-bootstrap-and-binding) +owns native uniform names and graphics types. + +## Crash and exception traces + +`Cheryl::Engine` delivers the automatic crash bootstrap. Static builds forward +its object directly to final consumers, so the linker cannot omit an unreferenced +archive initializer. Shared Engine embeds one copy; modules and applications +reuse that copy through Engine. Installation follows the bootstrap object's +`NDEBUG` scope in the engine build: Debug-style builds install +`backward::SignalHandling`; `NDEBUG` builds do not. +Exception and explicit stack capture remain available through the existing bounded +capture/fallback implementation in all builds. Static SDKs also export +`Cheryl::SignalHandlers`; shared SDKs keep the bootstrap inside `Cheryl::Engine`. +The demo needs only the selected engine/module targets. `NDEBUG` controls signal +bootstrap installation, not explicit trace capture. The legacy `ST_ON_SIGNALS` +definition exposes the `sh` declaration through `core/logging.h`; Debug shared +Engine exports its owned instance, and static Debug consumers receive it through +the bootstrap object. The definition does not change installation scope. See the +[signal procedure](architecture-validation.md#manual-acceptance-drivers). + +## Independent consumers and header probes + +The maintainer procedure is in +[Independent consumers and header probes](architecture-validation.md#independent-consumers-and-header-probes), +including standalone Engine/native/OpenGL consumers and their coverage limits. + +## Optional output terminal + +For Linux GNU/Clang consumers, explicitly link `Cheryl::DebugTerminal::Linux` (or +the root's selected `Cheryl::DebugTerminal`) to choose the process entry attachment. +The source `Cheryl::Cheryl` facade also includes it when configured; SDK facades +omit this source-only module and its Debug terminal support. +Plain Engine and command-line Startup do not select it. `AUTO` includes actual +Debug only; `ON` permits a non-Debug consumer to display it with `--debug-terminal`. +See the [module guide](../modules/debug-terminal-linux.md) +for argument consumption, fallback and independent viewer lifetime. + +The small `Cheryl::DebugTerminal::Startup` support target accepts an implementation +callback; with no callback it calls the application unchanged. Native capture, +viewer launch and process-wide shutdown live entirely in the selected module. + +## Standard headers in an existing build + +Compiler-discovery recovery is documented in +[Standard headers in an existing build](building.md#standard-headers-in-an-existing-build). diff --git a/docs/development/contributing.md b/docs/site/development/contributing.md similarity index 55% rename from docs/development/contributing.md rename to docs/site/development/contributing.md index 9b9dd924..65809b2b 100644 --- a/docs/development/contributing.md +++ b/docs/site/development/contributing.md @@ -1,30 +1,49 @@ # Contributing -Start with the [architecture overview](../runtime/runtime-architecture.md) and the -guide for the subsystem you are changing. Choose the owning library before adding -code: neutral contracts belong in Engine, runtime integration SDKs in their -modules, optional Engine helpers in support targets, and game mechanics in the -application. The [module guide](modules.md) explains -when a new owner is useful and how to compose it. +Start with the guide for the subsystem you are changing. Choose the owning library +before adding code: neutral contracts belong in Engine, runtime integration SDKs +in their modules, optional Engine helpers in support targets, and game mechanics +in the application. [Write a module](modules.md) explains when a new owner is +useful and how to compose it. Keep a change focused on its intended behavior. Preserve unrelated work in this shared checkout and avoid changing interfaces or formatting nearby code merely because it is convenient. Apply the [C++ style](code-style.md) to affected code. Pinned dependencies retain their own licenses and upstream conventions. +```{toctree} +:hidden: +:titlesonly: + +C++ code style +``` + +## First change + +1. Find the subsystem's [Engine Systems entry](../engine-systems/index.md) and the + implementation owner; read the relevant API/subject contract before editing. +2. State the observable behavior being changed, the callers affected and any + compatibility or ownership constraint. Use the existing owner unless a new + module meets the [boundary criteria](modules.md#module-boundary-criteria). +3. Make the focused change and its directly affected regression coverage. Use the + [check selector](testing.md#choose-checks-for-a-change) to choose the smallest + checks that establish the changed contract. +4. Reconcile affected API/manual contracts during documentation maintenance, then + hand off the behavior, actual checks and outstanding acceptance separately. + ## Validation Add behavior checks under the owner they exercise. Engine checks use controlled adapters; integration checks belong with the selected module. Compile public headers through the owner's consumer probes when a change affects consumption. -Use [test selection](testing.md) to batch targets and avoid duplicate aggregate +Use [Testing and validation](testing.md) to batch targets and avoid duplicate aggregate runs. Native pixels, sound, controller reports and platform services require their own observation procedures. When reporting a change, explain the resulting behavior, why it belongs in that owner and the checks performed. Identify unresolved failures and missing coverage with their next action. Agent execution and commit rules live in -[AGENTS.md](../../AGENTS.md). +[AGENTS.md](../../../AGENTS.md). ## Documentation and planning @@ -32,12 +51,12 @@ During an authorized documentation-maintenance phase, update the authoritative API or subject guide when contracts change, including ownership, valid threads, units, preconditions and failure behavior. Keep build instructions in the [build guide](building.md), runner selection in the -[test guide](testing.md), and integration-specific details with their module. +[testing guide](testing.md), and integration-specific details with their module. Plans describe unresolved work, prerequisites, dependency boundaries and acceptance. Keep an active task's checklist in one owning plan and link it from the roadmap. Preserve useful decisions before removing completed plans; implementation history -belongs in Git. Maintain [testing requests](../testing-requests.md) only when that +belongs in Git. Maintain [testing requests](../../testing-requests.md) only when that maintenance is authorized: carry outstanding requirements in the phase handoff until then, and remove accepted requests after reconciling revision and coverage. Documentation audit reports findings without editing; maintenance implements the diff --git a/docs/site/development/documentation.md b/docs/site/development/documentation.md new file mode 100644 index 00000000..688b5b6d --- /dev/null +++ b/docs/site/development/documentation.md @@ -0,0 +1,444 @@ +(generating-documentation)= +# Documentation tooling + +The manual and C++ reference share one generation command. Doxygen extracts the +selected public headers to XML; Breathe and Exhale turn that XML into Sphinx pages, +and MyST includes the Markdown guides. Documentation generation does not configure +or compile the engine. Header selection comes from the public-root inventory in +[build.py](../generation/build.py), plus explicitly listed support headers. The +public exception types in `projects/engine/include/internals/exceptions.h` are +one such explicit owner; this does not make all `internals/` declarations public. + +```mermaid +flowchart LR + accTitle: Manual and API sources become one documentation site + accDescr: Selected public headers become Doxygen XML and generated API pages through Breathe and Exhale. MyST reads the Markdown guides; Sphinx combines both into the HTML site. + headers["Selected headers"] --> xml["Doxygen XML"] + xml --> api["Breathe and Exhale"] + api --> sphinx["Sphinx"] + markdown["Markdown guides"] -->|MyST| sphinx + sphinx --> html["HTML site"] +``` + +The generated artifact is an HTML site rooted at +`docs/site/generation/generated/html/index.html`, containing both the manual and API +reference. For an explicit CMake artifact target, use +[`docs` in the docs-only project](#cmake-entry-points); the repository-root +[`generate-docs.sh`](../../../generate-docs.sh) script provides the direct command. +Both update documentation incrementally by default; `docs-clean` or +`./generate-docs.sh --clean` requests full regeneration. Enabling +`CHERYL_BUILD_DOCS` makes the same targets available in an engine build; +documentation is never part of the default build. + +```{toctree} +:hidden: +:titlesonly: + +Publishing documentation +``` + +## Generate locally + +Use a Git checkout and Python 3.11 or newer with the standard `venv` module. You +do not need to install Sphinx, Doxygen or pip manually. The first fallback setup +needs network access to PyPI and the official Doxygen release assets, and a writable +user cache. The shell entry point requires Bash and selects `python3`, falling +back to `python`; set `CHERYL_DOCS_PYTHON` to choose another interpreter. Run this +block from any directory inside the checkout; the subshell preserves your starting +directory: + +```sh +( + set -e + cd "$(git rev-parse --show-toplevel)" + ./generate-docs.sh +) +``` + +The HTML entry point is `docs/site/generation/generated/html/index.html`. To view +Mermaid diagrams, serve the generated directory over HTTP so the browser can load +its JavaScript modules. From the checkout root, run: + +```sh +python3 -m http.server 8080 --bind 127.0.0.1 --directory docs/site/generation/generated/html +``` + +Open [the local site](http://127.0.0.1:8080/). On Windows, use `python` instead of +`python3`. Opening an HTML file directly still shows the diagram source as text; +use HTTP for rendered diagrams. The output directory also contains Doxygen XML, +Sphinx doctrees and `sphinx-warnings.log`. +`--output /path/to/output` selects another +output root outside the authored documentation, or another directory beneath +`docs/site/generation/generated/`; its `api/`, `pycache/`, `.api-previous/` and +`.xml-previous/` directories are reserved. +The generated tree is ignored by Git. Edit the declaring +headers or authored guides, then regenerate. Generation is serialized within a +checkout because all output profiles share the generated API source directory. + +The script locates the repository independently of the current directory. You +can also invoke `/path/to/cheryl-engine/generate-docs.sh` from elsewhere. It +preserves that working directory, so relative `--output` and `--cache-dir` paths +resolve from where you invoke it. Use `--help` for the available generation and +tool-selection options. `--extract-only` generates only Doxygen XML; `--output` +and `--doxygen` select an output root and explicit Doxygen executable. + +The Python entry point remains supported, including on Windows without Bash: +invoke `python docs/site/generation/build.py` from the checkout root, or use its +absolute path from another directory. It accepts the same generation flags and +selects tools through bootstrap. Tool-only `status` and `setup` operations remain +available through `docs/site/generation/bootstrap.py`; the shell script handles +documentation generation. + +The generation scripts, Sphinx configuration, extensions, styles, templates and +dependency manifests live under `docs/site/generation/`. Sphinx reads the authored +pages from `docs/site/` using that separate configuration directory. Generated API +sources live under `docs/site/generation/generated/api/`; Python bytecode defaults to +`docs/site/generation/generated/pycache/`. The other generation files are excluded from +the published manual's source discovery. + +All authored manual prose, including module references, lives under `docs/site/`. +The module READMEs beside the code are relative symlinks to those references; +generation reads the central regular files directly. Plans, status, pending +acceptance and agent guidance remain outside the Sphinx source root. Relative +links to those repository-only files become versioned repository links in HTML. + +The generator treats Sphinx warnings and unresolved references as errors, then +checks the public-header inventory, private-member exclusion, local links and +fragments, case-insensitive filename collisions, and source objects at the checked +revision. Successful extraction does not establish that every declaration has +useful prose, examples or a complete contract. Review public comments and the +[API index](../api/index.md) separately. + +Ordinary inline code in the manual and API prose links automatically when its name +identifies one published declaration. This includes qualified names, unique short +names, template types, calls such as `CE::Text::Rendering::prepare(shaped, raster_options)`, +and qualified member assignments. The displayed code stays unchanged. Call +arguments are expressions, so they do not select an overload: ambiguous names, +unknown names and private implementation details stay plain code. Code blocks, +declaration signatures and existing links are left alone. Explicit C++ roles remain +available when a specific overload is needed. The linker builds one lookup from +Sphinx's declaration inventory, then checks inline literals without reparsing +headers or XML for each reference. +The build log reports its indexing and linking time for serial Sphinx builds. + +Public API comments can link back to a manual contract with a checked guide marker, +for example `@see [Rendering guide](cheryl-guide:rendering/pipelines-and-materials)`. +The target is the site docname without `.md`; an optional `#fragment` must name a +real heading anchor. The generator resolves it for the generated reference and +checks the result. Keep detailed declaration contracts in the declaring header +and workflows in the manual; link between them instead of copying either. + +Derived pages link to the declaring base for inherited interfaces. Public nested +types have their own pages rather than repeated declarations in the outer class. +The reference adds Doxygen's `final`, `[[nodiscard]]` and template constraints as +declaration notes, and includes concept definitions. Each declaration page links +to its header at the recorded source revision. Consult that header for the full C++ +declaration. + +### Diagrams + +Write Mermaid directly in an ordinary Markdown code fence: + +````markdown +```mermaid +flowchart LR + Game --> Input + Game --> Rendering +``` +```` + +State and sequence diagrams use the same fence with `stateDiagram-v2` or +`sequenceDiagram` as the first line. In dependency maps, point arrows from the +consumer toward what it uses and leave them unlabeled; the surrounding prose +owns precise contracts. Add +`accTitle:` and `accDescr:` for an accessible title and description. + +MyST passes these fences to `sphinxcontrib-mermaid`, installed through the normal +locked Python documentation requirements. The extension leaves diagram text in +the HTML and Mermaid renders it in the browser. Its pinned JavaScript modules +ship with the site, including the module chunks and upstream license under +`generation/static/vendor/mermaid-11.12.1/`. `UPSTREAM.json` records the source and +archive integrity for updates; preserve the corresponding chunks and license. +Readers need JavaScript, but no CDN or rendering service. + +Generation uses the extension's `raw` output mode. It needs no Graphviz, Node, +Mermaid CLI or diagram-specific CI step. Preview changed diagrams in a browser: +successful Sphinx generation alone does not validate Mermaid syntax. + +Engine Systems shares its curated sources with the +[Diagram explorer](../engine-systems/diagram-explorer.md). Reuse a view in a guide +with its stable diagram ID: + +````markdown +```{engine-diagram} rendering-focus-sprite +``` +```` + +The sources live in `engine-systems/diagrams/*.mmd`. The neighboring +`catalog.json` owns view questions, notes, symbol identities and source evidence. +Keep its node order and arrow endpoints aligned with the Mermaid source; the +generator checks that contract before publishing. Ordinary generation includes +the explorer, Mermaid downloads and public API links. It renders selected views +in the browser using the same pinned Mermaid modules as the manual; no rendered +SVG inventory or separate preview deployment is needed. + +Application views can add an `operations` mapping from a node key to its selected +public methods. Keep these choices specific to the view: usually one to three +entry points per type, with value types left as values. Each entry records the +qualified declaration name, short display label, source file/line, purpose and +`role` (`call` or `override`). Use the declaring base type for inherited methods. +Include the corresponding signatures beneath the owning type in Mermaid, +omitting parameter names when their types are shown and declaration qualifiers; +label checks reject stale +metadata. Wrapping markup may change whitespace, but must +preserve type spelling, including template arguments. Mark override groups +**Implement hooks** to distinguish engine callbacks from application calls. The +explorer adds method search, source links and unambiguous API links without +changing the type's identity or adding operation details to the internal and +focused views. + +Write labels as `method(arguments): QualifiedResultType`. Include relevant +Engine argument and result types, including public aliases and types without +their own box. Keep argument names in catalog labels for the inspector, and +omit those names from Mermaid when their types are shown. Preserve `const`, +pointer/reference qualifiers and ownership/completion markers in catalog labels; +omit them from Mermaid so the diagram shows the participating types. Keep `[]` +for collections. Omit defaults, the leading `CE::`, and primitive or `std::` +type spellings. Retain names for scalar and library-only arguments; +include template parameter types where they clarify generic arguments. A +missing result suffix does not imply `void`: scalar and +library-only results are abridged too. Inspector markers such as `[shared]`, +`[unique]`, `[future]` or `[borrowed]` retain ownership, asynchronous completion +and borrowing details from omitted library wrappers. The API reference remains +the complete signature. + +A view can declare `namespace`, such as `"Input"`, to omit that prefix from +its Mermaid labels. Keep nested namespaces (`Actions::Context`) and other +namespaces (`Runtime::Context`) explicit. The explorer states this local scope +beside the view question; retain it in the Mermaid accessibility description +too. Keep catalog symbol and method labels fully qualified for the inspector, +search and API links. Preserve a local prefix when shortening it would collide +with another Engine namespace, such as `Text::Rendering` or `Input::Events`. +Label validation applies the same namespace and parameter-name abbreviations. +Mixed views such as diagnostics and utilities keep their namespaces explicit. + +For a named free-function box, use the view's `operation_labels` mapping from +its existing node key to the typed label. Keep the global symbol identity and +other views' labels unchanged. Apply the same parameter-name and namespace +abbreviations in the Mermaid source; its +validation also ignores wrapping whitespace while preserving template types. + +Connect selected methods to their public Engine argument and result types when +those types are shown in the application view. Record signature evidence with +the exact selected declaration name in `member` and `kind: accepts` or +`kind: returns`. The explorer uses these records for each method's **Accepts** +and **Returns** links and the arrow's method explanation. Use explicit parameter +types in method labels even when that type has no box in the selected view. +Add another type box only when it clarifies an essential application boundary; +the views remain selective rather than duplicating the complete API inventory. + +### Incremental rebuilds and full regeneration + +The first generation performs the complete extraction and site build. Existing +output created before incremental support also needs one complete generation to +establish its cache state; the normal command handles this automatically. +When relocating the documentation source root, create fresh output and doctrees +at the new default path. Retain any older output still being viewed; reusable +tool installations remain in the shared cache. +Later runs reuse completed stages: + +- Editing a guide reuses Doxygen XML, unchanged API pages and Sphinx doctrees. + Sphinx rebuilds the affected pages. CSS/template changes rewrite HTML using + the cached doctrees and update the site assets. +- Changing page titles, navigation ownership, captions or table-of-contents depth + rewrites every HTML page so cached sidebars and breadcrumbs stay consistent. + This uses Sphinx's parsed navigation and retains unchanged doctrees. +- Editing public headers reruns Doxygen and rebuilds affected reference pages; + a changed XML file inventory starts a fresh Sphinx build. +- Changes to generation tools or configuration can + invalidate reuse and require a full rebuild. + +Each run retains the warning, link, API-inventory and source-revision checks. +A Sphinx failure preserves successful Doxygen extraction but requires fresh +rendering on retry. A later link or source-revision check failure preserves both +completed stages; fixing the cause and rerunning still performs all checks. +Cached rendering does not mean the output passed validation. Tool-installation +caches are separate from generated-site caches and survive clean generation. + +Use `--jobs 4` to let Sphinx read and write documents with up to four workers. +The default is one; the count must be a positive integer. This selects rendering +parallelism without changing the generated content or validation requirements. + +The log explains why XML or Sphinx output needs regeneration and reports the +time spent extracting, rendering and checking. `Generating fresh Sphinx output` +means the saved environment is being replaced; Sphinx then reports every guide +and generated API page as added. A changed Git revision refreshes repository +links and revision footers in retained HTML; it does not by itself discard the +parsed-page cache. `Navigation changed` reports a site-wide HTML rewrite using +that cache. + +Authored guides and included examples use Sphinx's normal modification-time +tracking. If an external tool changes their contents while preserving or +backdating timestamps, request a clean generation. + +To force full extraction and site generation: + +```sh +( + set -e + cd "$(git rev-parse --show-toplevel)" + ./generate-docs.sh --clean +) +``` + +`--extract-only` still limits the command to Doxygen XML; combine it with `--clean` +when you need fresh extraction without generating HTML. CI and Read the Docs use +`--clean` explicitly so publication does not depend on a previous generated site. + +## Tool selection and reuse + +[bootstrap.py](../generation/bootstrap.py) prefers a compatible installed +Doxygen and the Python packages in the invoking interpreter. Newer versions are +allowed for local work. Python selection checks the complete installed dependency +closure, version constraints, Python requirements and imports, rather than only +the five direct package versions. + +| Tool | Minimum installed version | +| --- | --- | +| Doxygen | 1.18.0 | +| Sphinx | 8.2.3 | +| Breathe | 4.36.0 | +| Exhale | 0.3.7 | +| MyST Parser | 5.1.0 | +| Sphinx RTD Theme | 3.1.0 | + +If installed tools do not qualify, bootstrap selects the exact versions in +[requirements.txt](../generation/requirements.txt) and +[native-tools.json](../generation/native-tools.json). Python fallback uses an +isolated virtual environment, a pinned pip wheel and hash-checked binary wheels; +it does not install into the invoking interpreter. Doxygen fallback uses a +hash-checked official archive. A healthy cached environment is validated and +reused without running an installer. + +The default shared cache is `cheryl-engine/docs-tools` beneath the platform's +user cache: `$XDG_CACHE_HOME` or `~/.cache` on Linux, `~/Library/Caches` on macOS, +and `%LOCALAPPDATA%` on Windows. Python cache identity includes the interpreter, +platform and requirements hash; native cache identity includes the archive hash. +Locks and readiness markers prevent concurrent setup from reusing a partial +installation. An interrupted setup is repaired on the next invocation. + +Pinned Doxygen archives are provided for Linux x86_64, Windows x86_64, and macOS +x86_64/arm64. Other hosts need a compatible installed Doxygen; Python fallback +also requires matching binary wheels. The pinned Python lock has wheel metadata +coverage for CPython 3.11–3.14 on these platforms. Linux fallback has been exercised; +Windows and macOS native setup still require execution acceptance. + +Bootstrap's `status` reports tool readiness without installing anything; `setup` +prepares tools without generating documentation. The shell script's +`--force-pinned` selects the locked tools. Combine it with `--clean` for the full +generation used by CI and Read the Docs: + +```sh +( + set -e + cd "$(git rev-parse --show-toplevel)" + ./generate-docs.sh --force-pinned --clean +) +``` + +Set `CHERYL_DOCS_CACHE_DIR` or pass `./generate-docs.sh --cache-dir /path/to/cache` +to choose another shared cache. `CHERYL_DOCS_PYTHON=/path/to/python` selects the +script's interpreter; the Python and CMake entry points also support explicit +interpreter selection. `./generate-docs.sh --doxygen /path/to/doxygen` supplies an +executable that must meet the minimum and takes precedence over automatic native +tool selection. Keep local overrides out of publication configuration. + +## CMake entry points + +The docs-only project needs CMake 3.28 or newer and Ninja in addition to Python. +It enables no compiler languages and does not discover engine dependencies: + +```sh +( + set -e + cd "$(git rev-parse --show-toplevel)" + cmake -S docs/site/generation -B docs/site/generation/generated/cmake -G Ninja + cmake --build docs/site/generation/generated/cmake --target docs +) +``` + +This incrementally updates `docs/site/generation/generated/html/index.html`. Select +`docs-clean` instead of `docs` in the build command to force full regeneration. +The optional `docs-tools` target prepares tools; both generation targets also +select them, so running `docs-tools` first is unnecessary. None of these targets +belongs to the default build. + +If the docs-only source project moves, configure a fresh build directory for its +new location. An existing CMake cache records the previous source directory. + +To add documentation to an existing root engine build, reconfigure that same build +directory and explicitly select the target: + +```sh +( + set -e + cd "$(git rev-parse --show-toplevel)" + cmake -S . -B build/debug -G Ninja -DCHERYL_BUILD_DOCS=ON + cmake --build build/debug --target docs +) +``` + +Use your existing build directory in place of `build/debug`; its other cached +settings are retained. The root project still has its usual compiler and dependency +requirements; use the [building guide](building.md) for an initial engine +configuration. Building `docs` or `docs-clean` runs documentation generation +without compiling the engine. Both CMake entry points expose +`CHERYL_DOCS_PYTHON`, `CHERYL_DOCS_CACHE_DIR` and `CHERYL_DOCS_FORCE_PINNED`: + +```sh +( + set -e + cd "$(git rev-parse --show-toplevel)" + cmake -S docs/site/generation -B docs/site/generation/generated/cmake -G Ninja \ + -DCHERYL_DOCS_PYTHON=/path/to/python \ + -DCHERYL_DOCS_CACHE_DIR=/path/to/shared-cache \ + -DCHERYL_DOCS_FORCE_PINNED=ON + cmake --build docs/site/generation/generated/cmake --target docs +) +``` + +## Troubleshooting + +| Symptom | Next action | +| --- | --- | +| First setup cannot download or write its environment | Check network access and the selected shared-cache permissions. `python3 docs/site/generation/bootstrap.py status` reports readiness without installing; use `--cache-dir` for a writable location and rerun setup/generation there. | +| A Sphinx warning, missing symbol or bad fragment stops generation | Inspect the selected output root's `sphinx-warnings.log` and the declaring header/guide. Repair the target or comment; do not accept an older HTML file as the current successful result. | +| API output appears stale after changing extraction rules or tools | Use [full regeneration](#incremental-rebuilds-and-full-regeneration) after correcting the inputs. Ordinary public-header edits already invalidate extraction; changing unrelated output paths is not a substitute. | +| Generation waits while another invocation is running | All output profiles share generated API sources and a checkout lock. Let the active invocation finish, then rerun; use a separate checkout for independent concurrent generation. | +| CMake retains the old docs source directory | Configure a fresh docs-only build directory for `docs/site/generation`; preserve any active build/output instead of repurposing its cache. | + +## Refreshing the pinned tools + +Treat a tool upgrade as a coherent change to the fallback environment, rather than +changing one direct version and accepting whatever an installer resolves: + +1. Resolve the full dependency closure from official PyPI release metadata, + including platform and Python markers. Pin every required package and the + installer; include SHA-256 hashes for the accepted wheel variants. Keep binary + wheels required, and verify Python requirements and wheel availability across + the supported Python/platform combinations. +2. Update `native-tools.json` when changing Doxygen or the pinned pip wheel. Obtain + archive URLs and digests from the [official Doxygen releases](https://github.com/doxygen/doxygen/releases) + and [PyPI](https://pypi.org/), and keep platform mappings explicit. Update the + bootstrap minima only when the supported local baseline changes; fallback pins + and minimum installed versions serve different purposes. +3. With execution authorized, generate using `--force-pinned`, check the output, + and confirm warm-cache reuse. Review API signatures and public/private scope + when changing Doxygen, Breathe or Exhale. A new requirements hash selects a new + cache identity; old caches do not need to be overwritten. + +## Publish the generated site + +The [publishing guide](publishing-documentation.md) owns account setup, version +policy, stable promotion and hosted recovery. Local generation and link checks +prepare the artifact; they do not publish a website. diff --git a/docs/development/logging-acceptance.md b/docs/site/development/logging-acceptance.md similarity index 76% rename from docs/development/logging-acceptance.md rename to docs/site/development/logging-acceptance.md index 295d1bbe..62088231 100644 --- a/docs/development/logging-acceptance.md +++ b/docs/site/development/logging-acceptance.md @@ -1,4 +1,5 @@ -# Logging acceptance +(logging-acceptance)= +# Logging checks Logging acceptance covers owned destinations, queue loss, callback reentry, retained resources and supported teardown. @@ -7,7 +8,7 @@ Configure a separate directory for each row. Use Release as the build configuration for this matrix so profile changes remain independent of the Debug sanitizer configuration. The Engine-only selection below avoids unrelated native/display prerequisites; use the -[architecture procedures](architecture-validation.md) when module composition also changes. +[Component and SDK checks](architecture-validation.md) when module composition also changes. | Row | CHERYL_LOG_PROFILE | CHERYL_LOG_COMPILED_MASK | | --- | --- | --- | @@ -31,8 +32,10 @@ For the developer row: -DCHERYL_BUILD_DEBUG_TERMINAL_LINUX=OFF \ -DCHERYL_BUILD_UI_TGUI=OFF -DCHERYL_BUILD_UI_RMLUI=OFF \ -DCHERYL_BUILD_AUDIO_MINIAUDIO=OFF \ + -DCHERYL_BUILD_WORKER_TOPOLOGY_HWLOC=OFF -DCHERYL_BUILD_WORKER_TOPOLOGY_JSON=OFF \ + -DCHERYL_BUILD_DOCS=OFF -DCHERYL_BUILD_SDK=OFF \ -DCHERYL_BUILD_DEMO=OFF \ - -DCHERYL_SANDBOX_BUILD=OFF -DCHERYL_LOG_PROFILE=developer + -DCHERYL_LOG_PROFILE=developer cmake --build build/log-developer --parallel \ --target tests-logging acceptance-logging python3 projects/engine/tests/logging-acceptance/logging.py build/log-developer @@ -51,7 +54,7 @@ compile stripping. Record these distinctions instead of claiming zero skips. Independent processes exercise standard/non-standard sink log/flush failures, failed rotation leaving a closed file, saturated recursive native submission, -Block and DiscardNew loss behavior, discarded flush requests, retained clones, +Block and DiscardNew loss behavior, discarded flush requests, retained backend ownership, static singleton/pool teardown, and fatal final owner destruction from a callback. Named-routing acceptance separately checks the five engine files, formatted/stream/ direct named writes, category filtering and closed/reopened behavior after the @@ -62,6 +65,15 @@ checks shared loss output, and verifies the fatal case's deliberate exit code. Timeouts and nonzero results are failures, not accepted limitations. Native GPU or real-font fixtures are not prerequisites for this logging matrix. +When the runner fails, use the scenario to choose the next investigation: + +| Result | Check next | +| --- | --- | +| A required executable is missing | Build both targets for that row; the Python driver does not configure or build. | +| Expected severity records are absent | Compare the row's compiled profile/mask, runtime gates and selected destination; compile stripping cannot be undone at runtime. | +| A child times out or returns an unexpected status | Inspect that isolated scenario's output and teardown path. The deliberate fatal-child exit is accepted only when it matches the driver's expectation. | +| A fixture or platform case skips | Preserve it as missing coverage and supply its prerequisites; a successful remaining selection does not accept the skipped case. | + For concurrency changes, run relevant cases under separate sanitizer configurations. Debug currently enables ASan/UBSan. A TSan configuration must disable that combination before enabling TSan; adding it is a separate build diff --git a/docs/site/development/modules.md b/docs/site/development/modules.md new file mode 100644 index 00000000..f6f837cf --- /dev/null +++ b/docs/site/development/modules.md @@ -0,0 +1,322 @@ +(engine-and-integration-modules)= +# Module authoring + +Cheryl uses one owning `Cheryl::Engine` library and independently selected integration +libraries. Each owner declares its sources, public includes, dependencies and tests. +The root selects the assembly. The [module index](../../../projects/modules/README.md) +maps owners to Engine contracts and links their APIs; [build options](building.md#configuration-options) +control selection. + +Engine has no reverse dependency on an integration. Logging, memory, workers, +events, assets and utilities remain within the one Engine library. Platform owners +implement display/window/input contracts, graphics owners implement rendering and +resources, UI owners consume neutral contracts, and audio owners implement playback. +The optional Linux Debug terminal owns process output/viewer behavior outside the +runtime adapter graph. Its Engine support attachment accepts a module callback; +an absent implementation leaves application startup unchanged. + +The public [Startup module](../modules/startup.md), `Cheryl::Startup`, supplies +standard application bootstrap and privately integrates configured backends. +`Cheryl::OpenGL::Startup` is a compatibility interface over Startup/OpenGL rather +than another runtime library. CLI11 remains the explicit public option frontend; +Engine and OpenGL do not depend back on Startup. + +An integration's public target carries its required Engine and header usage. +Dependency arrows point from the consumer to what it needs: + +```mermaid +flowchart TB + accTitle: Public and private module dependencies + accDescr: An application links an integration module's public target. The module uses Engine and public-header dependencies publicly, while implementation-only dependencies remain private. + app["Application"] -->|links public target| module["Integration module"] + module -->|PUBLIC| engine["Engine"] + module -->|PUBLIC| headers["Public-header dependencies"] + module -->|PRIVATE| implementation["Implementation dependencies"] +``` + +The optional [Workers module](../modules/workers.md), `Cheryl::Workers`, groups +configuration loading and topology integration with independent JSON/hwloc +features. Core pool execution/scheduling remains in Engine. Its capability-specific +`Cheryl::WorkerTopology::Hwloc` and `Cheryl::WorkerTopology::Json` compatibility +names refer to the same selected implementation. Private support belongs inside +its owning binary; configurable build policy alone is not a public module. + +| Owner | Dependency direction | +| --- | --- | +| Application | Links Engine and its selected integrations. | +| Startup | Uses Engine and privately integrates the configured OpenGL path. | +| Native GLFW | Implements Engine platform/input contracts. | +| OpenGL | Uses Engine contracts and the selected Native GLFW integration. | +| TGUI / RmlUi | Consume Engine input/rendering/font contracts; their public APIs do not require OpenGL or Native GLFW. | +| miniaudio | Implements Engine audio contracts independently of the window/graphics path. | + +```{toctree} +:hidden: +:maxdepth: 1 + +Integration contracts <../architecture/interfaces-adapters> +UI adapter authoring +``` + +## Adapter contracts + +Use [Integration contracts](../architecture/interfaces-adapters.md) to find the +required runtime interfaces, optional capabilities, existing implementations and +replacement dependencies. [Context assembly](consuming-engine.md#context-assembly) +explains how application factories connect compatible implementations. This guide +covers creating and composing the module that owns an implementation. + +### SDL3 scaffold + +`CHERYL_BUILD_NATIVE_SDL3=ON` adds the source-only +`Cheryl::NativeSDL3::Scaffold` interface target from +[`platform/native-sdl3`](../../../projects/modules/platform/native-sdl3). +The option defaults to `OFF`. Its +[`cheryl/backends/native-sdl3.h`](../../../projects/modules/platform/native-sdl3/include/cheryl/backends/native-sdl3.h) +includes the existing Engine contracts and forward-declares +`CE::Platform::SDL3::Window`, `DisplaySystem` and `InputSystem`. + +The scaffold has no runtime implementations, SDL3 dependency discovery or startup +factory. It is not registered as an SDK component or a selectable application +backend. Native GLFW remains the implemented platform integration. SDL3 runtime +implementation, OpenGL/startup selection and GLFW deprecation policy remain +future work. + +## Module boundary criteria + +A separate module is justified when it gives an application a concrete choice or +isolation benefit: omitting a dependency, selecting an alternative implementation, +or testing an external integration independently. A directory, internal interface +or small caller count does not by itself justify a library boundary. Cheryl keeps +one `Cheryl::Engine` owner with selectable static/shared linkage. + +Keep the selected Native GLFW, whole OpenGL and independent TGUI/RmlUi owners +cohesive unless a consumer demonstrates a benefit from another boundary. Native +input depends on a live window; dividing display and input splits that coupled +lifetime. OpenGL's context interface permits another window integration within its +owner and does not itself justify context/bridge libraries. + +Before introducing another library target, record: + +1. the concrete omit/replace/test benefit; +2. the engine contract the owner implements; +3. dependencies that become optional or isolated; +4. lifetime/dependency direction and how cycles are avoided; +5. test ownership and the smallest independent consumer proving the boundary; +6. compatibility consequences for current consumers. + +If those points do not identify a real benefit, keep the code inside the existing +owner. Logging, memory, workers, events, general utilities and similar internal +facilities remain engine organization unless a consumer demonstrates otherwise. +Evaluate architectural interfaces by their contract rather than deleting them +because they have few callers. + +## Select an assembly + +Application module selection, public targets, aggregate headers and deployment are +owned by [Source and SDK integration](consuming-engine.md). Use the +[module catalogue](../modules/index.md) to choose capabilities, the +[granular composition path](consuming-engine.md#granular-composition) to select +individual targets, and [Dependencies and headers](consuming-engine.md#dependencies-and-headers) +for public/private dependency requirements. The [build guide](building.md) +owns root configuration options and dependency preparation. + +Each compiled dependency has one owning Cheryl binary: Engine owns logging, +font/text and image implementations; Native GLFW owns GLFW/Gainput/HID; OpenGL +owns GLAD; each UI module owns its toolkit implementation and exports its native +authoring API. Startup owns selected integration glue and Workers owns optional +topology/configuration adaptation. Header/template usage such as GLM, CTTI and +CLI11 remains explicitly public. Preserve those boundaries when adding an owner. + +## Standalone modules + +Standalone entry points locate the enclosing checkout's shared helpers and Engine +automatically. Set an absolute `CHERYL_REPOSITORY_ROOT` when selecting another +checkout. Each owner reuses a supplied `Cheryl::Engine` or adds Engine from that +root. OpenGL also reuses `Cheryl::NativeGLFW` or requires +`CHERYL_NATIVE_GLFW_SOURCE`. Bootstrapping suppresses other owners, demo and Engine +tests in a local variable scope, without rewriting a host's cache. + +This builds only the OpenGL owner and its dependencies: + +```sh +( + set -e + cd "$(git rev-parse --show-toplevel)" + cmake -S projects/modules/graphics/opengl -B build/opengl-module -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCHERYL_REPOSITORY_ROOT="$PWD" \ + -DCHERYL_NATIVE_GLFW_SOURCE="$PWD/projects/modules/platform/native-glfw" + cmake --build build/opengl-module --target module_opengl --parallel +) +``` + +Other module directories are standalone source entry points too. Module-local +checks use `CHERYL_BUILD_TESTS=ON` without enabling Engine's broader suites. +Hosts own their root `enable_testing()` policy. Independent consumers use the +owner's `tests/consumer/` entry point. Prebuilt selected owners are also available +through the [installed SDK](consuming-engine.md#prebuilt-development-sdks), which +uses the same public aliases and its producer's compiled configuration. + +Adding a source module beside a supplied SDK leaves the SDK's `Cheryl::Cheryl` +selection unchanged. Link that source module's target explicitly alongside the +SDK facade; its public feature definition enables its umbrella in the same include. + +## Add a module + +Resolve the boundary criteria above before adding a target. Then: + +1. Create an owner under `projects/modules///` with `include/`, `src/` + and `tests/`. Keep public contracts under `include/cheryl/`; implementation + helpers and SDK details stay private where the API permits it. Add one + owner-local `include/cheryl/modules/.h` umbrella that includes its public + contracts and excludes `internals/`. Support libraries use the corresponding + `include/cheryl/support/` location. Include the owner umbrella from its selected + group in `cheryl/modules.h` or `cheryl/support.h`. +2. Declare the owner using the [linkage-aware scaffold below](#library-scaffold). + [Module.cmake.in](../../../cmake/templates/Module.cmake.in) supplies the basic + source/test structure, but currently fixes `STATIC` and omits the library + configuration helper. Apply the linkage/export steps below rather than copying + it unchanged. `cheryl_prepare_module()` reuses or bootstraps Engine; pass + `REQUIRES_NATIVE` only when Native GLFW is a real prerequisite. +3. Declare shared metadata using the same ID in the files below. Add SDK discovery + inside the selected owner, reusing compatible supplied targets before owned + sources. Publish required header dependencies with `PUBLIC`; keep private + implementation dependencies `PRIVATE`. Call + `cheryl_register_public_component(target FEATURE CHERYL_HAS_)` from + [CherylPublicHeaders.cmake](../../../cmake/CherylPublicHeaders.cmake) to register + the owner's public feature definition and make the component available. + Selection through `cheryl_select_public_component(target)` then adds it to an + owned `Cheryl::Cheryl` assembly. Use the feature definition to guard its group + umbrella include. Do not rewrite supplied targets/cache. +4. Declare a root selection with `cheryl_option(name description default_value)` + in [CherylOptions.cmake](../../../cmake/CherylOptions.cmake), and conditionally add + the owner in the root [CMakeLists.txt](../../../CMakeLists.txt), including its + `cheryl_select_public_component` call. No unrelated module should select this + owner implicitly. For SDK distribution, explicitly add its target declaration, + public-header root and feature metadata to + [CherylSdk.cmake](../../../cmake/CherylSdk.cmake); component registration alone + does not install or export a new owner. Update packaging component/version + metadata and the installed consumer alongside that export. +5. Add owner-local behavior suites and an independent consumer with first-include + probes. Keep cross-owner cases in `projects/tests/`. Document the public alias, + dependency selection, lifetime/thread requirements and capability limits in the + module's page under `docs/site/modules/`, and add it to the relevant site + navigation. Make the owner's `README.md` a relative symlink to that page. + Register the public include root in the explicit inventory in + [build.py](../generation/build.py) and add the owner's visibility decorator to + `PREDEFINED` in [Doxyfile.in](../generation/Doxyfile.in). Extraction does not + follow includes into unselected roots. Check the generated API as described in + [Documentation tooling](documentation.md). + +| Metadata file | Module declaration | +| --- | --- | +| [CherylVersions.cmake](../../../cmake/CherylVersions.cmake) | `VERSION_PROJECT_MODULE_` | +| [CherylTargets.cmake](../../../cmake/CherylTargets.cmake) | `TARGET_LIB_MODULE_` and test target identities | +| [CherylOutputs.cmake](../../../cmake/CherylOutputs.cmake) | `OUTPUT_LIB_MODULE_` and runner artifact names | +| [CherylLinkage.cmake](../../../cmake/CherylLinkage.cmake) | Scoped `LINKAGE_PUBLIC_*` / `LINKAGE_PRIVATE_*` requirements | + +Declaration files reload metadata into the caller's scope; helper definitions are +guarded. Load helpers before declaring a standalone project so versions and target +names are available. The template collects only its owner's `src/*.cpp` with +`CONFIGURE_DEPENDS`, making source additions/removals trigger regeneration. Keep +conditional implementations, generated sources and tests in their own selections. + +### Library scaffold + +This example uses an illustrative `EXAMPLE` metadata ID and `Cheryl::Example` +alias. Declare its version, target, output and linkage variables in the metadata +files above first. From `projects/modules///CMakeLists.txt`: + +```cmake +cmake_minimum_required(VERSION 3.28) +include("${CMAKE_CURRENT_LIST_DIR}/../../../../cmake/CherylModules.cmake") +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + project(cheryl_example_module VERSION ${VERSION_PROJECT_MODULE_EXAMPLE} LANGUAGES CXX) +endif() +if(TARGET Cheryl::Example) + return() +endif() +cheryl_configure_module_options() +cheryl_prepare_module() + +file(GLOB_RECURSE example_sources CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") +add_library(${TARGET_LIB_MODULE_EXAMPLE} ${CHERYL_LIBRARY_TYPE} ${example_sources}) +add_library(Cheryl::Example ALIAS ${TARGET_LIB_MODULE_EXAMPLE}) +cheryl_register_public_component(${TARGET_LIB_MODULE_EXAMPLE} FEATURE CHERYL_HAS_EXAMPLE) +set_target_properties(${TARGET_LIB_MODULE_EXAMPLE} PROPERTIES + VERSION "${VERSION_PROJECT_MODULE_EXAMPLE}" + OUTPUT_NAME "${OUTPUT_LIB_MODULE_EXAMPLE}" + ARCHIVE_OUTPUT_DIRECTORY "${CHERYL_BUILD_ROOT}") +cheryl_configure_library(${TARGET_LIB_MODULE_EXAMPLE} EXAMPLE) +target_include_directories(${TARGET_LIB_MODULE_EXAMPLE} PUBLIC + "$") +target_link_libraries(${TARGET_LIB_MODULE_EXAMPLE} + PUBLIC ${LINKAGE_PUBLIC_LIB_MODULE_EXAMPLE} + PRIVATE ${LINKAGE_PRIVATE_LIB_MODULE_EXAMPLE}) + +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + cheryl_select_public_component(${TARGET_LIB_MODULE_EXAMPLE}) +endif() +if(CHERYL_BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() +if(CHERYL_BUILD_CONSUMER_TESTS) + add_subdirectory(tests/consumer) +endif() +``` + +`LINKAGE_PUBLIC_LIB_MODULE_EXAMPLE` includes `Cheryl::Engine` and any dependencies +used by public headers; private implementation libraries belong in the private +list. Register external implementation dependencies through the existing +`cheryl_private_dependency` / `cheryl_shared_dependency` helpers so ownership, +static link closure and SDK runtime collection remain visible to their owners. +See the [OpenGL CMake owner](../../../projects/modules/graphics/opengl/CMakeLists.txt) +for a complete current implementation with dependency discovery and test hooks. + +`CHERYL_LIBRARY_TYPE` follows `CHERYL_BUILD_SHARED_LIBS`. +`cheryl_configure_library(target EXAMPLE)` sets the producer's +`CE_BUILDING_EXAMPLE`, propagates `CE_EXAMPLE_STATIC` for static consumers, and +configures shared visibility, output paths, versioning and runtime paths. A shared +Cheryl module requires a shared Engine; the helper rejects a static Engine below +it. Do not replace this policy with `BUILD_SHARED_LIBS` or hand-written import flags. + +### Public symbol exports + +Give compiled public methods/functions their owning component's decorator. +`CE_API` belongs to Engine; exporting a module with it would assign the symbol to +the wrong DLL. Existing owner decorators live in +[export.h](../../../projects/engine/include/cheryl/export.h). A new owner can +provide an export header using the same policy: + +```cpp +#pragma once +#include + +#if defined(CE_EXAMPLE_STATIC) +#define CE_EXAMPLE_API +#elif defined(CE_BUILDING_EXAMPLE) +#define CE_EXAMPLE_API CE_DETAIL_EXPORT +#else +#define CE_EXAMPLE_API CE_DETAIL_IMPORT +#endif +``` + +Use `CE_EXAMPLE_API` on the compiled public functions the owner implements. +Public types whose separately exported methods require cross-library RTTI use +`CE_TYPE_API`; it preserves type visibility on supported ELF compilers and does +not replace the method decorator. Header-only templates need their definitions +available to consumers. Let the CMake target supply static/import/export flags, +and check both linkages through the owner's independent consumer. + +[CherylTests.cmake](../../../cmake/CherylTests.cmake) creates an owner aggregate with +`cheryl_add_test_aggregate(... OWNER)`; set `CHERYL_TEST_AGGREGATE` before adding +focused suites through `cheryl_add_test_suite`. Suites compile cases once into +object targets reused by focused, owner and assembly runners. Keep test hooks on +case targets so applications do not inherit them. [Test selection](testing.md) +covers runner options; [architecture validation](architecture-validation.md) +covers isolated graphs, supplied-target reuse and native acceptance. + +UI-specific font, alpha, input and publication requirements are in +[Writing a UI adapter](ui-adapters.md). diff --git a/docs/development/native-desktop-checks.md b/docs/site/development/native-desktop-checks.md similarity index 85% rename from docs/development/native-desktop-checks.md rename to docs/site/development/native-desktop-checks.md index 85740a52..c39b057e 100644 --- a/docs/development/native-desktop-checks.md +++ b/docs/site/development/native-desktop-checks.md @@ -1,4 +1,19 @@ -# Desktop smoke checks +(desktop-smoke-checks)= +# Desktop and controller checks + +These are interactive observations of a current native build, separate from +controlled-adapter checks. Prepare a usable display/GPU, readable input devices +for controller checks and the configured assets/fonts. Start with the demo's +[native QA preparation](../../../projects/apps/demo/README.md#native-qa-preparation) +when no matching executable exists; its configuration and explicit `demo` build +step establish the launch prerequisite. A configured directory alone is not a +refreshed executable. + +Use the [repeatable sequence](#repeatable-sequence) for rendering/input/reload and +normal shutdown, [Linux controller automation](#linux-controller-automation) for +the synthetic backend cases, and the physical-controller procedure below for real +reports. Record the source/configuration, actions and observations; a clean exit +alone does not establish visible or physical behavior. ## Repeatable sequence @@ -17,10 +32,10 @@ In each mode, check these observations: 2. In a toolkit-free build, press F2, type ASCII text, move the caret and use Backspace/Delete. Text focus prevents those keystrokes from moving the camera; Enter/Esc releases it. With UI adapters selected, use their - [interaction sequence](../../projects/apps/demo/README.md#interaction-checks); + [interaction sequence](../../../projects/apps/demo/README.md#interaction-checks); Escape releases UI focus. The builtin HUD uses the Unicode text service. Its multilingual, bidi/fallback and wrapping scope is maintained in the - [demo procedure](../../projects/apps/demo/README.md#unicode-preview-validation); this existing input + [demo procedure](../../../projects/apps/demo/README.md#unicode-preview-validation); this existing input sequence alone does not establish them. Probe editing still uses logical scalars. 3. Test indexed shader reload using the temporary-root launch block below. Press Escape to leave text editing, then F5 with valid definitions/sources. Append @@ -35,7 +50,7 @@ In each mode, check these observations: reloads. The process exits normally without hanging or printing a cleanup error. This block uses the refreshed `build/testing-graphics/demo` from the -[demo QA preparation](../testing-requests.md#tr19-qa-indexed-demo-materials-and-recovered-graphics), +[demo QA preparation](../../testing-requests.md#tr19-qa-indexed-demo-materials-and-recovered-graphics), or a matching native demo. It copies only shader/UI/font inputs, preserving the checkout and omitting the assets repository's Git metadata and bulk collections. Optional tile/sprite samples skip in this minimal root; that is not artwork QA. @@ -75,7 +90,7 @@ aggregates. Automation does not establish HID readiness or physical reports. Reuse the matching `build/testing-native-linux` directory and refresh its demo for targeted text/artwork observations; the demo's -[QA preparation](../../projects/apps/demo/README.md#native-qa-preparation) builds +[QA preparation](../../../projects/apps/demo/README.md#native-qa-preparation) builds only that target. When controller source or configuration requires new coverage, this procedure batches the relevant targets and exports the owned Gainput manager's compiler command. With HID disabled its HID macro arguments must be absent; inspect @@ -87,10 +102,10 @@ the printed result. Physical observations use the following controller checks. cd "$(git rev-parse --show-toplevel)" cmake -S . -B build/testing-native-linux -G Ninja \ -DCMAKE_BUILD_TYPE=Release -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ - -DBUILD_SHARED_LIBS=OFF \ + -DCHERYL_BUILD_SHARED_LIBS=OFF \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_SCAN_FOR_MODULES=OFF \ -DCMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE=PRE_TEST \ - -DCHERYL_LOG_PROFILE=developer -DCHERYL_SANDBOX_BUILD=OFF \ + -DCHERYL_LOG_PROFILE=developer \ -DCHERYL_BUILD_NATIVE_GLFW=ON -DCHERYL_BUILD_OPENGL=ON \ -DCHERYL_NATIVE_INPUT=ON -DCHERYL_NATIVE_NULL_PLATFORM=OFF \ -DGAINPUT_ENABLE_HID=OFF -DGLFW_BUILD_X11=ON -DGLFW_BUILD_WAYLAND=OFF \ @@ -134,7 +149,7 @@ PY Use the existing `build/testing-native-linux/demo` with developer logging, native input and GLFW's X11 backend selected, both optional UI adapters disabled, and `GAINPUT_ENABLE_HID=OFF`. This checks the -[Linux joystick path](../../projects/modules/platform/native-glfw/README.md#linux-controller-mapping); +[Linux joystick path](../modules/native-glfw.md#linux-controller-mapping); it does not establish direct HID report/output behavior or Windows notifications. The physical acceptance configuration is a Bluetooth DualSense. Other controllers and USB connections require their own observations. diff --git a/docs/site/development/publishing-documentation.md b/docs/site/development/publishing-documentation.md new file mode 100644 index 00000000..26304c74 --- /dev/null +++ b/docs/site/development/publishing-documentation.md @@ -0,0 +1,151 @@ +# Publishing documentation + +Publish the generated manual and API as a versioned Read the Docs site. Prepare +the artifact with [Documentation tooling](documentation.md), then use the +account, version and verification procedures below. Hosted setup requires the +repository/account permissions stated in the setup procedure. + +Before the first publication: + +1. Generate/check the intended revision using [Documentation tooling](documentation.md). +2. Complete the [account/site setup](#first-account-and-site-setup), recording its + actual project URL and desired active versions. +3. Confirm the [version policy](#version-policy) and separately approve stable + promotion before enabling its repository variable. +4. Apply the [publication checks](#publication-verification-and-recovery) to the hosted URL, including + exact revision, navigation/search and version selection. Local HTML success + does not establish hosted delivery. + +## Publishing contract + +The publishing configuration is +[.readthedocs.yaml](../../../.readthedocs.yaml), +[documentation.yml](../../../.github/workflows/documentation.yml) and +[sync-docs-stable.py](../../../.github/scripts/sync-docs-stable.py). Hosted publication +still requires the account and repository setup below. Both `develop` and `main` +must contain the engine headers and documentation implementation before their +respective sites can build. + +GitHub Actions generates and checks HTML for pull requests, pushes to `develop` +and `main`, `v*` tags, and manual runs. It retains checked HTML and build diagnostics +as downloadable artifacts for 30 days. Read the Docs independently clones the +selected Git ref, runs the same pinned generator, and hosts the resulting HTML. +Both publication paths request full generation with `--clean`. +Actions artifacts are not the website's source and do not need an upload token for +Read the Docs. Engine compilation belongs to the existing code CI; the documentation +job does not compile examples. + +Direct pushes to other branches do not schedule these validation jobs. Pull +requests targeting `develop` or `main` still run them. Keep workflow definitions +valid on every pushed branch: GitHub creates a failed run for each new commit +containing an invalid workflow, even on a branch excluded by its filters. Changing +branch filters or job conditions does not suppress those configuration errors; +correct the invalid YAML or expression. See +[GitHub's workflow-log guidance](https://docs.github.com/en/actions/how-tos/monitor-workflows/use-workflow-run-logs). + +The Read the Docs configuration uses Ubuntu 24.04 and Python 3.13, with one custom +`build.jobs.build.html` command: + +```yaml +version: 2 +build: + os: ubuntu-24.04 + tools: + python: "3.13" + jobs: + build: + html: + - python docs/site/generation/bootstrap.py exec --force-pinned -- python docs/site/generation/build.py --clean --output "$READTHEDOCS_OUTPUT" +``` + +Keep `sphinx`, `mkdocs` and a separate Python installation step out of this custom +configuration: bootstrap owns setup and generation. Read the Docs publishes the +`html/` directory beneath `$READTHEDOCS_OUTPUT`. Its +[custom build documentation](https://docs.readthedocs.com/platform/stable/build-customization.html) +and [configuration reference](https://docs.readthedocs.com/platform/stable/config-file/v2.html#build-jobs) +describe this contract. PDF and other builders are outside the current configuration. + +### Version policy + +| Website version | Git source | Policy | +| --- | --- | --- | +| `latest` | `develop` | Current development documentation. | +| `stable` | Explicit `stable` branch | Fast-forward mirror of a checked commit on `main`. | +| Release version, such as `vX.Y.Z` | Matching release tag | Historical documentation, activated individually or by an agreed tag rule. | + +Set the Read the Docs default branch to `develop`. An explicit Git branch named +`stable` supplies the stable version instead of Read the Docs automatically choosing +the greatest stable semantic version tag. Do not create a conflicting `stable` tag. +Activate desired historical tags and leave them visible for the version selector; +tags predating the documentation tooling cannot generate a site without a separate +backport policy. See [Read the Docs versions](https://docs.readthedocs.com/platform/stable/versions.html). + +The stable sync job is disabled unless the repository variable +`CHERYL_DOCS_PUBLISH_STABLE` equals the literal string `true`. Enable it only after +approval, the documentation merge into `main`, and account setup. The job must run +only for canonical-repository pushes to `main`, after documentation checks succeed; +it must never check out pull-request code with write credentials. Serialize stable +updates and compare the checked revision with the live `main` ref before updating. +Create `stable` if absent, otherwise fast-forward it without force. Reject divergence +and respect repository rules; do not delete, reset or force-update the branch to +bypass a rejection. GitHub's [ref API](https://docs.github.com/en/rest/git/refs#update-a-reference) +supports the required non-forced update. + +Read the Docs builds `latest` directly from `develop`; that build is independent of +the Actions result. The stable mirror advances only after the documentation job +succeeds. Branch protection and release policy determine which engine checks must +pass before a change reaches `main`. + +### First account and site setup + +1. Merge the documentation configuration into `develop`. Create an account at + [Read the Docs Community](https://app.readthedocs.org/), install its GitHub App + with access to `cppcooper/cheryl-engine`, and import that repository. Importing + requires repository administration access. The App subscribes to the required + events, so no additional manual webhook is needed. If the project was imported + manually, connect the repository in project settings. See the + [Git integration instructions](https://docs.readthedocs.com/platform/stable/reference/git-integration.html#adding-a-project-from-a-repository). +2. Record the actual project slug and public URL returned by Read the Docs; do not + assume the slug from the repository name. Set its default branch to `develop`, + activate `latest`, and check that version using the publication checks below. +3. Merge the approved release into `main`. Enable `CHERYL_DOCS_PUBLISH_STABLE=true` + only when stable publication is approved. Enabling the variable does not + trigger a workflow, and manual dispatch runs only the documentation checks. + The next qualifying `main` push creates or advances the `stable` branch; let + Read the Docs sync versions, then activate `stable`. Trigger a build to refresh + versions if it has not appeared yet. +4. After a successful stable build and revision check, choose `stable` as the + project's default website version. Activate existing release tags manually; + a future-tag activation rule does not replace that initial selection. + +### Publication verification and recovery + +Every successful HTML generation writes `html/revision.json`. Its +`source_revision` is the full checked Git commit, `source_ref` identifies the build +ref, and `dirty` records whether the source checkout differs from that commit. +The marker also records selected tool versions and lock/manifest hashes, without +machine-specific executable or cache paths. Source links must use the same full +commit; an overridden revision must match Git `HEAD`. + +For the first site and each publication investigation, fetch `revision.json` from +the actual version URL, for example `/revision.json`. Compare +`source_revision` exactly with the expected commit and the Read the Docs build's +reported commit; a successful build status or plausible page content is insufficient. +For `stable`, also compare the Git `stable` and intended checked `main` revisions. +Publication requires a clean source marker, successful internal links and fragments, +and Git source links resolving to objects at that commit. + +First-site acceptance also includes opening the manual and API entry points, +following an inherited base link and a declaring-header link, checking navigation +and search, and switching between `latest` and `stable` once both are active. +Check historical-version selection when a release tag containing the documentation +tooling is available; do not count that coverage before such a tag exists. Confirm +the GitHub App triggers the expected branch rebuilds. Local HTML checks do not +establish that the hosted version selector, search or webhook delivery works. + +To pause stable updates, set `CHERYL_DOCS_PUBLISH_STABLE` to `false`. Correct a bad +publication with a new reviewed commit on `main`, including a revert when restoring +earlier content, then let the normal fast-forward path rebuild `stable`. Do not move +the stable branch backward. If a ref diverges or repository rules reject an update, +inspect the refs and resolve the cause before retrying; do not force the mirror. +After recovery, repeat the exact revision and page checks against the public URL. diff --git a/docs/development/testing.md b/docs/site/development/testing.md similarity index 69% rename from docs/development/testing.md rename to docs/site/development/testing.md index 329fbd81..e897252c 100644 --- a/docs/development/testing.md +++ b/docs/site/development/testing.md @@ -1,10 +1,37 @@ -# Building and selecting tests +(building-and-selecting-tests)= +# Testing and validation Tests belong to the owner whose behavior they exercise. Engine uses controlled contract implementations; modules test their real integrations; `projects/tests/` -owns cross-module behavior. See [architecture validation](architecture-validation.md) +owns cross-module behavior. See [Component and SDK checks](architecture-validation.md) when changing isolation, composition or lifetime boundaries. +```{toctree} +:hidden: +:titlesonly: + +Component and SDK checks +Logging checks +Desktop and controller checks +``` + +## Choose checks for a change + +| Changed contract | Start with | Add when applicable | +| --- | --- | --- | +| Engine algorithm or lifecycle behavior | The affected focused suite in `tests-engine`. | Engine acceptance for cross-thread, failure or teardown boundaries. | +| Integration implementation | Its module's focused runner. | Coexistence checks when another owner participates; native/device observations for the actual backend. | +| Public header, target or dependency boundary | The owner's consumer and first-include probes. | Standalone/supplied-target checks when composition changes. | +| SDK installation or deployment | [Installed SDK consumers](architecture-validation.md#installed-sdk-consumers). | Static/shared and platform variants affected by the packaging change. | +| Logging failure or lifetime policy | [Logging checks](logging-acceptance.md). | The affected compile-profile rows and isolated failure drivers. | +| Visible desktop/controller behavior | [Desktop and controller checks](native-desktop-checks.md). | The relevant automated adapter/native regressions; neither substitutes for the other. | + +A **first-include probe** compiles a small translation unit with one public header +included first. It catches missing direct includes and unintended dependency +leakage. Its object-only success establishes header self-containment; the linked +consumer checks symbols and usage requirements, and runtime checks establish +behavior. Choose the layers affected by the change. + ## Runner selection `CHERYL_BUILD_TESTS=ON` selects focused GoogleTest runners and makes broader runners @@ -26,7 +53,7 @@ acceptance runners. Both require tests. Standalone module tests default to `OFF` Aggregates reuse focused and GoogleTest acceptance case objects. Build targets use `all-*`, but their executables use `tests-all-*`; `all-tests` produces `tests-all`. `tests-logging` produces `tests-engine-logging`. Other focused executable names -match their build targets. The [owner guides](../../projects/modules/README.md) +match their build targets. The [owner guides](../../../projects/modules/README.md) describe their coverage. Linux's separate `tests-native-joystick` uses syscall wrapping and stays outside aggregates. @@ -58,13 +85,13 @@ targets, and keep tests that share mutable files in isolated working directories | --- | --- | --- | | `acceptance-engine` | `tests-acceptance-engine` | Runtime, concurrency, failure and resource GoogleTests. | | `acceptance-opengl` | `tests-acceptance-opengl` | Native graphics/runtime GoogleTests; requires native input selection. | -| `acceptance-logging` | `tests-acceptance-engine-logging` | Fault/timeout/static-teardown driver via [logging acceptance](logging-acceptance.md). | +| `acceptance-logging` | `tests-acceptance-engine-logging` | Fault/timeout/static-teardown driver via [Logging checks](logging-acceptance.md). | | `acceptance-signal` | `tests-acceptance-engine-signal` | POSIX crash-bootstrap driver in Debug/Release. | | `acceptance-worker-topology-hwloc` | `tests-acceptance-worker-topology-hwloc` | Host topology/masks, inherited eligibility and CPU binding without memory placement; requires selected hwloc support and native capabilities. | | `backward-cpp` | `tests-dependency-backward-cpp` | Standalone dependency trace check. | Manual logging and signal drivers stay outside GoogleTest aggregates and CTest; -[architecture validation](architecture-validation.md#manual-acceptance-drivers) +[Component and SDK checks](architecture-validation.md#manual-acceptance-drivers) owns their invocations. `CHERYL_BUILD_CONSUMER_TESTS=ON` selects consumers independently of GoogleTest. @@ -72,7 +99,7 @@ Each consumer also builds its object-only first-include header probes. The Engin targets are `consumer-cengine` and `consumer-headers-cengine`; modules list their consumer targets in their owner guides. Configure each `tests/consumer/` entry point independently to establish standalone composition; a root consumer alone -cannot prove that boundary. See [independent consumers](consuming-engine.md#independent-consumers-and-header-probes). +cannot prove that boundary. See [independent consumers](architecture-validation.md#independent-consumers-and-header-probes). Selected worker support adds `consumer-worker-topology` and its neutral support-header probes; CTest registers that consumer under its exact target name without a suite @@ -81,7 +108,7 @@ separates enabled/disabled dependency selections and controlled/native coverage. ## CI and installed SDK checks -[GitHub CI](building.md#github-ci-and-sdk-artifacts) selects owner prefixes rather +[GitHub CI](ci.md) selects owner prefixes rather than aggregate duplicates. Headless jobs select Engine, logging, Engine acceptance, JSON/composition and the worker consumer. Modules jobs additionally select Native GLFW, OpenGL, both UI owners, miniaudio and coexistence, and run their independent @@ -100,12 +127,12 @@ identify skipped fixtures/capabilities as unresolved coverage. The Linux module adds `tests-acceptance-engine-terminal` and `tests-acceptance-engine-terminal-reports` when its tests are selected. They remain outside CTest/GoogleTest aggregates and are driven by -[terminal.py](../../projects/modules/platform/debug-terminal-linux/tests/acceptance/terminal.py), +[terminal.py](../../../projects/modules/platform/debug-terminal-linux/tests/acceptance/terminal.py), which selects deliberate failure/termination cases. The accepted -[configuration matrix](../../projects/modules/platform/debug-terminal-linux/README.md#configuration-matrix) +[configuration matrix](../modules/debug-terminal-linux.md#configuration-matrix) is reusable from the module guide; actual desktop observations remain -[TR18](../testing-requests.md#tr18-qa-linux-debug-terminal-on-a-real-desktop). -The [module guide](../../projects/modules/platform/debug-terminal-linux/README.md#test-reporting) +[TR18](../../testing-requests.md#tr18-qa-linux-debug-terminal-on-a-real-desktop). +The [module guide](../modules/debug-terminal-linux.md#test-reporting) explains saved runner reporting, discovery/headless suppression and supplied-GoogleTest fallback. Debug consumers can disable display with `--no-debug-terminal` when attached; omitted configurations pass that option through unchanged. @@ -119,9 +146,9 @@ attached; omitted configurations pass that option through unchanged. - RmlUi checks require its SDK sample font or an explicit `CHERYL_RMLUI_TEST_FONT`. - CPU text, tile and offline audio checks do not establish appearance or device output. Physical observations have separate procedures in the - [desktop guide](native-desktop-checks.md) and [audio guide](../../projects/modules/audio/miniaudio/README.md#acceptance-boundaries). + [Desktop and controller checks](native-desktop-checks.md) and [audio guide](../modules/miniaudio.md#acceptance-boundaries). An empty selection, skipped case or missing device leaves that coverage pending. Report the revision, configuration, platform and unavailable observations. The -[testing queue](../testing-requests.md) tracks pending checks and QA; owning plans track +[testing queue](../../testing-requests.md) tracks pending checks and QA; owning plans track implementation progress. Durable limitations belong in the relevant subject guide. diff --git a/docs/development/ui-adapters.md b/docs/site/development/ui-adapters.md similarity index 60% rename from docs/development/ui-adapters.md rename to docs/site/development/ui-adapters.md index 0ec4c42a..ce519e08 100644 --- a/docs/development/ui-adapters.md +++ b/docs/site/development/ui-adapters.md @@ -1,10 +1,11 @@ -# Writing a UI adapter +(writing-a-ui-adapter)= +# Write a UI adapter A UI adapter is an optional module that translates a toolkit into Cheryl's existing input, render and resource contracts. The toolkit owns its widgets, documents, styling and layout. Engine does not include toolkit headers or select -an adapter. The [TGUI](../../projects/modules/ui/tgui/README.md) and -[RmlUi](../../projects/modules/ui/rmlui/README.md) modules provide independent +an adapter. The [TGUI](../modules/tgui.md) and +[RmlUi](../modules/rmlui.md) modules provide independent examples under `CE::UI::TGUI` and `CE::UI::RmlUi`. Their public classes are toolkit-specific, not requirements for other adapters. @@ -15,7 +16,12 @@ candidate; it does not define player-facing widget architecture. ## Module and application ownership Give the adapter one library target, `include/`, `src/` and owner-local `tests/`. -Link its public target to `Cheryl::Engine` and the selected toolkit. Follow the +Link its public target to `Cheryl::Engine` and select toolkit linkage according to +that library's convention. The current TGUI and RmlUi integrations use shared +toolkit libraries and expose their native authoring headers/API through the +adapter target. Keep Engine-dependent service implementations in the adapter; +private versioned callbacks let the toolkit call those services without linking +another Engine copy when Cheryl itself is static. Follow the [module guide](modules.md) for supplied targets, standalone composition and dependency selection. Configure only an explicitly selected dependency; ordinary CMake configuration does not download it or change a host's toolkit settings. @@ -38,17 +44,34 @@ resolve the smallest demonstrated seam first. | --- | --- | --- | | Draw geometry and clipping | [Render submission](../rendering/pipelines-and-materials.md) | Record ordered, owned colored triangles and logical rectangular clips. Match the toolkit's alpha convention to the supplied material. | | Images and font atlases | [Immutable resource publication](../resources/resource-residency.md#immutable-resource-publication) | Copy CPU pixels into immutable generations; publish provider-owned resources on the platform owner. Resolve font ownership and sampling before exposing textures. | +| Font selection and loaded data | [Engine font service](../text/index.md) | Consume the configured default/catalogue and explicit retained `Font` handles; translate byte/index/style ownership into native toolkit fonts. | | Keyboard, pointer and text | [Input records and focus](../runtime/input-state-model.md) | Translate portable identities and committed text without native input APIs, invented characters or modification of another reader's records. | -| Time, size and OS services | [Runtime ticks](../runtime/runtime-frame-boundary.md) and capability reporting | Use copied window/framebuffer dimensions and explicit simulation time; report unavailable clipboard, cursor and IME services. | +| Time, size and OS services | [Selected views](../runtime/display-and-window-contract.md#committed-window-and-simulation-views), [service phases](../runtime/runtime-frame-boundary.md#runtime-service-phases) and capability reporting | Offer explicit owner attachments for automatic sizing/time while preserving manual use; report unavailable clipboard, cursor and IME services. | | Queued upload and retained playback | [Thread dispatch](../runtime/thread-dispatch.md) and [native retirement](../resources/resource-residency.md#native-retirement-and-maintenance) | Send owned preparation through platform submission and retain complete published scenes through replacement and teardown. | -The application supplies backend-appropriate pipelines and materials. Both demo -UI integrations retain [manual construction](../rendering/pipelines-and-materials.md#manual-material-construction); -indexed loading currently migrates only main text/image materials. An adapter -validates their vertex layout, topology, alpha/depth/culling state, projection -semantic and texture parameters. It does not create OpenGL pipelines or query a -native context. Font rasterization may belong to the toolkit; its resulting atlas -still follows the same CPU-copy and resource-publication boundary. +Adapters request [built-in 2D materials](../rendering/pipelines-and-materials.md#default-ui-materials) +through the provider-owned `CE::Assets::Shaders::Builder`, selecting the toolkit's +alpha convention. Engine owns the generic profiles; each graphics backend owns +its shader sources, bindings and construction. OpenGL embeds those sources, so +applications need no UI shader files or native material builder. This uses the +existing provider capability without a dependency from the adapter to OpenGL. + +Applications may pass compatible material handles loaded from JSON or constructed +manually as explicit overrides. The adapter validates vertex layout, topology, +alpha/depth/culling state, projection semantic and texture parameters. It does not +create OpenGL pipelines or query a native context. Unsupported providers reject +default construction while explicit compatible overrides remain usable. Engine +owns the loaded font bytes and face selection; native atlas/effect behavior stays +with the toolkit. An atlas follows the same CPU-copy and resource-publication boundary. + +The context-aware sessions import `Context::fonts()` once and establish native +default inheritance. Explicit `Font` handles select registered families through +session helpers without source-path extraction. Preserve actual face indices and +style metadata when registering memory with a toolkit, and retain that memory +until native shutdown. Use the service's ordered `fallbacks()` snapshot for a +toolkit with global glyph fallback; importing every catalogue family as a fallback +would make specialty fonts affect unrelated text. Reconfiguration does not rebind +existing sessions. A standalone input-only constructor remains available. Expanded triangles suffice for the current adapters. Indexing, batching and draw sorting require their own consumer need; preserve authored world/HUD/overlay order. @@ -65,14 +88,14 @@ The independent implementations differ where their toolkits differ: | --- | --- | --- | | Authoring | Native GUI/widgets; optional typed numeric placement and resizing. | Native context/documents; RML and RCSS layout, including percentage bounds and flex. | | Alpha | Straight vertex/image RGBA and `StraightAlpha` materials. | Premultiplied output and `PremultipliedAlpha` materials; file images convert once. | -| Fonts | Guarded FreeType backend and embedded default font. | Stock Core FreeType engine and explicitly registered owned font files/bytes. | +| Fonts | Configured Engine default/catalogue; retained face indices, real bold faces and glyph fallback through a custom backend. Native italic style uses geometry; explicit `FontStyle` selects an italic face. | Configured Engine default/catalogue; retained byte/index/style registration under native RCSS family names and the configured common glyph fallback chain. | | Sampling | Each draw retains nearest/linear smoothing independently of pixels; UI samplers clamp, with no mipmaps or anisotropy. | Existing image upload defaults; repeating UV/effect extensions reject. | | Toolkit lifetime | One guarded process-global backend. | One guarded Core initialization with contexts and interfaces. | Existing Engine contracts cover both initial scopes. Their CPU recordings and uploaders remain toolkit-owned implementations; matching names do not justify a -common widget or session API. The application chooses compatible materials and -presentation order for each view. +common widget or session API. The application chooses presentation order and may +override default materials for each view. Unsupported renderer features need an explicit scope and failure/capability contract. Rectangular clipping does not establish arbitrary masks, filters, @@ -81,15 +104,32 @@ when an implemented consumer demonstrates the requirement. ## Update, publication and shutdown -1. On platform, prepare application materials and the resource upload endpoint. -2. On simulation/UI, create the toolkit session and native presentation objects. +Record on the UI owner, upload on the provider owner, and publish complete values. +Frame preparation runs on simulation/UI; graphics upload and playback run on +platform in the runtime's native assembly. + +```mermaid +flowchart TB + accTitle: UI adapter publication boundaries + accDescr: The UI owner transfers an owned CPU recording to the upload owner. A complete scene returns to simulation for frame preparation, and retained packets reach playback without live toolkit objects. + ui["UI owner"] -->|owned CPU recording| upload["Upload owner"] + upload -->|complete scene| prepare["Frame preparation"] + prepare -->|retained packets| playback["Playback owner"] +``` + +1. On platform, prepare the resource upload endpoint. Default materials are + created lazily on that owner during the first nonempty upload. +2. Configure Engine fonts before consumers. On simulation/UI, create the toolkit + session from the Engine context, then native presentation objects. Keep all toolkit calls on that owner, including font and layout changes. -3. Each update supplies copied logical/framebuffer sizes, simulation time and the - immutable input stream. Logical UI coordinates and framebuffer pixels remain - distinct; zero drawable dimensions produce an empty recording. -4. Record owned CPU draw data, then submit it and retained materials for platform - upload. Toolkit vertices, pixel pointers, widgets and documents do not cross - that queue boundary. +3. Size/time may follow initialized owner sources through required attachments, + or be supplied manually. Context-aware TGUI/RmlUi sessions opt in with + `automatic_view`/`automatic_time`; defaults remain manual. Preserve the whole + input stream as an explicit application call. Logical UI coordinates and + framebuffer pixels remain distinct; zero drawable dimensions produce an empty recording. +4. Record owned CPU draw data, then submit it for platform upload, optionally + supplying retained custom material handles. Borrowed toolkit vertices, pixel + pointers, widgets and documents do not cross that queue boundary. 5. Adopt a replacement only when complete. A pending or failed replacement leaves the current scene usable. Bound outstanding replacements instead of queuing a new upload every tick. @@ -100,6 +140,14 @@ when an implemented consumer demonstrates the requirement. its context and global interfaces. A final transfer to platform is serial and quiescent. Retained Cheryl frames own resources independently of toolkit state. +Follow the [attachment contract](../runtime/event-delivery.md#observe-a-change-or-participate-in-an-operation) +for required integration. Apply a view seed before exposing a ready consumer, +acknowledge detach before releasing toolkit state, and propagate required apply +failures. A manual sizing call validates before detaching automatic mode. +Automatic step clocks have no replay and reject simultaneous manual advancement; +keep input/preemption and scene adoption ordering explicit. A queued observation +of the same payload is not a replacement for required owner participation. + ## Routing and unavailable services `CE::Input::Capture` and `CE::Input::Routing` are neutral protocols shared by @@ -148,12 +196,12 @@ record consumption or stale-lease interference, ordered retained passes, and resources surviving either toolkit's teardown. Keep shared functionality in Engine only when these independent implementations demonstrate its purpose. -The repository's [coexistence suite](../../projects/tests/ui-coexist/CMakeLists.txt) -owns that cross-project proof. The [demo](../../projects/apps/demo/README.md) +The repository's [coexistence suite](../../../projects/tests/ui-coexist/CMakeLists.txt) +owns that cross-project proof. The [demo](../../../projects/apps/demo/README.md) selects either adapter or both and supplies separate focus controls. Each module's standalone consumer and implementation checks remain independent. -The [demo interaction guide](../../projects/apps/demo/README.md#interaction-checks) +The [demo interaction guide](../../../projects/apps/demo/README.md#interaction-checks) owns native observations and selected-platform limits. A new backend, platform or supplied package needs its own applicable proof. @@ -161,7 +209,7 @@ supplied package needs its own applicable proof. Reuse `build/testing-native-ui` when its source, toolchain and configuration match. This Linux Release selection compiles both adapters, their consumers/probes, the -coexistence suite, OpenGL startup support and the UI-enabled demo with Cheryl-owned +coexistence suite, Startup with OpenGL integration and the UI-enabled demo with Cheryl-owned TGUI/RmlUi dependencies. It uses GLFW/X11/OpenGL with HID disabled. Initialize the pinned dependencies from the [build guide](building.md); RmlUi cases also require the SDK sample font or an explicit `CHERYL_RMLUI_TEST_FONT`. The executed consumers @@ -177,17 +225,16 @@ one owner is affected. A matching compiled build can be reused without rebuildin cd "$(git rev-parse --show-toplevel)" cmake -S . -B build/testing-native-ui -G Ninja \ -DCMAKE_BUILD_TYPE=Release -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ - -DBUILD_SHARED_LIBS=OFF \ + -DCHERYL_BUILD_SHARED_LIBS=OFF \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_CXX_SCAN_FOR_MODULES=OFF \ -DCMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE=PRE_TEST \ - -DCHERYL_LOG_PROFILE=developer -DCHERYL_SANDBOX_BUILD=OFF \ + -DCHERYL_LOG_PROFILE=developer \ -DCHERYL_BUILD_NATIVE_GLFW=ON -DCHERYL_BUILD_OPENGL=ON \ -DCHERYL_NATIVE_INPUT=ON -DCHERYL_NATIVE_NULL_PLATFORM=OFF \ -DGAINPUT_ENABLE_HID=OFF -DGLFW_BUILD_X11=ON -DGLFW_BUILD_WAYLAND=OFF \ -DCHERYL_BUILD_UI_TGUI=ON -DCHERYL_BUILD_UI_RMLUI=ON \ -DCHERYL_TGUI_SOURCE="$PWD/extern/tgui" \ -DCHERYL_RMLUI_SOURCE="$PWD/extern/rmlui" \ - -DCHERYL_RMLUI_PLACEHOLDER_FIX_VERIFIED=OFF \ -DCHERYL_BUILD_AUDIO_MINIAUDIO=OFF -DCHERYL_BUILD_DEMO=ON \ -DCHERYL_BUILD_TESTS=ON -DCHERYL_BUILD_CONSUMER_TESTS=ON \ -DCHERYL_BUILD_ALL_TESTS=OFF -DCHERYL_BUILD_ACCEPTANCE_TESTS=OFF @@ -203,22 +250,30 @@ one owner is affected. A matching compiled build can be reused without rebuildin Inspect the compile inventory when the RmlUi correction changes: Core must compile the generated `rmlui-fixes/WidgetTextInput.cpp` rather than the unguarded original. -The recorded root session/editing/lifetime/coexistence baseline is accepted, but does -not directly exercise `CE::Application::Startup`'s parser/backend factory, support headers, the new -`before_record` overload or placeholder hit-testing. These gaps remain in the -[owning checklist](../planning/develop-review-and-development-plan.md#startup-and-ui-follow-up); -font-weight fixture gaps remain in the [Unicode plan](../planning/short-term/unicode-text.md#progress). +The recorded root session/editing/lifetime/coexistence baseline is accepted. +The separate [Startup suite](../../../projects/modules/runtime/startup/tests/src/startup.cpp) +covers parser validation, factory forwarding and help/error paths before context +creation. Successful context/result/runtime handoff, the `before_record` overload +and placeholder hit-testing still need dedicated regressions in the +[owning checklist](../../planning/short-term/README.md#startup-and-ui-follow-up); +CPU and adapter fixtures cover registered weight/style selection and face indices; +automatic discovery's numeric-weight boundary cases remain in the +[font-selection plan](../../planning/short-term/README.md#font-selection-regressions). These checks do not +establish native visual correctness of distinct authored style faces. Corrected supplied RmlUi targets/packages require an independent consuming host and declaration/rejection checks under the -[module contract](../../projects/modules/ui/rmlui/README.md#placeholder-dependency-contract). +[module contract](../modules/rmlui.md#placeholder-dependency-contract). Owned-source success does not establish those supplied paths or standalone composition. Native startup, focus, placeholder hit-testing, layout and shutdown observations are accepted in the selected Linux root composition. The -[demo guide](../../projects/apps/demo/README.md#startup-and-ui-validation) retains +[demo guide](../../../projects/apps/demo/README.md#startup-and-ui-validation) retains the reusable native procedure. Builtin Unicode and sample artwork keep their own coverage limits there; dedicated deterministic cases and supplied dependencies remain outside that native baseline. -Sampling and shared indexed-material/resource changes postdate that accepted -baseline. Their targeted reruns are in the [testing queue](../testing-requests.md); -the earlier observations do not establish acceptance of the changed code. +Current sampling, retained-frame and positioned-text acceptance is recorded with +its reusable procedure in [Component and SDK checks](architecture-validation.md#sampling-and-positioned-text). +Keep that evidence separate from supplied-toolkit composition, distinct authored +font-face appearance and other desktop/material-failure observations. The +[testing queue](../../testing-requests.md) retains the outstanding observations; +an earlier native baseline does not establish a later changed path. diff --git a/docs/site/diagnostics/index.md b/docs/site/diagnostics/index.md new file mode 100644 index 00000000..fe09c4ac --- /dev/null +++ b/docs/site/diagnostics/index.md @@ -0,0 +1,24 @@ +(diagnostics)= +# Logging and diagnostics + +Configure useful output, inspect subsystem observations, and preserve failure +context through cleanup. The [Diagnostics system](../engine-systems/diagnostics.md) +introduces the available snapshots and their observation boundaries. Start with +logging for missing records or destination/filter settings, subsystem diagnostics +for a stalled or unexpected runtime/resource state, and failure reporting when an +operation throws or cleanup also fails. The output viewer displays process output; +it does not replace the log files or saved failure report. + +```{toctree} +:maxdepth: 1 +:titlesonly: + +Configure logging and investigate missing records <../runtime/logging> +Investigate runtime and resource symptoms <../runtime/subsystem-diagnostics> +Handle native and cleanup failures <../runtime/failure-reporting> +Linux output viewer <../modules/debug-terminal-linux> +``` + +A log record or counter is evidence about an operation, not a completion barrier. +Use the owning operation's future or lifecycle when another step depends on its +completion. diff --git a/docs/site/engine-systems/assets.md b/docs/site/engine-systems/assets.md new file mode 100644 index 00000000..02400b43 --- /dev/null +++ b/docs/site/engine-systems/assets.md @@ -0,0 +1,148 @@ +# Assets + +## Purpose + +Assets discovers source files and manifests, prepares owned CPU data and +publishes reusable images, sprites, tilesets, shaders and materials. Application +classes keep their own position, behavior and animation state while sharing +immutable assets. + +## Interface + +Types below are in `CE::Assets` unless another namespace is shown. + +### Types and operations + +| API and purpose | +| --- | +| {cpp:struct}`Loading::Loader `, {cpp:struct}`Loading::Prepared `
Prepare owned CPU input, upload on the provider owner, or synchronously `load_assets()`. [Loading entry point](../assets/asset-loading.md#runtime-entry-point); [publication and retry](../assets/asset-loading.md#publication-and-retry). | +| {cpp:class}`Loading::FileRegistry `, {cpp:struct}`Manifests::Graphics `
Discover indexed files and retain parsed manifest metadata. [Index selection](../assets/asset-manifests.md#index-selection-and-paths); [typed consumers](../assets/asset-loading.md#typed-manifest-data-and-consumers). | +| {cpp:struct}`Sprites::Manager `, {cpp:struct}`Tilesets::Manager `
Retrieve shared sprite/tile assets; validate handles before use. [Resources and caches](../assets/asset-values-and-playback.md#resources-and-caches); [sprite walkthrough](../getting-started/first-game.md#put-a-sprite-on-screen). | +| [Catalogue sources and exact-key changes](../assets/asset-loading.md#catalogue-changes)
Observe complete Published/Replaced/Reset commits, retain generation metadata and read native-domain provenance separately. Upload settlement remains one outcome per executed batch. | +| {cpp:struct}`Graphics::Asset `
Load a whole image or wrap an uploaded image as a retained quad with a default material. [Draw a whole image](../getting-started/how-to/draw-sprite.md#draw-a-whole-image). | +| {cpp:struct}`Sprites::Definition `, {cpp:struct}`Tilesets::Definition `, {cpp:struct}`Grids::Definition `
Describe immutable atlas grids, cells, clips and tile rules. [Grid coordinates](../assets/asset-values-and-playback.md#coordinates-and-grid-geometry); [manifest schema](../assets/asset-manifests.md). | +| {cpp:class}`Sprites::Animation `, {cpp:struct}`Tilesets::Animation `
Keep independent playback cursors over shared clip metadata. [Sprite playback](../assets/asset-values-and-playback.md#sprite-playback); [tile playback](../assets/asset-values-and-playback.md#tile-playback-and-rules). | + +### Selection values and enums + +| Type | Choices and details | +| --- | --- | +| {cpp:type}`Grids::CellIndex `, {cpp:struct}`Grids::View ` | Address cells and resolve authored grid geometry/UVs. [Grid and cell addressing](../assets/asset-manifests.md#grid-and-cell-addressing); [coordinates](../assets/asset-values-and-playback.md#coordinates-and-grid-geometry). | +| {cpp:enum}`Tilesets::Direction ` | Eight compass neighbors used by tile selection. [Tile selection](../assets/asset-values-and-playback.md#tile-selection). | +| {cpp:enum}`Tilesets::BitmaskType `, {cpp:enum}`Tilesets::WangType ` | Four/eight-neighbor masks or corner/edge Wang rules. [Tile rules](../assets/asset-values-and-playback.md#tile-playback-and-rules); [autotile schema](../assets/asset-manifests.md#autotiles). | +| {cpp:struct}`Sprites::Clip `, {cpp:struct}`Sprites::Profile ` | Select named clip timing and application-selected animation profiles. [Animations](../assets/asset-manifests.md#animations); [animation task](../getting-started/how-to/animate-sprite.md). | + +## Usage + +Load a root during platform initialization, then retain the resulting asset +handles in application-owned objects. For larger loads, split CPU preparation +from provider-owned upload; the application selects and schedules that split. + +```{engine-diagram} assets-focus-loading +``` + +The [application map](diagram-explorer.md#assets-application) includes the worker +and dispatch handoff; the [engine dependency map](diagram-explorer.md#assets-internal) +shows the caches and retained content behind this small loading path. + +### Loading stages and results + +| Stage | Result and owner | What becomes visible | +| --- | --- | --- | +| `Loader::prepare()` or `prepare_graphics(indexes)` | An owned {cpp:struct}`Assets::Loading::Prepared ` containing definitions, decoded pixels and shader bytes; CPU work can run on a worker. | No cache entries or native resources. Focused preparation decodes only images referenced by the selected indexes. | +| `Loader::upload()` | Consumes the prepared value on the active provider/loading owner. | Complete entries publish incrementally; only a successful batch replaces the loader's submitted-definition snapshots. Failure can leave earlier entries published. | +| Manager lookup | A retained sprite, tileset, image or shader/material generation. | The selected handle remains the same generation after later cache changes. Validate missing lookups before building game objects. | + +`load_assets(provider)` performs preparation and upload synchronously. Use +`prepare_graphics(indexes)` when only selected installed packages are needed. +Keep a prepared copy before moving it into upload if retry needs the exact same +input. The [loading guide](../assets/asset-loading.md#publication-and-retry) +owns the failure and shader-replacement rules. + +### Uses + +| Consumer | Dependency | Integration boundary | +| --- | --- | --- | +| `Loading::Loader` preparation | {cpp:class}`Files::FileIndex ` from [Utilities](utilities.md), manifest readers and image decoding | Discovers paths and owns the CPU inputs before graphics work begins. `FileRegistry` publishes discovered paths; it is separate from the discovery index. | +| Loader and asset managers | {cpp:struct}`Rendering::Resources::Provider ` from [Resources](resources.md) | Upload images/geometry and build compatible materials on the provider owner. Constructing another loader does not create another cache domain. | +| Loader and cache observation | {cpp:class}`Events::EventBus::Publisher ` from [Events](events.md) | Optional weak routes report one upload settlement per executed batch and separate committed catalogue changes per key. [Observation contracts](../assets/asset-loading.md#catalogue-changes). | + +[Workers](workers.md) and [Dispatch](dispatch.md) are application composition +choices for loading. `Loader` itself does not submit CPU jobs or platform requests. + +### Used by + +| Consumer | Retained input | Responsibility | +| --- | --- | --- | +| Application-owned actors and maps | {cpp:struct}`Assets::Sprites::Asset `, {cpp:struct}`Assets::Tilesets::Asset ` | Keep position, map cells and animation cursors outside shared definitions. [Asset values and playback](../assets/asset-values-and-playback.md). | +| [Rendering submission](rendering.md#usage) | Sprite/tile assets or {cpp:struct}`Assets::Graphics::Asset ` | Resolve selected cells and styles into retained draw packets; resolution does not advance animation. | +| Loading tools and resource browsers | Prepared definitions, `manifests()` and catalogue snapshots | Inspect CPU metadata and exact-key changes. A successful metadata snapshot is not an atomic view of all caches. | + +### Shared assets, independent playback + +Give each moving actor its own {cpp:class}`Assets::Sprites::Animation ` +while sharing the sprite asset. Advance it during game update, then copy the +selected cell into frame preparation. A clip entry index selects an animation +entry; that entry's cell selects atlas geometry. Tile playback similarly wraps +shared tileset data in an application-owned cursor. + +The [playback diagram](diagram-explorer.md#assets-focus-playback) separates +shared content from per-object state. Follow [animate a sprite](../getting-started/how-to/animate-sprite.md) +for the complete update and drawing route. Fonts use the separate [Text](text.md) +selection/layout path. + +## Contracts + +Preparation changes no global caches or native state. Upload is serialized on +the active provider/loading owner. Successful entries can become visible before +the whole upload finishes; failure does not imply rollback. Retain a prepared +copy before consuming it when retry needs that exact input. Existing keys and +explicit shader replacement have different update rules. + +An owned loader binds one immutable root, but another loader does not create a +second resource domain. The shared caches currently support one active +provider/loading-owner domain. Retained definitions and handles remain distinct +from a live backend's ability to use them. + +## Lifecycle + +Create an owned loader for an asset root, prepare CPU input and upload after +renderer initialization. Validate retrieved handles before constructing game +objects. Release the game's handles during cleanup; published frames may retain +their own copies until recycled. Native resource retirement follows the backend +domain, not loader destruction or the last manager lookup. + +## Execution + +CPU preparation may run on workers. Cache publication and resource upload obey +provider thread/current-context requirements. Advance animation and select +visible state on simulation; frame preparation copies selected cells and styles +without changing essential game state. + +## Integration + +On the platform owner during `init_platform()`: + +```cpp +CE::Assets::Loading::Loader loader(asset_root_); +loader.load_assets(engine_.resources()); +auto sprite = CE::Assets::Sprites::Manager::get().get_asset("first-game", "actor"); +``` + +Check `sprite` and required materials before constructing your player; a lookup +can return an empty handle. The [loading failure contract](../assets/asset-loading.md#publication-and-retry) +explains which earlier entries remain published after an upload error. Keep +player position and animation state in the player, not in shared asset metadata. See +[loading and drawing a sprite](../getting-started/first-game.md#put-a-sprite-on-screen). + +## Limits and further reading + +There is no engine-owned map/world or prescribed controller/Character class. Tile selection +operates on application-owned map data. Read [loading](../assets/asset-loading.md), +[manifests](../assets/asset-manifests.md), +[asset values and playback](../assets/asset-values-and-playback.md), +[file/font discovery](../assets/file-and-font-discovery.md), +[asset catalog](../assets/catalog.md) and +[residency](../resources/resource-residency.md). The +[Loader header](../../../projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h) +defines preparation/publication entry points. diff --git a/docs/site/engine-systems/audio.md b/docs/site/engine-systems/audio.md new file mode 100644 index 00000000..24c6679c --- /dev/null +++ b/docs/site/engine-systems/audio.md @@ -0,0 +1,143 @@ +# Audio + +## Purpose + +Audio plays independent voices from immutable clips or streamed files. Playback +has its own output clock and application-owned lifetime rather than depending +on render-frame publication or a runtime context adapter. + +## Interface + +Types below are in `CE::Audio` unless another namespace is shown. + +### Types and operations + +| API and purpose | +| --- | +| {cpp:class}`Clip `, {cpp:struct}`Format `
Own immutable interleaved float PCM and source rate/channel metadata through `from_samples()`. [Clips and voices](../runtime/audio.md#clips-and-voices). | +| {cpp:class}`System `
Play clips/streams, set master volume, maintain retired voices and close output. [Lifetime and scheduling](../runtime/audio.md#lifetime-and-scheduling); [adapter catalogue](../architecture/interfaces-adapters.md#integrations-alongside-the-runtime). | +| {cpp:class}`Voice `, {cpp:struct}`VoiceSnapshot `
Control independent playback and obtain a copied state/volume/loop/duration observation. [Playback controls](../runtime/audio.md#clips-and-voices). | +| {cpp:class}`Miniaudio::System `
Select device or offline output; use module decoding for file clips. [miniaudio setup](../modules/miniaudio.md#preparation-and-output). | +| {cpp:struct}`PlaybackEnded `, {cpp:struct}`PlaybackNotifications `
Observe logical generation outcomes through an explicit owner pump and reconcile retained current state after bounded loss. [Playback outcomes](../runtime/audio.md#playback-outcomes); [runtime pump](../modules/miniaudio.md#runtime-notification-pump). | + +### Playback values and choices + +| Type or choice | Purpose and details | +| --- | --- | +| {cpp:struct}`PlaybackOptions ` | Initial normalized volume, looping and paused state per voice. [Clip/stream submission](../runtime/audio.md#clips-and-voices). | +| {cpp:enum}`PlaybackState ` | `Playing`, `Paused`, `Finished`, `Stopped`, `Closed`; stopped/finished voices require explicit restart. [State transitions and closed handles](../runtime/audio.md#clips-and-voices). | +| Device / offline output | Device output advances independently; offline output advances only through explicit `render()` calls. [Backend selection](../runtime/audio.md#backends-and-limits); [miniaudio preparation](../modules/miniaudio.md#preparation-and-output). | +| Clip / streamed file | Retained decoded samples or file-backed playback whose source must remain available. [Sources and limits](../runtime/audio.md#clips-and-voices). | + +Use a decoded clip when several effects should share retained samples without +reopening a file. Stream longer tracks when retaining the whole decoded source +is undesirable; keep that file available during playback. The +[miniaudio preparation example](../modules/miniaudio.md#preparation-and-output) +shows both entry points. + +## Usage + +Own one selected audio system alongside the game, prepare clips at loading +boundaries and retain voice handles for playback needing later control. Audio +progresses on its output clock; its optional event pump is a separate owner task. + +```{engine-diagram} audio-application +``` + +The runtime maintenance attachment in this map is optional. Audio-only programs +need no Context, display or renderer. The [engine dependency map](diagram-explorer.md#audio-internal) +shows the neutral contracts and miniaudio implementation boundary. + +### Uses + +| Consumer | Dependency | Integration boundary | +| --- | --- | --- | +| {cpp:class}`Audio::System ` playback | {cpp:class}`Audio::Clip `, `Audio::Format` and `Audio::PlaybackOptions`, or a streamed file | Retain immutable PCM for independent voices; keep stream files available through playback and restart. The selected module owns decoding/mixing/output. | +| Outcome publication | [Events](events.md) {cpp:class}`Events::EventBus::Publisher ` | The designated owner explicitly pumps publication outside audio locks. Immediate listeners run there; queued listeners use their selected delivery adapter. | +| Miniaudio `pump_with()` | [Runtime](runtime.md) {cpp:class}`Runtime::RuntimeServices ` and {cpp:class}`Runtime::ServiceAttachment ` | Optional platform-maintenance notification pump. It retains weak audio access and does not own output or voice reclamation. | + +[Workers](workers.md) can run application-owned CPU file decoding. `Audio::System` +does not depend on an Engine worker pool, and playback does not wait for game +updates or render-frame publication. + +### Used by + +| Consumer | Audio value/operation | Responsibility | +| --- | --- | --- | +| Application sound/music controller | `System::play()` / `stream()` and retained {cpp:class}`Audio::Voice ` handles | Trigger prepared sources, control individual voices and explicitly close output after producers stop. Dropping a handle does not stop active playback. | +| Application state/UI observation | {cpp:struct}`Audio::VoiceSnapshot ` and optional {cpp:struct}`Audio::PlaybackEnded ` | Observe copied state or logical generation outcomes; select simulation delivery when a platform pump must reach simulation-owned state. | +| Offline processors and tests | `Audio::Miniaudio::System::open_offline()` and `render()` | Explicitly advance the mixer clock with complete output frames; no native device opens. [Output selection](../modules/miniaudio.md#preparation-and-output). | + +### Playback, maintenance and notifications + +| Operation | What it advances or releases | What the application must arrange | +| --- | --- | --- | +| Device output / offline `render()` | Audio playback time. | Choose real device or explicit offline output; opening and initial source preparation can block. | +| `maintain()` | Reclaims unreferenced inactive voices. | Call regularly while open. It neither advances audio time nor publishes callbacks. | +| `publish_notifications()` | Observes terminal facts and attempts pending event publication. | Call on the designated owner, or retain the optional `pump_with()` attachment. The attachment does not call `maintain()`. | +| `close()` | Stops output and retires voices; surviving handles report `Closed`. | Settle producers first. Pump final outcomes explicitly before removing subscriptions and releasing the system. | + +### Observe playback outcomes + +The optional `Audio::playback_ended_event` reports one logical terminal fact per +admitted generation: `Completed`, `Stopped`, `Replaced`, `OutputClosed` or +`Reclaimed`. Restart admits another generation while retaining voice identity; +pause/resume and loop iterations do not create generations. Miniaudio supports +this extension; `notification_snapshot().system == 0` means unsupported. + +Notifications use bounded storage and one publication attempt per retained +record. Use {cpp:struct}`Audio::PlaybackNotifications ` +to detect loss and a retained voice's current `snapshot().terminal` to reconcile +that generation. This does not recover superseded playback history. The +[playback outcome contract](../runtime/audio.md#playback-outcomes) explains +identity, reason selection and pump ownership; the +[miniaudio example](../modules/miniaudio.md#playback-notifications) supplies +subscription setup and removal. + +## Contracts + +Clips require complete nonempty finite mono/stereo PCM at supported source +rates. Volume is finite normalized linear gain in `[0, 1]`; mixing loud sources +can still exceed nominal amplitude. A stream file must remain available through +playback. Device opening, decode and initial stream opening can block. + +Dropping a voice handle does not stop active playback. Stop and natural finish +require explicit restart; toggling looping alone does not restart them. +Snapshots observe playback rather than freezing its native cursor. + +## Lifecycle + +Create output before submitting voices. Retain handles for voices needing later +control and call `maintain()` regularly to reclaim eligible unreferenced voices. +Maintenance does not advance audio time. Settle producers before system +destruction; idempotent `close()` stops output and retires voices before native +state. Surviving handles report `Closed` and reject controls. Construct a new +system for another session. + +## Execution + +Controls, submissions, snapshots and maintenance synchronize application +producers. Playback advances independently on the backend clock, without calling +game callbacks from mixing. Runtime concurrency does not change the audio +ownership contract. + +## Integration + +With an application-owned `audio` system and previously prepared `clip`: + +```cpp +auto voice = audio.play(clip, {.volume = 0.5f}); +// Keep voice if the game needs to pause, stop or change this playback. +audio.maintain(); +``` + +Create the system and decode clips at loading boundaries. Trigger prepared clips +from game state, then close output only after all producers stop. + +## Limits and further reading + +Offline processing is explicit; an unavailable device must not silently succeed +inaudibly. Spatial/world/collision behavior belongs to game code or another +selected capability. Read [audio ownership](../runtime/audio.md), +[miniaudio setup and coverage](../modules/miniaudio.md) and +[the System/Voice interface](../../../projects/engine/include/cheryl/audio/system.h). diff --git a/docs/site/engine-systems/diagnostics.md b/docs/site/engine-systems/diagnostics.md new file mode 100644 index 00000000..dae69d31 --- /dev/null +++ b/docs/site/engine-systems/diagnostics.md @@ -0,0 +1,143 @@ +# Diagnostics + +## Purpose + +Diagnostics exposes operational observations: session counts, queue activity, +resource retirement, worker policy, asset publication and memory bookkeeping. +It helps correlate a failure without changing the owning operation's semantics. + +## Interface + +Types below are in `CE::Diagnostics` unless another namespace is shown. + +### Types and observations + +| API and purpose | +| --- | +| {cpp:type}`DomainId `
Correlate one process-local owner through `diagnostic_id()` or snapshot domain fields. [Domain identities](../runtime/subsystem-diagnostics.md). | +| {cpp:struct}`CE::Runtime::RuntimeStats`
Inspect completed-session update, poll, frame and pressure counts. [Runtime statistics](../runtime/subsystem-diagnostics.md#runtime-statistics). | +| {cpp:struct}`CE::Workers::GroupStatus`, {cpp:struct}`DispatchStats `
Observe CPU work, queue activity and reported policy behavior. [Worker statistics](../runtime/subsystem-diagnostics.md#worker-statistics); [dispatcher statistics](../runtime/subsystem-diagnostics.md#dispatcher-statistics). | +| {cpp:struct}`CE::Events::EventStats`
Observe registration, dispatch and delivery activity. [Event statistics](../runtime/subsystem-diagnostics.md#event-statistics). | +| {cpp:struct}`CE::Assets::Loading::UploadStats`, {cpp:struct}`CE::Memory::Stats`
Inspect asset publication and synchronized CPU allocation bookkeeping. [Asset diagnostics](../runtime/subsystem-diagnostics.md#asset-diagnostics); [memory statistics](../runtime/subsystem-diagnostics.md#memory-statistics). | + +### Observation boundaries + +| Observation | Purpose and details | +| --- | --- | +| `GameRuntime::diagnostics()` | Owned completed-session counts after synchronization with run completion; live sampling rejects. [Runtime statistics](../runtime/subsystem-diagnostics.md#runtime-statistics). | +| Queue/group status | Activity at the snapshot boundary; an empty queue does not prove detached work finished. [Worker](../runtime/subsystem-diagnostics.md#worker-statistics) and [dispatcher](../runtime/subsystem-diagnostics.md#dispatcher-statistics) limits. | +| Resource retirement statistics | Tracked native resources and retirement/collection activity in one backend domain. [Native resource statistics](../runtime/subsystem-diagnostics.md#native-resource-statistics). | +| Explicit diagnostic report | A bounded operational summary outside sensitive locks/native callbacks; required emergency failure output has a separate path. [Failure reporting](../runtime/failure-reporting.md#emergency-reporting); [validation limits](../runtime/subsystem-diagnostics.md#validation-and-limits). | + +## Usage + +Read the service that owns the observation, retain its copied values, then +interpret them with the operation's result or failure. Diagnostics does not +create a global monitor that queries every subsystem for the application. + +```{engine-diagram} diagnostics-application +``` + +The [owner dependency view](diagram-explorer.md#diagnostics-internal) shows +the available snapshot families and their originating services. + +### Choose an observation boundary + +| Question | Query/result | When and how to interpret it | +| --- | --- | --- | +| How did this session progress? | `Runtime::GameRuntime::diagnostics()` → {cpp:struct}`Runtime::RuntimeStats ` | Synchronize with `run()` returning or throwing. Unstarted/completed sessions may be sampled; live sampling rejects. | +| Is worker work queued/running or using fallback policy? | `Workers::Group::status()` → {cpp:struct}`Workers::GroupStatus `; `policy()` → {cpp:struct}`Workers::GroupPolicy ` | Status observes activity/failures; policy reports effective eligibility and preferred fallback. Use each job's future for its result. | +| Is dispatched work still queued or detached? | Platform/simulation dispatcher `diagnostics()` → {cpp:struct}`Diagnostics::DispatchStats ` | `pending` omits detached work; also inspect `running`. A snapshot is not a join. | +| What happened to event delivery? | `Events::EventBus::diagnostics()` → {cpp:struct}`Events::EventStats ` | Registration, invocation, discard and queued-failure observations; no event payload/name sampling. | +| Did an upload publish anything before failing? | `Assets::Loading::Loader::diagnostics()` → {cpp:struct}`Assets::Loading::UploadStats ` | Last-attempt counts/completion can describe partial publication. Keep the original upload error; no batch rollback is implied. | +| Are cache entries still owned? | `Assets::Cache::Store::diagnostics()` → {cpp:struct}`Assets::Cache::Stats ` | Separates current entries from cumulative publications/replacements; clearing one cache does not drop every asset owner. | +| Which CPU ranges remain managed? | `Memory::Manager::diagnostics()` → {cpp:struct}`Memory::Stats ` | Synchronized byte-domain totals, free bytes, owners and partitions; not live-object or GPU-byte counts. | +| Are native handles awaiting retirement? | `Rendering::OpenGL::ResourceLifetime::diagnostics()` → {cpp:struct}`Rendering::OpenGL::ResourceStats ` | Compare live/pending/collected ownership on the selected backend; sampling performs no graphics collection. | + +Follow the [symptom, evidence and action table](../runtime/subsystem-diagnostics.md#investigate-a-runtime-symptom) +when deciding what to sample. It connects an observation to the owner whose +policy or lifetime may need investigation. + +### Uses + +| Observer | Dependency | Relationship | +| --- | --- | --- | +| Application report | The owner-specific snapshot APIs above | Copies observations under each owner's access rules; it does not acquire service ownership through the result. | +| Runtime/resource/work correlation | {cpp:type}`Diagnostics::DomainId ` | Joins observations from the same process-local owner. IDs are neither addresses nor persistent asset/session keys. | +| Ordinary formatted report | {cpp:class}`Logging::Logger ` | Selects a named destination after sampling, outside containing locks/native callbacks. [Logging](logging.md). | +| Required native/cleanup failure report | [Bounded emergency output](../runtime/failure-reporting.md#emergency-reporting) | Preserves failure context independently of ordinary logging and leaves the owning exception/future authoritative. | + +### Used by + +| Owner | Diagnostic identity/observation | Useful correlation | +| --- | --- | --- | +| [Runtime](runtime.md) | `Runtime::GameRuntime` / `Runtime::Context` domains and completed `RuntimeStats` | Connect a session outcome to update, polling and frame counts. | +| [Workers](workers.md), [Dispatch](dispatch.md), [Events](events.md) | Pool/group/dispatcher/bus domains and queue/delivery observations | Distinguish admission, pending, detached/running, completion and failure. | +| [Assets](assets.md) | Prepared batch, `Rendering::Resources::Provider` and cache domains | Connect CPU preparation, provider upload and actual publication without relying on source paths. | +| [Memory](memory.md), [Resources](resources.md) | Shared byte-domain and native resource-lifetime counts | Separate retained CPU backing from backend-native retirement. | +| [Display](display.md), [Input](input.md), [Rendering](rendering.md) | Explicit platform/native operational reports | Inspect capabilities, resize/focus epochs or supported debug observations without assuming every native call is traced. | + +### Retain values, compare domains + +A copied snapshot can outlive the observed service. Its values do not keep that +service alive, refresh themselves or make a query safe during destruction. +Retain the owner through the query and obey its thread/lifecycle boundary. + +Compare counters only within the domain and scope they describe. Memory growth +facades share the byte bookkeeping domain; worker group and physical pool IDs +identify different owners. A zero domain means unavailable/exhausted. Counts +sampled from different owners are not one atomic engine-wide snapshot. The +[diagnostics guide](../runtime/subsystem-diagnostics.md) defines each family's +sampling and reporting limits. + +## Contracts + +Counters are observations, not completion barriers, admission limits or resource +budgets. Domain IDs are monotonic process-local identities, not addresses/native +handles or durable keys; zero indicates exhaustion. Unavailable observations +must remain explicit. + +`GameRuntime::diagnostics()` requires caller synchronization with run start and +return; live sampling rejects. An empty dispatcher queue does not establish that +detached work completed. Asset completion counts do not establish batch rollback +or distinguish every reused key from a newly published entry without their +documented counters. + +## Lifecycle + +An owner acquires its identity with construction and publishes observations at +documented boundaries. Take a completed runtime snapshot after `run()` returns +or throws, retaining the runtime through that query. Explicit reports must run +outside containing locks/native callbacks according to the subsystem contract. +Retained snapshots own their values without retaining the observed service. + +## Execution + +Each snapshot follows the observed owner's access rules. Reporting can allocate, +format or log, so it is not suitable for arbitrary destructor/native callback +contexts. Emergency reporting has a deliberately separate bounded contract and +must not mask the original exception. + +## Integration + +After synchronizing with runtime completion: + +```cpp +const auto stats = runtime.diagnostics(); +const auto updates = stats.updates; +const auto rendered = stats.rendered; +``` + +Use these values in an application report with the run outcome. Different +update/render counts are expected with independent scheduling and skipped or +superseded visual snapshots. Use the +[symptom, evidence and action table](../runtime/subsystem-diagnostics.md#investigate-a-runtime-symptom) +to decide which observation to inspect before changing a scheduling or resource policy. + +## Limits and further reading + +Counters do not measure cache speedups, native thread residency or unselected +hardware behavior. Read [subsystem observation limits](../runtime/subsystem-diagnostics.md), +[failure reporting](../runtime/failure-reporting.md), +[architecture acceptance limits](../development/architecture-validation.md#evidence-limits) +and [diagnostic declarations](../../../projects/engine/include/cheryl/core/diagnostics.h). diff --git a/docs/site/engine-systems/diagram-explorer.md b/docs/site/engine-systems/diagram-explorer.md new file mode 100644 index 00000000..ed4844fa --- /dev/null +++ b/docs/site/engine-systems/diagram-explorer.md @@ -0,0 +1,48 @@ +# Diagram explorer + +Choose a subsystem, then compare its engine dependencies, application use and +focused views. Arrows point from the consumer toward what it uses. Select a type +or arrow for its source evidence and connections; public types also link to the +C++ API reference. + +Application views list the most relevant methods beneath their owning types. +These are selected entry points, not complete method inventories or a call +sequence. **Implement hooks** marks methods your game overrides for the engine +to call. Select a box for each method's purpose, source and available API link; +search also finds method names. + +Signatures use `method(arguments): ResultType`, with qualified Engine types and +public aliases for arguments and results. Diagram signatures omit parameter +names when their types are shown, declaration qualifiers such as `const`, +pointers/references, and ownership or completion markers. `[]` marks a collection. +The inspector retains those contract details; the API reference has the full +types. A missing result suffix can mean a scalar or standard-library result +and does not imply `void`. +When a view names a local namespace, its prefix is omitted in the diagram: +within Input, `Actions::Context` means `Input::Actions::Context`. Other +namespaces remain explicit, as do local names that would collide with another +namespace, such as `Text::Rendering` and `Input::Events`. Mixed diagnostics +and utility views keep their namespaces; the inspector retains full names. + +Application arrows include the argument and result types used by selected +methods. In the inspector, **Accepts** and **Returns** connect a method to those +types in the current view. Select an arrow to see the supporting method +relationship. Both use the same consumer-to-dependency direction; these arrows +do not describe data flow. The API reference supplies the complete signature, +including types omitted from the selected view. + +Use **Docs navigation** to keep the documentation sidebar, or **Wide explorer** +for a subsystem sidebar and more room to inspect a diagram. The selected view +is preserved when changing layouts or returning from a guide. + +Names retain their namespaces without the leading `CE::`. Primitive and `std::` +types are omitted. Rounded cream boxes describe illustrative application roles; +they are not Engine types. These are curated relationships rather than complete +declaration inventories. Detailed guides own the execution, lifetime and failure +contracts. + +The interactive explorer needs JavaScript and HTTP, as do the manual's other +Mermaid diagrams. The view index below remains available without JavaScript. + +```{engine-explorer} +``` diff --git a/docs/site/engine-systems/diagrams/assets-application.mmd b/docs/site/engine-systems/diagrams/assets-application.mmd new file mode 100644 index 00000000..2ec7c3eb --- /dev/null +++ b/docs/site/engine-systems/diagrams/assets-application.mmd @@ -0,0 +1,51 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Assets · application loading and playback + accDescr: Local names omit Assets::; ambiguous nested namespaces remain qualified. Arrows point toward the API or type used, not call order or data flow. Methods beneath type names are selected application entry points; Implement hooks marks engine-invoked overrides. Dashed arrows mark optional choices. + k0(["Application
loading work"]):::app + k1["Loading::
Loader


load_assets(Rendering::Resources::Provider)

prepare(): Loading::Prepared

upload(
Loading::Prepared,
Rendering::Resources::Provider,
replace_shaders)"]:::focal + k2["Loading::
Prepared"]:::focal + k3(["Application
init_platform()"]):::app + k4["Rendering::Resources::
Provider"]:::neighbor + k5(["Application-owned game objects"]):::app + k6["Sprites::
Manager


get_asset(name_space, name): Sprites::Manager::Handle"]:::focal + k7["Tilesets::
Manager


get_asset(key): Tilesets::Manager::Handle"]:::focal + k8["Sprites::
Asset


animation(animation, facing): Sprites::Animation"]:::focal + k9["Tilesets::
Asset


tile(cell): Tilesets::Tile

animation(name): Tilesets::Animation

cell_at(Grids::CellIndex, elapsed): Grids::CellIndex"]:::focal + k10(["Application
update()"]):::app + k11["Sprites::
Animation


advance(elapsed)

cell(): Grids::CellIndex"]:::focal + k12(["Application
prepare_render_frame()"]):::app + k13("Rendering::Submission::resolve_sprite(
Sprites::Asset,
Grids::CellIndex,
Rendering::DrawStyle2D,
Rendering::Submission::Context2D):
Rendering::DrawPacket2D"):::neighbor + k14["Rendering::
DrawPacket2D"]:::neighbor + k0 --> k1 + k1 --> k2 + k3 --> k4 + k1 --> k4 + k5 --> k6 + k5 --> k7 + k5 --> k8 + k5 --> k9 + k10 --> k11 + k12 --> k13 + k13 --> k8 + k13 --> k14 + k6 --> k8 + k7 --> k9 + k8 --> k11 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/assets-focus-loading.mmd b/docs/site/engine-systems/diagrams/assets-focus-loading.mmd new file mode 100644 index 00000000..75057892 --- /dev/null +++ b/docs/site/engine-systems/diagrams/assets-focus-loading.mmd @@ -0,0 +1,34 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Assets · simple loading and lookup + accDescr: Local names omit Assets::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0(["Application
init_platform()"]):::app + k1["Loading::
Loader"]:::focal + k2["Loading::
Prepared"]:::focal + k3["Rendering::Resources::
Provider"]:::neighbor + k4(["Application-owned game objects"]):::app + k5["Sprites::
Manager"]:::focal + k6["Sprites::
Asset"]:::focal + k0 --> k1 + k1 --> k2 + k1 --> k3 + k4 --> k5 + k5 --> k6 + k4 --> k6 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/assets-focus-playback.mmd b/docs/site/engine-systems/diagrams/assets-focus-playback.mmd new file mode 100644 index 00000000..07a68464 --- /dev/null +++ b/docs/site/engine-systems/diagrams/assets-focus-playback.mmd @@ -0,0 +1,38 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart TB + accTitle: Assets · practical shared sprite, separate playback + accDescr: Local names omit Assets::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0(["Application-owned game objects"]):::app + k1["Sprites::
Asset"]:::focal + k2(["Application
update()"]):::app + k3["Sprites::
Animation"]:::focal + k4["Sprites::
Clip"]:::focal + k5(["Application
prepare_render_frame()"]):::app + k6("Rendering::Submission::
resolve_sprite()"):::neighbor + k7["Rendering::
DrawStyle2D"]:::neighbor + k8["Rendering::
DrawPacket2D"]:::neighbor + k0 --> k1 + k2 --> k3 + k1 --> k3 + k3 --> k4 + k5 --> k6 + k6 --> k1 + k6 --> k7 + k6 --> k8 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/assets-internal.mmd b/docs/site/engine-systems/diagrams/assets-internal.mmd new file mode 100644 index 00000000..781ddbb7 --- /dev/null +++ b/docs/site/engine-systems/diagrams/assets-internal.mmd @@ -0,0 +1,58 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Assets · engine integration + accDescr: Local names omit Assets::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["Loading::
Loader"]:::focal + k1["Loading::
FileRegistry"]:::focal + k2["Files::
FileIndex"]:::neighbor + k3["Loading::
Prepared"]:::focal + k4["Rendering::Resources::
Provider"]:::neighbor + k5["Sprites::
Manager"]:::focal + k6["Tilesets::
Manager"]:::focal + k7["Cache::
Store"]:::focal + k8["Cache::
AssetCatalogue"]:::focal + k9["Memory::Objects::
Reservation"]:::neighbor + k10["Memory::Allocators::
ObjectPoolAllocator"]:::neighbor + k11["Memory::Objects::
Pool"]:::neighbor + k12["Sprites::
Asset"]:::focal + k13["Rendering::Resources::
Image"]:::neighbor + k14["Rendering::Geometry::
Buffer2D"]:::neighbor + k15("Rendering::Submission::
resolve_sprite()"):::neighbor + k16("Rendering::Submission::
resolve_tile()"):::neighbor + k17["Tilesets::
Asset"]:::focal + k18["Rendering::
DrawPacket2D"]:::neighbor + k0 --> k1 + k0 --> k2 + k0 --> k3 + k0 --> k4 + k0 --> k5 + k0 --> k6 + k5 --> k7 + k6 --> k7 + k7 --> k8 + k7 --> k9 + k9 --> k10 + k10 --> k11 + k12 --> k13 + k12 --> k14 + k15 --> k12 + k16 --> k17 + k15 --> k18 + k16 --> k18 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/audio-application.mmd b/docs/site/engine-systems/diagrams/audio-application.mmd new file mode 100644 index 00000000..5159b067 --- /dev/null +++ b/docs/site/engine-systems/diagrams/audio-application.mmd @@ -0,0 +1,59 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart TB + accTitle: Audio · application-owned playback and outcomes + accDescr: Local names omit Audio::; ambiguous nested namespaces remain qualified. Arrows point toward the API or type used, not call order or data flow. Methods beneath type names are selected application entry points; Implement hooks marks engine-invoked overrides. Dashed arrows mark optional choices. + k0(["Application
composition"]):::app + k1["Miniaudio::
System


open_device(Format, PlaybackNotificationOptions):
Miniaudio::System

publish_events_to(Events::EventBus::Publisher)

pump_with(Runtime::RuntimeServices):
Runtime::ServiceAttachment

publish_notifications()"]:::focal + k2(["Application
loading work"]):::app + k3("Miniaudio::decode_file(file, Miniaudio::DecodeOptions):
Clip"):::focal + k4["Clip

from_samples(Format, samples): Clip"]:::focal + k5(["Application
sound controller"]):::app + k6["System

play(Clip, PlaybackOptions): Voice

stream(file, PlaybackOptions): Voice

maintain()

close()"]:::focal + k7["PlaybackOptions"]:::focal + k8["Voice

snapshot(): VoiceSnapshot

pause()

resume()

stop()"]:::focal + k9["VoiceSnapshot"]:::focal + k10(["Application
listener setup"]):::app + k11["Events::
EventBus


register_listener(
Events::EventChannel<PlaybackEnded>,
Events::EventChannel<PlaybackEnded>::Callback,
Events::EventBus::Delivery,
Events::EventBus::ErrorHandler):
Events::EventBus::Registration

publisher(): Events::EventBus::Publisher"]:::neighbor + k12["PlaybackEnded"]:::focal + k13(["Application
init_platform()"]):::app + k14["Runtime::
RuntimeServices"]:::neighbor + k15["Runtime::
ServiceAttachment


reset()"]:::neighbor + k16(["Application
cleanup / quiesce()"]):::app + k17["Events::EventBus::
Publisher"]:::neighbor + k0 --> k1 + k2 -.-> k4 + k5 --> k6 + k5 --> k7 + k6 --> k4 + k6 --> k8 + k5 --> k8 + k8 --> k9 + k10 --> k11 + k10 --> k12 + k13 --> k1 + k1 --> k14 + k1 --> k15 + k13 --> k15 + k16 --> k6 + k2 --> k3 + k3 --> k4 + k6 --> k7 + k11 --> k17 + k1 --> k17 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/audio-internal.mmd b/docs/site/engine-systems/diagrams/audio-internal.mmd new file mode 100644 index 00000000..ba87bdf2 --- /dev/null +++ b/docs/site/engine-systems/diagrams/audio-internal.mmd @@ -0,0 +1,44 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Audio · engine integration + accDescr: Local names omit Audio::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["Miniaudio::
System"]:::focal + k1["System"]:::focal + k2["Clip"]:::focal + k3["PlaybackOptions"]:::focal + k4["Voice"]:::focal + k5["VoiceSnapshot"]:::focal + k6["PlaybackNotifications"]:::focal + k7["PlaybackEnded"]:::focal + k8["PlaybackTerminal"]:::focal + k9["Events::EventBus::
Publisher"]:::neighbor + k10["Runtime::
RuntimeServices"]:::neighbor + k11["Runtime::
ServiceAttachment"]:::neighbor + k0 --> k1 + k1 --> k2 + k1 --> k3 + k1 --> k4 + k4 --> k5 + k0 --> k6 + k7 --> k8 + k5 --> k8 + k0 --> k9 + k0 --> k10 + k0 --> k11 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/catalog.json b/docs/site/engine-systems/diagrams/catalog.json new file mode 100644 index 00000000..12d2b1ee --- /dev/null +++ b/docs/site/engine-systems/diagrams/catalog.json @@ -0,0 +1,37515 @@ +{ + "schema_version": 1, + "mermaid": "11.12.1", + "systems": [ + { + "id": "runtime", + "title": "Runtime" + }, + { + "id": "display", + "title": "Display" + }, + { + "id": "input", + "title": "Input" + }, + { + "id": "assets", + "title": "Assets" + }, + { + "id": "resources", + "title": "Resources" + }, + { + "id": "rendering", + "title": "Rendering" + }, + { + "id": "text", + "title": "Text" + }, + { + "id": "gui", + "title": "GUI" + }, + { + "id": "audio", + "title": "Audio" + }, + { + "id": "events", + "title": "Events" + }, + { + "id": "dispatch", + "title": "Dispatch" + }, + { + "id": "workers", + "title": "Workers" + }, + { + "id": "memory", + "title": "Memory" + }, + { + "id": "state", + "title": "State" + }, + { + "id": "logging", + "title": "Logging" + }, + { + "id": "diagnostics", + "title": "Diagnostics" + }, + { + "id": "utilities", + "title": "Utilities" + } + ], + "symbols": { + "Runtime::GameRuntime": { + "name": "CE::Runtime::GameRuntime", + "label": "Runtime::GameRuntime", + "kind": "class", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 59, + "brief": "Coordinates platform input, game simulation, rendering, presentation, and teardown.", + "symbol": "Runtime::GameRuntime", + "key": "Runtime::GameRuntime", + "system": "runtime", + "instance": "" + }, + "Runtime::Context": { + "name": "CE::Runtime::Context", + "label": "Runtime::Context", + "kind": "class", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 60, + "brief": "Owns a compatible set of platform, presentation, rendering, and resource adapters.", + "symbol": "Runtime::Context", + "key": "Runtime::Context", + "system": "runtime", + "instance": "" + }, + "Game::AbstractGame": { + "name": "CE::Game::AbstractGame", + "label": "Game::AbstractGame", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 26, + "brief": "Game simulation hooks driven by GameRuntime.", + "symbol": "Game::AbstractGame", + "key": "Game::AbstractGame", + "system": "runtime", + "instance": "" + }, + "Rendering::Frame": { + "name": "CE::Rendering::Frame", + "label": "Rendering::Frame", + "kind": "class", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 35, + "brief": "One reusable storage slot.", + "symbol": "Rendering::Frame", + "key": "Rendering::Frame", + "system": "rendering", + "instance": "" + }, + "Rendering::Renderer": { + "name": "CE::Rendering::Renderer", + "label": "Rendering::Renderer", + "kind": "class", + "file": "projects/engine/include/cheryl/core/rendering/renderer.h", + "line": 20, + "brief": "Renders using a compatible graphics context.", + "symbol": "Rendering::Renderer", + "key": "Rendering::Renderer", + "system": "rendering", + "instance": "" + }, + "Input::System": { + "name": "CE::Input::System", + "label": "Input::System", + "kind": "class", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 29, + "brief": "Platform-thread adapter.", + "symbol": "Input::System", + "key": "Input::System", + "system": "input", + "instance": "" + }, + "Runtime::RuntimeLifecycle": { + "name": "CE::Runtime::RuntimeLifecycle", + "label": "Runtime::RuntimeLifecycle", + "kind": "class", + "file": "projects/engine/include/cheryl/core/game-framework/runtime-lifecycle.h", + "line": 144, + "brief": "Retained synchronized observation, without a borrowed Runtime or adapter.", + "symbol": "Runtime::RuntimeLifecycle", + "key": "Runtime::RuntimeLifecycle", + "system": "runtime", + "instance": "" + }, + "Platform::DisplaySystem": { + "name": "CE::Platform::DisplaySystem", + "label": "Platform::DisplaySystem", + "kind": "class", + "file": "projects/engine/include/cheryl/core/display/display-system-interface.h", + "line": 17, + "brief": "Platform-owner display/window factory.", + "symbol": "Platform::DisplaySystem", + "key": "Platform::DisplaySystem", + "system": "display", + "instance": "" + }, + "Rendering::PresentationSurface": { + "name": "CE::Rendering::PresentationSurface", + "label": "Rendering::PresentationSurface", + "kind": "class", + "file": "projects/engine/include/cheryl/core/rendering/presentation-surface.h", + "line": 11, + "brief": "Presents a completed frame on a window owned by the display adapter.", + "symbol": "Rendering::PresentationSurface", + "key": "Rendering::PresentationSurface", + "system": "rendering", + "instance": "" + }, + "Rendering::Resources::Provider": { + "name": "CE::Rendering::Resources::Provider", + "label": "Rendering::Resources::Provider", + "kind": "struct", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 40, + "brief": "Creates resources in one selected backend domain.", + "symbol": "Rendering::Resources::Provider", + "key": "Rendering::Resources::Provider", + "system": "resources", + "instance": "" + }, + "Runtime::PlatformDispatcher": { + "name": "CE::Runtime::PlatformDispatcher", + "label": "Runtime::PlatformDispatcher", + "kind": "class", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 30, + "brief": "Transfers owned requests to the runtime's platform/graphics thread.", + "symbol": "Runtime::PlatformDispatcher", + "key": "Runtime::PlatformDispatcher", + "system": "dispatch", + "instance": "" + }, + "Workers::Group": { + "name": "CE::Workers::Group", + "label": "Workers::Group", + "kind": "class", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 127, + "brief": "A workload handle sharing physical capacity with other groups in its pool.", + "symbol": "Workers::Group", + "key": "Workers::Group", + "system": "workers", + "instance": "" + }, + "Events::EventBus": { + "name": "CE::Events::EventBus", + "label": "Events::EventBus", + "kind": "class", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 40, + "brief": "An owned named-event registry.", + "symbol": "Events::EventBus", + "key": "Events::EventBus", + "system": "events", + "instance": "" + }, + "Text::FontService": { + "name": "CE::Text::FontService", + "label": "Text::FontService", + "kind": "class", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 41, + "brief": "CPU font catalogue owned by an Engine context or used independently.", + "symbol": "Text::FontService", + "key": "Text::FontService", + "system": "text", + "instance": "" + }, + "Game::TickContext": { + "name": "CE::Game::TickContext", + "label": "Game::TickContext", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 21, + "brief": "One simulation update.", + "symbol": "Game::TickContext", + "key": "Game::TickContext", + "system": "runtime", + "instance": "" + }, + "Rendering::FrameWriter": { + "name": "CE::Rendering::FrameWriter", + "label": "Rendering::FrameWriter", + "kind": "class", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 119, + "brief": "Writes into a free slot after update().", + "symbol": "Rendering::FrameWriter", + "key": "Rendering::FrameWriter", + "system": "rendering", + "instance": "" + }, + "Input::Tick": { + "name": "CE::Input::Tick", + "label": "Input::Tick", + "kind": "class", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 53, + "brief": "Immutable State and ordered records for one independently scheduled update.", + "symbol": "Input::Tick", + "key": "Input::Tick", + "system": "input", + "instance": "" + }, + "Platform::GLFW::DisplaySystem": { + "name": "CE::Platform::GLFW::DisplaySystem", + "label": "Platform::GLFW::DisplaySystem", + "kind": "class", + "file": "projects/modules/platform/native-glfw/include/cheryl/core/display/display-system.h", + "line": 39, + "brief": "Owns a share of GLFW lifetime and all created windows on the platform thread.", + "symbol": "Platform::GLFW::DisplaySystem", + "key": "Platform::GLFW::DisplaySystem", + "system": "display", + "instance": "" + }, + "Platform::Window": { + "name": "CE::Platform::Window", + "label": "Platform::Window", + "kind": "class", + "file": "projects/engine/include/cheryl/core/display/window-interface.h", + "line": 20, + "brief": "Display-owned window accessed on the platform owner.", + "symbol": "Platform::Window", + "key": "Platform::Window", + "system": "display", + "instance": "" + }, + "Platform::GLFW::Window": { + "name": "CE::Platform::GLFW::Window", + "label": "Platform::GLFW::Window", + "kind": "class", + "file": "projects/modules/platform/native-glfw/include/cheryl/core/display/window.h", + "line": 25, + "brief": "Owns a GLFW window and its logical/framebuffer dimensions on the platform thread.", + "symbol": "Platform::GLFW::Window", + "key": "Platform::GLFW::Window", + "system": "display", + "instance": "" + }, + "Rendering::OpenGL::GLFWContext": { + "name": "CE::Rendering::OpenGL::GLFWContext", + "label": "Rendering::OpenGL::GLFWContext", + "kind": "class", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-context.h", + "line": 15, + "brief": "Adapts a display-owned GLFW window to the OpenGL presentation contract.", + "symbol": "Rendering::OpenGL::GLFWContext", + "key": "Rendering::OpenGL::GLFWContext", + "system": "rendering", + "instance": "" + }, + "Platform::Dimensions": { + "name": "CE::Platform::Dimensions", + "label": "Platform::Dimensions", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/display/viewport.h", + "line": 6, + "brief": "Pair of dimensions with caller-selected representation and units.", + "symbol": "Platform::Dimensions", + "key": "Platform::Dimensions", + "system": "display", + "instance": "" + }, + "Platform::FramebufferSize": { + "name": "CE::Platform::FramebufferSize", + "label": "Platform::FramebufferSize", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/display/framebuffer-size.h", + "line": 5, + "brief": "Copied drawable dimensions in framebuffer pixels; zero extent can represent a minimized window.", + "symbol": "Platform::FramebufferSize", + "key": "Platform::FramebufferSize", + "system": "display", + "instance": "" + }, + "Platform::WindowResized": { + "name": "CE::Platform::WindowResized", + "label": "Platform::WindowResized", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/display/window-interface.h", + "line": 42, + "brief": "Payload of window_resized_event and legacy \"window-resized\": copied pixels and a display-borrowed window pointer.", + "symbol": "Platform::WindowResized", + "key": "Platform::WindowResized", + "system": "display", + "instance": "" + }, + "Events::EventSystem": { + "name": "CE::Events::EventSystem", + "label": "Events::EventSystem", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/subsystems/event-system.h", + "line": 13, + "brief": "Global access to the default persistent event bus.", + "symbol": "Events::EventSystem", + "key": "Events::EventSystem", + "system": "events", + "instance": "" + }, + "Rendering::Camera2D": { + "name": "CE::Rendering::Camera2D", + "label": "Rendering::Camera2D", + "kind": "class", + "file": "projects/engine/include/cheryl/core/rendering/camera.h", + "line": 42, + "brief": "Y-up GLM orthographic camera spanning [0,width] by [0,height], near/far 0/1.", + "symbol": "Rendering::Camera2D", + "key": "Rendering::Camera2D", + "system": "rendering", + "instance": "" + }, + "Platform::GLFW::InputSystem": { + "name": "CE::Platform::GLFW::InputSystem", + "label": "Platform::GLFW::InputSystem", + "kind": "class", + "file": "projects/modules/platform/native-glfw/include/cheryl/core/controls/input-system.h", + "line": 53, + "brief": "GLFW/Gainput input adapter on the platform owner, available only with CHERYL_NATIVE_INPUT.", + "symbol": "Platform::GLFW::InputSystem", + "key": "Platform::GLFW::InputSystem", + "system": "input", + "instance": "" + }, + "Input::Actions::Bindings": { + "name": "CE::Input::Actions::Bindings", + "label": "Input::Actions::Bindings", + "kind": "class", + "file": "projects/engine/include/cheryl/core/controls/input-bindings.h", + "line": 42, + "brief": "Owns physical-to-semantic mappings.", + "symbol": "Input::Actions::Bindings", + "key": "Input::Actions::Bindings", + "system": "input", + "instance": "" + }, + "Input::Actions::ContextSubmission": { + "name": "CE::Input::Actions::ContextSubmission", + "label": "Input::Actions::ContextSubmission", + "kind": "class", + "file": "projects/engine/include/cheryl/core/controls/action-context.h", + "line": 65, + "brief": "Retained request endpoint acquired on the binding/poll owner.", + "symbol": "Input::Actions::ContextSubmission", + "key": "Input::Actions::ContextSubmission", + "system": "input", + "instance": "" + }, + "Input::Actions::ContextDefinition": { + "name": "CE::Input::Actions::ContextDefinition", + "label": "Input::Actions::ContextDefinition", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/controls/action-context.h", + "line": 32, + "brief": "Owned context definition, copied/configured before acceptance; no device is borrowed.", + "symbol": "Input::Actions::ContextDefinition", + "key": "Input::Actions::ContextDefinition", + "system": "input", + "instance": "" + }, + "Input::Actions::Context": { + "name": "CE::Input::Actions::Context", + "label": "Input::Actions::Context", + "kind": "class", + "file": "projects/engine/include/cheryl/core/controls/action-context.h", + "line": 42, + "brief": "One requested action namespace.", + "symbol": "Input::Actions::Context", + "key": "Input::Actions::Context", + "system": "input", + "instance": "" + }, + "Input::Capture::Collector": { + "name": "CE::Input::Capture::Collector", + "label": "Input::Capture::Collector", + "kind": "class", + "file": "projects/engine/include/cheryl/core/controls/input-capture.h", + "line": 47, + "brief": "Platform-owned record collector; capture requests may come from any thread.", + "symbol": "Input::Capture::Collector", + "key": "Input::Capture::Collector", + "system": "input", + "instance": "" + }, + "Input::Routing::Manager": { + "name": "CE::Input::Routing::Manager", + "label": "Input::Routing::Manager", + "kind": "class", + "file": "projects/engine/include/cheryl/core/controls/input-routing.h", + "line": 57, + "brief": "Thread-safe focus requests, independent of State mapping and capture.", + "symbol": "Input::Routing::Manager", + "key": "Input::Routing::Manager", + "system": "input", + "instance": "" + }, + "Input::PollSnapshot": { + "name": "CE::Input::PollSnapshot", + "label": "Input::PollSnapshot", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/controls/poll-snapshot.h", + "line": 12, + "brief": "One complete poll: State and independent ordered capture records.", + "symbol": "Input::PollSnapshot", + "key": "Input::PollSnapshot", + "system": "input", + "instance": "" + }, + "Input::PollingBacklog": { + "name": "CE::Input::PollingBacklog", + "label": "Input::PollingBacklog", + "kind": "class", + "file": "projects/engine/include/cheryl/core/controls/polling-backlog.h", + "line": 26, + "brief": "Platform-to-simulation polling batch.", + "symbol": "Input::PollingBacklog", + "key": "Input::PollingBacklog", + "system": "input", + "instance": "" + }, + "Input::Actions::View": { + "name": "CE::Input::Actions::View", + "label": "Input::Actions::View", + "kind": "class", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 108, + "brief": "Local action queries for one namespace in a shared immutable Tick.", + "symbol": "Input::Actions::View", + "key": "Input::Actions::View", + "system": "input", + "instance": "" + }, + "UI::TGUI::Session": { + "name": "CE::UI::TGUI::Session", + "label": "UI::TGUI::Session", + "kind": "class", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 51, + "brief": "Owns one toolkit process-global session on its creating simulation/UI thread.", + "symbol": "UI::TGUI::Session", + "key": "UI::TGUI::Session", + "system": "gui", + "instance": "" + }, + "UI::RmlUi::Session": { + "name": "CE::UI::RmlUi::Session", + "label": "UI::RmlUi::Session", + "kind": "class", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 53, + "brief": "Owns one toolkit process-global session on its creating simulation/UI thread.", + "symbol": "UI::RmlUi::Session", + "key": "UI::RmlUi::Session", + "system": "gui", + "instance": "" + }, + "Assets::Loading::Loader": { + "name": "CE::Assets::Loading::Loader", + "label": "Assets::Loading::Loader", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 88, + "brief": "One immutable root; owned loaders can share the one supported cache domain.", + "symbol": "Assets::Loading::Loader", + "key": "Assets::Loading::Loader", + "system": "assets", + "instance": "" + }, + "Assets::Loading::FileRegistry": { + "name": "CE::Assets::Loading::FileRegistry", + "label": "Assets::Loading::FileRegistry", + "kind": "class", + "file": "projects/engine/include/cheryl/core/resources/asset-management/file-registry.h", + "line": 24, + "brief": "Synchronized path registry for files without typed resource loading.", + "symbol": "Assets::Loading::FileRegistry", + "key": "Assets::Loading::FileRegistry", + "system": "assets", + "instance": "" + }, + "Files::FileIndex": { + "name": "CE::Files::FileIndex", + "label": "Files::FileIndex", + "kind": "class", + "file": "projects/engine/include/cheryl/core/resources/fileio/file-mgr.h", + "line": 21, + "brief": "Incremental file index by extension beneath explicitly supplied directories.", + "symbol": "Files::FileIndex", + "key": "Files::FileIndex", + "system": "utilities", + "instance": "" + }, + "Assets::Loading::Prepared": { + "name": "CE::Assets::Loading::Prepared", + "label": "Assets::Loading::Prepared", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 35, + "brief": "Owned transferable CPU batch; preparation changes no runtime caches or registry.", + "symbol": "Assets::Loading::Prepared", + "key": "Assets::Loading::Prepared", + "system": "assets", + "instance": "" + }, + "Assets::Sprites::Manager": { + "name": "CE::Assets::Sprites::Manager", + "label": "Assets::Sprites::Manager", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/resources/asset-management/sprite-mgr.h", + "line": 25, + "brief": "Retained namespace:name cache.", + "symbol": "Assets::Sprites::Manager", + "key": "Assets::Sprites::Manager", + "system": "assets", + "instance": "" + }, + "Assets::Tilesets::Manager": { + "name": "CE::Assets::Tilesets::Manager", + "label": "Assets::Tilesets::Manager", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/resources/asset-management/tileset-mgr.h", + "line": 22, + "brief": "Retained namespace:name cache.", + "symbol": "Assets::Tilesets::Manager", + "key": "Assets::Tilesets::Manager", + "system": "assets", + "instance": "" + }, + "Assets::Cache::Store": { + "name": "CE::Assets::Cache::Store", + "label": "Assets::Cache::Store", + "kind": "struct", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 114, + "brief": "Caches constructed assets by key.", + "symbol": "Assets::Cache::Store", + "key": "Assets::Cache::Store", + "system": "assets", + "instance": "" + }, + "Assets::Cache::AssetCatalogue": { + "name": "CE::Assets::Cache::AssetCatalogue", + "label": "Assets::Cache::AssetCatalogue", + "kind": "class", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 90, + "brief": "Retained catalogue observations, independent of Store/native lifetime.", + "symbol": "Assets::Cache::AssetCatalogue", + "key": "Assets::Cache::AssetCatalogue", + "system": "assets", + "instance": "" + }, + "Memory::Objects::Reservation": { + "name": "CE::Memory::Objects::Reservation", + "label": "Memory::Objects::Reservation", + "kind": "class", + "file": "projects/engine/include/cheryl/core/resources/objects/object-reservation.hpp", + "line": 52, + "brief": "Reserves one CE::Memory::Block and tracks only its unclaimed ranges.", + "symbol": "Memory::Objects::Reservation", + "key": "Memory::Objects::Reservation", + "system": "memory", + "instance": "" + }, + "Memory::Allocators::ObjectPoolAllocator": { + "name": "CE::Memory::Allocators::ObjectPoolAllocator", + "label": "Memory::Allocators::ObjectPoolAllocator", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/resources/memory/allocators/object-pool-allocator.hpp", + "line": 19, + "brief": "Adapts Pool to an allocator and retains the PoolState captured at construction.", + "symbol": "Memory::Allocators::ObjectPoolAllocator", + "key": "Memory::Allocators::ObjectPoolAllocator", + "system": "memory", + "instance": "" + }, + "Memory::Objects::Pool": { + "name": "CE::Memory::Objects::Pool", + "label": "Memory::Objects::Pool", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/resources/objects/pool.h", + "line": 42, + "brief": "Singleton entry point for allocating objects.", + "symbol": "Memory::Objects::Pool", + "key": "Memory::Objects::Pool", + "system": "memory", + "instance": "" + }, + "Assets::Sprites::Asset": { + "name": "CE::Assets::Sprites::Asset", + "label": "Assets::Sprites::Asset", + "kind": "struct", + "file": "projects/engine/include/cheryl/assets/types/2d/sprite.h", + "line": 56, + "brief": "Retains a definition snapshot and grid resources.", + "symbol": "Assets::Sprites::Asset", + "key": "Assets::Sprites::Asset", + "system": "assets", + "instance": "" + }, + "Rendering::Resources::Image": { + "name": "CE::Rendering::Resources::Image", + "label": "Rendering::Resources::Image", + "kind": "struct", + "file": "projects/engine/include/cheryl/assets/resources/image.h", + "line": 16, + "brief": "Immutable uploaded contents/dimensions in the selected backend domain.", + "symbol": "Rendering::Resources::Image", + "key": "Rendering::Resources::Image", + "system": "resources", + "instance": "" + }, + "Rendering::Geometry::Buffer2D": { + "name": "CE::Rendering::Geometry::Buffer2D", + "label": "Rendering::Geometry::Buffer2D", + "kind": "struct", + "file": "projects/engine/include/cheryl/assets/resources/geometry2d.h", + "line": 26, + "brief": "Retained immutable vertex upload, independent of material/image bindings.", + "symbol": "Rendering::Geometry::Buffer2D", + "key": "Rendering::Geometry::Buffer2D", + "system": "resources", + "instance": "" + }, + "Rendering::Submission::resolve_sprite()": { + "symbol": "Rendering::Submission::resolve_sprite()", + "label": "Rendering::Submission::resolve_sprite()", + "name": "CE::Rendering::Submission::resolve_sprite", + "kind": "operation", + "brief": "Resolve one sprite grid cell into a validated retained CPU packet.", + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "key": "Rendering::Submission::resolve_sprite()", + "system": "rendering", + "instance": "" + }, + "Rendering::Submission::resolve_tile()": { + "symbol": "Rendering::Submission::resolve_tile()", + "label": "Rendering::Submission::resolve_tile()", + "name": "CE::Rendering::Submission::resolve_tile", + "kind": "operation", + "brief": "Resolve a checked tileset grid cell as one four-vertex strip packet.", + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 48, + "key": "Rendering::Submission::resolve_tile()", + "system": "rendering", + "instance": "" + }, + "Assets::Tilesets::Asset": { + "name": "CE::Assets::Tilesets::Asset", + "label": "Assets::Tilesets::Asset", + "kind": "struct", + "file": "projects/engine/include/cheryl/assets/types/2d/tileset.h", + "line": 68, + "brief": "Shared tile grid plus definitions for static cells, animated targets, and autotile rules.", + "symbol": "Assets::Tilesets::Asset", + "key": "Assets::Tilesets::Asset", + "system": "assets", + "instance": "" + }, + "Rendering::DrawPacket2D": { + "name": "CE::Rendering::DrawPacket2D", + "label": "Rendering::DrawPacket2D", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 32, + "brief": "Fully resolved CPU submission.", + "symbol": "Rendering::DrawPacket2D", + "key": "Rendering::DrawPacket2D", + "system": "rendering", + "instance": "" + }, + "UI::TGUI::SceneUploader": { + "name": "CE::UI::TGUI::SceneUploader", + "label": "UI::TGUI::SceneUploader", + "kind": "class", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 51, + "brief": "Provider-domain upload/cache handle constructed on platform.", + "symbol": "UI::TGUI::SceneUploader", + "key": "UI::TGUI::SceneUploader", + "system": "gui", + "instance": "" + }, + "UI::RmlUi::SceneUploader": { + "name": "CE::UI::RmlUi::SceneUploader", + "label": "UI::RmlUi::SceneUploader", + "kind": "class", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 46, + "brief": "Provider-domain upload/cache handle constructed on platform.", + "symbol": "UI::RmlUi::SceneUploader", + "key": "UI::RmlUi::SceneUploader", + "system": "gui", + "instance": "" + }, + "Text::Rendering::upload()": { + "symbol": "Text::Rendering::upload()", + "label": "Text::Rendering::upload()", + "name": "CE::Text::Rendering::upload", + "kind": "operation", + "brief": "Upload one complete retained generation on the provider owner/current context.", + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 116, + "key": "Text::Rendering::upload()", + "system": "text", + "instance": "" + }, + "Rendering::OpenGL::ResourceProvider": { + "name": "CE::Rendering::OpenGL::ResourceProvider", + "label": "Rendering::OpenGL::ResourceProvider", + "kind": "class", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/resource-provider.h", + "line": 23, + "brief": "Translate backend-neutral resource requests into OpenGL images, buffers, and programs.", + "symbol": "Rendering::OpenGL::ResourceProvider", + "key": "Rendering::OpenGL::ResourceProvider", + "system": "resources", + "instance": "" + }, + "Rendering::Resources::Program": { + "name": "CE::Rendering::Resources::Program", + "label": "Rendering::Resources::Program", + "kind": "struct", + "file": "projects/engine/include/cheryl/assets/resources/shader.h", + "line": 33, + "brief": "Mutable executable-program access on the backend owner/current context.", + "symbol": "Rendering::Resources::Program", + "key": "Rendering::Resources::Program", + "system": "resources", + "instance": "" + }, + "Rendering::GraphicsDomain": { + "name": "CE::Rendering::GraphicsDomain", + "label": "Rendering::GraphicsDomain", + "kind": "class", + "file": "projects/engine/include/cheryl/core/rendering/graphics-domain.h", + "line": 40, + "brief": "Copyable observation token retaining no renderer, context, window or native objects.", + "symbol": "Rendering::GraphicsDomain", + "key": "Rendering::GraphicsDomain", + "system": "resources", + "instance": "" + }, + "Rendering::OpenGL::ResourceLifetime": { + "name": "CE::Rendering::OpenGL::ResourceLifetime", + "label": "Rendering::OpenGL::ResourceLifetime", + "kind": "class", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/resource-lifetime.h", + "line": 51, + "brief": "One retained native-resource domain with thread-safe retirement and owner-context deletion.", + "symbol": "Rendering::OpenGL::ResourceLifetime", + "key": "Rendering::OpenGL::ResourceLifetime", + "system": "resources", + "instance": "" + }, + "Rendering::Resources::Material": { + "name": "CE::Rendering::Resources::Material", + "label": "Rendering::Resources::Material", + "kind": "class", + "file": "projects/engine/include/cheryl/assets/resources/pipeline.h", + "line": 40, + "brief": "Immutable recipe retaining its pipeline/default images.", + "symbol": "Rendering::Resources::Material", + "key": "Rendering::Resources::Material", + "system": "resources", + "instance": "" + }, + "Rendering::Resources::Pipeline": { + "name": "CE::Rendering::Resources::Pipeline", + "label": "Rendering::Resources::Pipeline", + "kind": "class", + "file": "projects/engine/include/cheryl/assets/resources/pipeline.h", + "line": 15, + "brief": "Immutable noncopyable pipeline metadata plus a backend-owned executable implementation.", + "symbol": "Rendering::Resources::Pipeline", + "key": "Rendering::Resources::Pipeline", + "system": "resources", + "instance": "" + }, + "Rendering::OpenGL::Pipeline": { + "name": "CE::Rendering::OpenGL::Pipeline", + "label": "Rendering::OpenGL::Pipeline", + "kind": "class", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/pipeline.h", + "line": 39, + "brief": "Immutable reflected OpenGL pipeline retaining its linked program and reset values.", + "symbol": "Rendering::OpenGL::Pipeline", + "key": "Rendering::OpenGL::Pipeline", + "system": "resources", + "instance": "" + }, + "Rendering::OpenGL::Program": { + "name": "CE::Rendering::OpenGL::Program", + "label": "Rendering::OpenGL::Program", + "kind": "struct", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glslprogram.h", + "line": 47, + "brief": "Linked-program handle and legacy mutable uniform access.", + "symbol": "Rendering::OpenGL::Program", + "key": "Rendering::OpenGL::Program", + "system": "resources", + "instance": "" + }, + "Rendering::OpenGL::Renderer": { + "name": "CE::Rendering::OpenGL::Renderer", + "label": "Rendering::OpenGL::Renderer", + "kind": "class", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/renderer.h", + "line": 23, + "brief": "Implements rendering commands using a separately owned OpenGL context.", + "symbol": "Rendering::OpenGL::Renderer", + "key": "Rendering::OpenGL::Renderer", + "system": "rendering", + "instance": "" + }, + "Rendering::OpenGL::Context": { + "name": "CE::Rendering::OpenGL::Context", + "label": "Rendering::OpenGL::Context", + "kind": "class", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/context.h", + "line": 11, + "brief": "OpenGL context supplied by a compatible platform adapter, not by the renderer.", + "symbol": "Rendering::OpenGL::Context", + "key": "Rendering::OpenGL::Context", + "system": "rendering", + "instance": "" + }, + "Rendering::PassWriter": { + "name": "CE::Rendering::PassWriter", + "label": "Rendering::PassWriter", + "kind": "class", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 69, + "brief": "A short-lived handle to one pass.", + "symbol": "Rendering::PassWriter", + "key": "Rendering::PassWriter", + "system": "rendering", + "instance": "" + }, + "Rendering::Pass": { + "name": "CE::Rendering::Pass", + "label": "Rendering::Pass", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 19, + "brief": "One ordered pass.", + "symbol": "Rendering::Pass", + "key": "Rendering::Pass", + "system": "rendering", + "instance": "" + }, + "Rendering::Submission::Context2D": { + "name": "CE::Rendering::Submission::Context2D", + "label": "Rendering::Submission::Context2D", + "kind": "struct", + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 32, + "brief": "Owned pass inputs for CPU asset resolution.", + "symbol": "Rendering::Submission::Context2D", + "key": "Rendering::Submission::Context2D", + "system": "rendering", + "instance": "" + }, + "Text::Font": { + "name": "CE::Text::Font", + "label": "Text::Font", + "kind": "class", + "file": "projects/engine/include/cheryl/text/font.h", + "line": 36, + "brief": "Resolved immutable CPU font data, shared by copies and independent of Context/toolkit lifetime.", + "symbol": "Text::Font", + "key": "Text::Font", + "system": "text", + "instance": "" + }, + "Text::FontCollection": { + "name": "CE::Text::FontCollection", + "label": "Text::FontCollection", + "kind": "class", + "file": "projects/engine/include/cheryl/text/font-collection.h", + "line": 61, + "brief": "Immutable font bytes and ordered scalable Unicode faces.", + "symbol": "Text::FontCollection", + "key": "Text::FontCollection", + "system": "text", + "instance": "" + }, + "Text::layout_text()": { + "symbol": "Text::layout_text()", + "label": "Text::layout_text()", + "name": "CE::Text::layout_text", + "kind": "operation", + "brief": "Shape on CPU with bidi resolution and whole-grapheme fallback; retain font bytes but not the input view.", + "file": "projects/engine/include/cheryl/text/layout.h", + "line": 83, + "key": "Text::layout_text()", + "system": "text", + "instance": "" + }, + "Text::ShapedText": { + "name": "CE::Text::ShapedText", + "label": "Text::ShapedText", + "kind": "class", + "file": "projects/engine/include/cheryl/text/layout.h", + "line": 63, + "brief": "Owned immutable layout and original scalar/byte mappings.", + "symbol": "Text::ShapedText", + "key": "Text::ShapedText", + "system": "text", + "instance": "" + }, + "Text::Rendering::prepare()": { + "symbol": "Text::Rendering::prepare()", + "label": "Text::Rendering::prepare()", + "name": "CE::Text::Rendering::prepare", + "kind": "operation", + "brief": "Consume/copy an owned shaping result and rasterize only its used visible grayscale outline glyphs on CPU.", + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 115, + "key": "Text::Rendering::prepare()", + "system": "text", + "instance": "" + }, + "Text::Rendering::Prepared": { + "name": "CE::Text::Rendering::Prepared", + "label": "Text::Rendering::Prepared", + "kind": "class", + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 54, + "brief": "Owned CPU layout, grayscale pages and geometry.", + "symbol": "Text::Rendering::Prepared", + "key": "Text::Rendering::Prepared", + "system": "text", + "instance": "" + }, + "Text::Rendering::Generation": { + "name": "CE::Text::Rendering::Generation", + "label": "Text::Rendering::Generation", + "kind": "class", + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 82, + "brief": "One complete immutable geometry/atlas generation paired with its placements.", + "symbol": "Text::Rendering::Generation", + "key": "Text::Rendering::Generation", + "system": "text", + "instance": "" + }, + "Text::Rendering::Updater": { + "name": "CE::Text::Rendering::Updater", + "label": "Text::Rendering::Updater", + "kind": "class", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 42, + "brief": "Retains the last complete text while replacing it asynchronously.", + "symbol": "Text::Rendering::Updater", + "key": "Text::Rendering::Updater", + "system": "text", + "instance": "" + }, + "Runtime::PlatformDispatcher::Submission": { + "name": "CE::Runtime::PlatformDispatcher::Submission", + "label": "Runtime::PlatformDispatcher::Submission", + "kind": "class", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 54, + "brief": "Copyable submission endpoint, with no borrowed dispatcher or engine pointer.", + "symbol": "Runtime::PlatformDispatcher::Submission", + "key": "Runtime::PlatformDispatcher::Submission", + "system": "dispatch", + "instance": "" + }, + "UI::TGUI::RecordedScene": { + "name": "CE::UI::TGUI::RecordedScene", + "label": "UI::TGUI::RecordedScene", + "kind": "class", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/rendering.h", + "line": 54, + "brief": "Complete CPU recording independent of toolkit objects and source vertex/pixel storage.", + "symbol": "UI::TGUI::RecordedScene", + "key": "UI::TGUI::RecordedScene", + "system": "gui", + "instance": "" + }, + "UI::RmlUi::RecordedScene": { + "name": "CE::UI::RmlUi::RecordedScene", + "label": "UI::RmlUi::RecordedScene", + "kind": "class", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 28, + "brief": "Complete CPU recording independent of toolkit objects and source vertex/pixel storage.", + "symbol": "UI::RmlUi::RecordedScene", + "key": "UI::RmlUi::RecordedScene", + "system": "gui", + "instance": "" + }, + "UI::TGUI::Scene": { + "name": "CE::UI::TGUI::Scene", + "label": "UI::TGUI::Scene", + "kind": "class", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 34, + "brief": "Complete immutable uploaded UI packets.", + "symbol": "UI::TGUI::Scene", + "key": "UI::TGUI::Scene", + "system": "gui", + "instance": "" + }, + "UI::RmlUi::Scene": { + "name": "CE::UI::RmlUi::Scene", + "label": "UI::RmlUi::Scene", + "kind": "class", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 31, + "brief": "Complete immutable uploaded UI packets.", + "symbol": "UI::RmlUi::Scene", + "key": "UI::RmlUi::Scene", + "system": "gui", + "instance": "" + }, + "Audio::Miniaudio::System": { + "name": "CE::Audio::Miniaudio::System", + "label": "Audio::Miniaudio::System", + "kind": "class", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 31, + "brief": "Native-device or explicit offline miniaudio output, implementing the neutral synchronized voice/system contracts.", + "symbol": "Audio::Miniaudio::System", + "key": "Audio::Miniaudio::System", + "system": "audio", + "instance": "" + }, + "Audio::System": { + "name": "CE::Audio::System", + "label": "Audio::System", + "kind": "class", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 71, + "brief": "Application-owned audio output and voice lifetime, independent of graphics.", + "symbol": "Audio::System", + "key": "Audio::System", + "system": "audio", + "instance": "" + }, + "Audio::Clip": { + "name": "CE::Audio::Clip", + "label": "Audio::Clip", + "kind": "class", + "file": "projects/engine/include/cheryl/audio/clip.h", + "line": 27, + "brief": "Immutable, owned interleaved float PCM.", + "symbol": "Audio::Clip", + "key": "Audio::Clip", + "system": "audio", + "instance": "" + }, + "Audio::PlaybackOptions": { + "name": "CE::Audio::PlaybackOptions", + "label": "Audio::PlaybackOptions", + "kind": "struct", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 18, + "brief": "Initial per-voice controls; validate before admitting playback.", + "symbol": "Audio::PlaybackOptions", + "key": "Audio::PlaybackOptions", + "system": "audio", + "instance": "" + }, + "Audio::Voice": { + "name": "CE::Audio::Voice", + "label": "Audio::Voice", + "kind": "class", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 49, + "brief": "One independently controlled playback.", + "symbol": "Audio::Voice", + "key": "Audio::Voice", + "system": "audio", + "instance": "" + }, + "Audio::VoiceSnapshot": { + "name": "CE::Audio::VoiceSnapshot", + "label": "Audio::VoiceSnapshot", + "kind": "struct", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 27, + "brief": "Owned synchronized observation; native progress can change state immediately after return.", + "symbol": "Audio::VoiceSnapshot", + "key": "Audio::VoiceSnapshot", + "system": "audio", + "instance": "" + }, + "Audio::PlaybackNotifications": { + "name": "CE::Audio::PlaybackNotifications", + "label": "Audio::PlaybackNotifications", + "kind": "struct", + "file": "projects/engine/include/cheryl/audio/playback-events.h", + "line": 40, + "brief": "Current source reconciliation; does not recover retired generation history.", + "symbol": "Audio::PlaybackNotifications", + "key": "Audio::PlaybackNotifications", + "system": "audio", + "instance": "" + }, + "Audio::PlaybackEnded": { + "name": "CE::Audio::PlaybackEnded", + "label": "Audio::PlaybackEnded", + "kind": "struct", + "file": "projects/engine/include/cheryl/audio/playback-events.h", + "line": 29, + "brief": "Optional observation of one admitted playback generation; retains no Voice/native objects.", + "symbol": "Audio::PlaybackEnded", + "key": "Audio::PlaybackEnded", + "system": "audio", + "instance": "" + }, + "Audio::PlaybackTerminal": { + "name": "CE::Audio::PlaybackTerminal", + "label": "Audio::PlaybackTerminal", + "kind": "struct", + "file": "projects/engine/include/cheryl/audio/playback-events.h", + "line": 21, + "brief": "One terminal fact; later controls/closure preserve it until a successful restart.", + "symbol": "Audio::PlaybackTerminal", + "key": "Audio::PlaybackTerminal", + "system": "audio", + "instance": "" + }, + "Events::EventBus::Publisher": { + "name": "CE::Events::EventBus::Publisher", + "label": "Events::EventBus::Publisher", + "kind": "class", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 150, + "brief": "Copyable notification handle that does not retain the bus or Context.", + "symbol": "Events::EventBus::Publisher", + "key": "Events::EventBus::Publisher", + "system": "events", + "instance": "" + }, + "Runtime::RuntimeServices": { + "name": "CE::Runtime::RuntimeServices", + "label": "Runtime::RuntimeServices", + "kind": "class", + "file": "projects/engine/include/cheryl/core/engine/runtime-services.h", + "line": 72, + "brief": "Retained attachment access to Context's two runtime service owners.", + "symbol": "Runtime::RuntimeServices", + "key": "Runtime::RuntimeServices", + "system": "runtime", + "instance": "" + }, + "Runtime::ServiceAttachment": { + "name": "CE::Runtime::ServiceAttachment", + "label": "Runtime::ServiceAttachment", + "kind": "class", + "file": "projects/engine/include/cheryl/core/engine/runtime-services.h", + "line": 48, + "brief": "Owned immediate module participation; reset acknowledges invalidation.", + "symbol": "Runtime::ServiceAttachment", + "key": "Runtime::ServiceAttachment", + "system": "runtime", + "instance": "" + }, + "Events::EventChannel": { + "name": "CE::Events::EventChannel", + "label": "Events::EventChannel", + "kind": "class", + "file": "projects/engine/include/cheryl/core/subsystems/event-channel.h", + "line": 21, + "brief": "Owned exact name and payload type identify one typed channel within a bus.", + "symbol": "Events::EventChannel", + "key": "Events::EventChannel", + "system": "events", + "instance": "" + }, + "Events::EventBus::Registration": { + "name": "CE::Events::EventBus::Registration", + "label": "Events::EventBus::Registration", + "kind": "class", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 128, + "brief": "A copyable, bus-qualified identifier, not a subscription owner.", + "symbol": "Events::EventBus::Registration", + "key": "Events::EventBus::Registration", + "system": "events", + "instance": "" + }, + "Events::EventBus::Delivery": { + "symbol": "Events::EventBus::Delivery", + "label": "Events::EventBus::Delivery", + "name": "CE::Events::EventBus::Delivery", + "kind": "alias", + "brief": "Named Engine callback contract.", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 50, + "key": "Events::EventBus::Delivery", + "system": "events", + "instance": "" + }, + "Events::EventBus::ErrorHandler": { + "symbol": "Events::EventBus::ErrorHandler", + "label": "Events::EventBus::ErrorHandler", + "name": "CE::Events::EventBus::ErrorHandler", + "kind": "alias", + "brief": "Named Engine callback contract.", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 54, + "key": "Events::EventBus::ErrorHandler", + "system": "events", + "instance": "" + }, + "Events::platform_delivery()": { + "symbol": "Events::platform_delivery()", + "label": "Events::platform_delivery()", + "name": "CE::Events::platform_delivery", + "kind": "operation", + "brief": "Optional composition adapters.", + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 15, + "key": "Events::platform_delivery()", + "system": "events", + "instance": "" + }, + "Events::simulation_delivery()": { + "symbol": "Events::simulation_delivery()", + "label": "Events::simulation_delivery()", + "name": "CE::Events::simulation_delivery", + "kind": "operation", + "brief": "Adapt a simulation submission endpoint to deferred event delivery.", + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 16, + "key": "Events::simulation_delivery()", + "system": "events", + "instance": "" + }, + "Runtime::SimulationDispatcher::Submission": { + "name": "CE::Runtime::SimulationDispatcher::Submission", + "label": "Runtime::SimulationDispatcher::Submission", + "kind": "class", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 54, + "brief": "Copyable submission endpoint, with no borrowed dispatcher or game pointer.", + "symbol": "Runtime::SimulationDispatcher::Submission", + "key": "Runtime::SimulationDispatcher::Submission", + "system": "dispatch", + "instance": "" + }, + "Events::worker_delivery()": { + "symbol": "Events::worker_delivery()", + "label": "Events::worker_delivery()", + "name": "CE::Events::worker_delivery", + "kind": "operation", + "brief": "Create one serial deferred stream on shared worker capacity.", + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 19, + "key": "Events::worker_delivery()", + "system": "events", + "instance": "" + }, + "Runtime::SimulationDispatcher": { + "name": "CE::Runtime::SimulationDispatcher", + "label": "Runtime::SimulationDispatcher", + "kind": "class", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 30, + "brief": "Transfers owned requests to the runtime's simulation thread.", + "symbol": "Runtime::SimulationDispatcher", + "key": "Runtime::SimulationDispatcher", + "system": "dispatch", + "instance": "" + }, + "Diagnostics::DispatchStats": { + "name": "CE::Diagnostics::DispatchStats", + "label": "Diagnostics::DispatchStats", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/diagnostics.h", + "line": 19, + "brief": "Best-effort cumulative queue observations for one dispatcher, not an admission or completion barrier.", + "symbol": "Diagnostics::DispatchStats", + "key": "Diagnostics::DispatchStats", + "system": "diagnostics", + "instance": "" + }, + "Runtime::ExecutionOptions": { + "name": "CE::Runtime::ExecutionOptions", + "label": "Runtime::ExecutionOptions", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 47, + "brief": "Execution capacity for Context-owned CPU work; settings alone start no threads.", + "symbol": "Runtime::ExecutionOptions", + "key": "Runtime::ExecutionOptions", + "system": "runtime", + "instance": "" + }, + "Workers::Pool": { + "name": "CE::Workers::Pool", + "label": "Workers::Pool", + "kind": "class", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 181, + "brief": "Independently owned reusable CPU workers.", + "symbol": "Workers::Pool", + "key": "Workers::Pool", + "system": "workers", + "instance": "" + }, + "Workers::PoolOptions": { + "name": "CE::Workers::PoolOptions", + "label": "Workers::PoolOptions", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 70, + "brief": "Physical capacity and optional retained topology provider used at pool construction.", + "symbol": "Workers::PoolOptions", + "key": "Workers::PoolOptions", + "system": "workers", + "instance": "" + }, + "Workers::TopologyProvider": { + "name": "CE::Workers::TopologyProvider", + "label": "Workers::TopologyProvider", + "kind": "class", + "file": "projects/engine/include/cheryl/core/engine/worker-topology.h", + "line": 48, + "brief": "Application-supplied discovery and calling-thread CPU-binding boundary.", + "symbol": "Workers::TopologyProvider", + "key": "Workers::TopologyProvider", + "system": "workers", + "instance": "" + }, + "Workers::Topology": { + "name": "CE::Workers::Topology", + "label": "Workers::Topology", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/engine/worker-topology.h", + "line": 35, + "brief": "Owned discovery snapshot intersected with inherited CPU eligibility; not live OS state.", + "symbol": "Workers::Topology", + "key": "Workers::Topology", + "system": "workers", + "instance": "" + }, + "Workers::GroupOptions": { + "name": "CE::Workers::GroupOptions", + "label": "Workers::GroupOptions", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 78, + "brief": "Logical scheduling share, concurrency limit and native policy for one group.", + "symbol": "Workers::GroupOptions", + "key": "Workers::GroupOptions", + "system": "workers", + "instance": "" + }, + "Workers::GroupStatus": { + "name": "CE::Workers::GroupStatus", + "label": "Workers::GroupStatus", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 104, + "brief": "Cumulative group observations and current scheduler counts; not a completion barrier.", + "symbol": "Workers::GroupStatus", + "key": "Workers::GroupStatus", + "system": "diagnostics", + "instance": "" + }, + "Workers::CpuPolicy": { + "name": "CE::Workers::CpuPolicy", + "label": "Workers::CpuPolicy", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 46, + "brief": "Requested eligible CPUs and optional cache/locality constraints; unsupported NUMA requirements do not imply memory-placement support.", + "symbol": "Workers::CpuPolicy", + "key": "Workers::CpuPolicy", + "system": "workers", + "instance": "" + }, + "Workers::PolicyStrength": { + "name": "CE::Workers::PolicyStrength", + "label": "Workers::PolicyStrength", + "kind": "enum", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 36, + "brief": "Whether unavailable CPU/locality requirements may degrade.", + "symbol": "Workers::PolicyStrength", + "key": "Workers::PolicyStrength", + "system": "workers", + "instance": "" + }, + "Assets::Grids::make_geometry()": { + "symbol": "Assets::Grids::make_geometry()", + "label": "Assets::Grids::make_geometry()", + "name": "CE::Assets::Grids::make_geometry", + "kind": "operation", + "brief": "Build local Y-up cell strips and normalized texture coordinates on the CPU.", + "file": "projects/engine/include/cheryl/assets/geometry/grid-geometry.h", + "line": 25, + "key": "Assets::Grids::make_geometry()", + "system": "assets", + "instance": "" + }, + "Memory::ExactManager": { + "name": "CE::Memory::ExactManager", + "label": "Memory::ExactManager", + "kind": "alias", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 90, + "brief": "Manager whose default growth adds no spare bytes; it still reuses the shared byte pool.", + "symbol": "Memory::ExactManager", + "key": "Memory::ExactManager", + "system": "memory", + "instance": "" + }, + "Memory::Manager": { + "name": "CE::Memory::Manager", + "label": "Memory::Manager", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 27, + "brief": "Recycles byte ranges, aligning/growing a request before checkout and tracking owners, sections, and pooled remainders through CE::Memory::AbstractBlockManager.", + "symbol": "Memory::Manager", + "key": "Memory::Manager", + "system": "memory", + "instance": "" + }, + "Memory::Objects::PoolState": { + "name": "CE::Memory::Objects::PoolState", + "label": "Memory::Objects::PoolState", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/resources/objects/pool.h", + "line": 14, + "brief": "Owns the object pool's release operations and retains shared block bookkeeping.", + "symbol": "Memory::Objects::PoolState", + "key": "Memory::Objects::PoolState", + "system": "memory", + "instance": "" + }, + "Memory::Objects::Constructor::Context": { + "name": "CE::Memory::Objects::Constructor::Context", + "label": "Memory::Objects::Constructor::Context", + "kind": "class", + "file": "projects/engine/include/cheryl/core/resources/objects/object-construction.hpp", + "line": 20, + "brief": "Retained synchronized per-slot raw/live/busy state; user constructors/destructors run outside its mutex.", + "symbol": "Memory::Objects::Constructor::Context", + "key": "Memory::Objects::Constructor::Context", + "system": "memory", + "instance": "" + }, + "Memory::Block": { + "name": "CE::Memory::Block", + "label": "Memory::Block", + "kind": "struct", + "file": "projects/engine/include/cheryl/templates/block.h", + "line": 79, + "brief": "A contiguous range within one backing allocation.", + "symbol": "Memory::Block", + "key": "Memory::Block", + "system": "memory", + "instance": "" + }, + "Memory::BlockTransactions": { + "name": "CE::Memory::BlockTransactions", + "label": "Memory::BlockTransactions", + "kind": "struct", + "file": "projects/engine/include/cheryl/templates/block-transactions.h", + "line": 14, + "brief": "Transactions over one retained CE::Memory::BlockManagement::State.", + "symbol": "Memory::BlockTransactions", + "key": "Memory::BlockTransactions", + "system": "memory", + "instance": "" + }, + "Memory::ByteReleaseContext": { + "name": "CE::Memory::ByteReleaseContext", + "label": "Memory::ByteReleaseContext", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/resources/memory/release-context.h", + "line": 16, + "brief": "Retains byte bookkeeping for final releases independently of a Manager facade.", + "symbol": "Memory::ByteReleaseContext", + "key": "Memory::ByteReleaseContext", + "system": "memory", + "instance": "" + }, + "State::Singleton": { + "name": "CE::State::Singleton", + "label": "State::Singleton", + "kind": "class", + "file": "projects/engine/include/cheryl/templates/singleton.h", + "line": 26, + "brief": "CE::State::Singleton (Compile Time Safe) For simple singleton creation.", + "symbol": "State::Singleton", + "key": "State::Singleton", + "system": "state", + "instance": "" + }, + "Memory::Stats": { + "name": "CE::Memory::Stats", + "label": "Memory::Stats", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 11, + "brief": "One coherent shared byte-domain snapshot, independent of formatting/logging.", + "symbol": "Memory::Stats", + "key": "Memory::Stats", + "system": "diagnostics", + "instance": "" + }, + "State::ObservedVariable": { + "name": "CE::State::ObservedVariable", + "label": "State::ObservedVariable", + "kind": "class", + "file": "projects/engine/include/cheryl/templates/observed-variables.h", + "line": 22, + "brief": "Adds immediate observer callbacks to synchronized, versioned storage.", + "symbol": "State::ObservedVariable", + "key": "State::ObservedVariable", + "system": "state", + "instance": "" + }, + "State::VersionedVariable": { + "name": "CE::State::VersionedVariable", + "label": "State::VersionedVariable", + "kind": "class", + "file": "projects/engine/include/cheryl/templates/versioned-variable.h", + "line": 30, + "brief": "Synchronizes one value and its revision as a unit.", + "symbol": "State::VersionedVariable", + "key": "State::VersionedVariable", + "system": "state", + "instance": "" + }, + "State::VersionedSnapshot": { + "name": "CE::State::VersionedSnapshot", + "label": "State::VersionedSnapshot", + "kind": "struct", + "file": "projects/engine/include/cheryl/templates/versioned-variable.h", + "line": 14, + "brief": "Owned value/revision snapshot sampled under one storage lock.", + "symbol": "State::VersionedSnapshot", + "key": "State::VersionedSnapshot", + "system": "state", + "instance": "" + }, + "State::StateTracker": { + "name": "CE::State::StateTracker", + "label": "State::StateTracker", + "kind": "class", + "file": "projects/engine/include/cheryl/templates/state-tracker.h", + "line": 15, + "brief": "Caller-owned successive samples without synchronization.", + "symbol": "State::StateTracker", + "key": "State::StateTracker", + "system": "state", + "instance": "" + }, + "State::StateMachine": { + "name": "CE::State::StateMachine", + "label": "State::StateMachine", + "kind": "class", + "file": "projects/engine/include/cheryl/templates/state-machine.h", + "line": 28, + "brief": "A transition table for one typed state domain.", + "symbol": "State::StateMachine", + "key": "State::StateMachine", + "system": "state", + "instance": "" + }, + "Logging::Logger": { + "name": "CE::Logging::Logger", + "label": "Logging::Logger", + "kind": "class", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 18, + "brief": "Named lazy access to one Engine-owned category, with configuration before first use.", + "symbol": "Logging::Logger", + "key": "Logging::Logger", + "system": "logging", + "instance": "" + }, + "Logging::Log": { + "name": "CE::Logging::Log", + "label": "Logging::Log", + "kind": "class", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 113, + "brief": "Owned named logger with the LogHandle lifecycle and operation contracts.", + "symbol": "Logging::Log", + "key": "Logging::Log", + "system": "logging", + "instance": "" + }, + "Logging::Config": { + "name": "CE::Logging::Config", + "label": "Logging::Config", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 38, + "brief": "Initial destination and per-Log queue configuration.", + "symbol": "Logging::Config", + "key": "Logging::Config", + "system": "logging", + "instance": "" + }, + "Logging::LogHandle": { + "name": "CE::Logging::LogHandle", + "label": "Logging::LogHandle", + "kind": "class", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 42, + "brief": "An owned logger facade.", + "symbol": "Logging::LogHandle", + "key": "Logging::LogHandle", + "system": "logging", + "instance": "" + }, + "Logging::QueueStats": { + "name": "CE::Logging::QueueStats", + "label": "Logging::QueueStats", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 28, + "brief": "Independently sampled values from the shared Cheryl pool, including flush requests.", + "symbol": "Logging::QueueStats", + "key": "Logging::QueueStats", + "system": "diagnostics", + "instance": "" + }, + "Logging::FileEventHandlers": { + "name": "CE::Logging::FileEventHandlers", + "label": "Logging::FileEventHandlers", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/logging/types.h", + "line": 23, + "brief": "Owned file-lifecycle callbacks invoked by backend operations, not marshalled onto the application thread.", + "symbol": "Logging::FileEventHandlers", + "key": "Logging::FileEventHandlers", + "system": "logging", + "instance": "" + }, + "Logging::Level": { + "name": "CE::Logging::Level", + "label": "Logging::Level", + "kind": "enum", + "file": "projects/engine/include/cheryl/core/logging/types.h", + "line": 9, + "brief": "Runtime severity gate in increasing urgency; off disables admission, not destination opening.", + "symbol": "Logging::Level", + "key": "Logging::Level", + "system": "logging", + "instance": "" + }, + "Logging::OverflowPolicy": { + "name": "CE::Logging::OverflowPolicy", + "label": "Logging::OverflowPolicy", + "kind": "enum", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 20, + "brief": "Per-Log behavior at shared queue capacity, for records and flush requests.", + "symbol": "Logging::OverflowPolicy", + "key": "Logging::OverflowPolicy", + "system": "logging", + "instance": "" + }, + "Runtime::RuntimeStats": { + "name": "CE::Runtime::RuntimeStats", + "label": "Runtime::RuntimeStats", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/game-framework/runtime-lifecycle.h", + "line": 23, + "brief": "Completed/unstarted session observations, copied after synchronization; never a live admission policy.", + "symbol": "Runtime::RuntimeStats", + "key": "Runtime::RuntimeStats", + "system": "runtime", + "instance": "" + }, + "Events::EventStats": { + "name": "CE::Events::EventStats", + "label": "Events::EventStats", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 24, + "brief": "Owned bus observations; cumulative counters and closure are not callback-drain barriers.", + "symbol": "Events::EventStats", + "key": "Events::EventStats", + "system": "diagnostics", + "instance": "" + }, + "Assets::Loading::UploadStats": { + "name": "CE::Assets::Loading::UploadStats", + "label": "Assets::Loading::UploadStats", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 45, + "brief": "Copied counters for one last upload attempt; completion counters are not an atomic cache snapshot.", + "symbol": "Assets::Loading::UploadStats", + "key": "Assets::Loading::UploadStats", + "system": "diagnostics", + "instance": "" + }, + "Rendering::OpenGL::ResourceStats": { + "name": "CE::Rendering::OpenGL::ResourceStats", + "label": "Rendering::OpenGL::ResourceStats", + "kind": "struct", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/resource-lifetime.h", + "line": 33, + "brief": "Synchronized cumulative counters for one native lifetime, not a residency budget.", + "symbol": "Rendering::OpenGL::ResourceStats", + "key": "Rendering::OpenGL::ResourceStats", + "system": "diagnostics", + "instance": "" + }, + "Memory::adjust_length()": { + "symbol": "Memory::adjust_length()", + "label": "Memory::adjust_length()", + "name": "CE::Memory::adjust_length", + "kind": "operation", + "brief": "Computes a capacity request without allocating storage.", + "file": "projects/engine/include/cheryl/math/fit.h", + "line": 19, + "key": "Memory::adjust_length()", + "system": "utilities", + "instance": "" + }, + "Memory::FitPolicy": { + "name": "CE::Memory::FitPolicy", + "label": "Memory::FitPolicy", + "kind": "enum", + "file": "projects/engine/include/cheryl/enums/fit-type.h", + "line": 5, + "brief": "Capacity-growth choice used by block allocation and cache reservations.", + "symbol": "Memory::FitPolicy", + "key": "Memory::FitPolicy", + "system": "utilities", + "instance": "" + }, + "Rendering::Geometry::Pivot": { + "name": "CE::Rendering::Geometry::Pivot", + "label": "Rendering::Geometry::Pivot", + "kind": "struct", + "file": "projects/engine/include/cheryl/math/anchor.h", + "line": 13, + "brief": "Normalized origin within a frame: (0,0) is its top-left, (1,1) its bottom-right.", + "symbol": "Rendering::Geometry::Pivot", + "key": "Rendering::Geometry::Pivot", + "system": "utilities", + "instance": "" + }, + "Assets::Sprites::Definition": { + "name": "CE::Assets::Sprites::Definition", + "label": "Assets::Sprites::Definition", + "kind": "struct", + "file": "projects/engine/include/cheryl/assets/definitions/sprite.h", + "line": 18, + "brief": "Owned sprite metadata; parsing/loading validates it, aggregate assignment does not.", + "symbol": "Assets::Sprites::Definition", + "key": "Assets::Sprites::Definition", + "system": "assets", + "instance": "" + }, + "Text::Numbers::string_to_number()": { + "symbol": "Text::Numbers::string_to_number()", + "label": "Text::Numbers::string_to_number()", + "name": "CE::Text::Numbers::string_to_number", + "kind": "operation", + "brief": "Selects the legacy integer or floating parser from token spelling.", + "file": "projects/engine/include/cheryl/math/string-numbers.h", + "line": 99, + "key": "Text::Numbers::string_to_number()", + "system": "utilities", + "instance": "" + }, + "Text::Numbers::parse_floats()": { + "symbol": "Text::Numbers::parse_floats()", + "label": "Text::Numbers::parse_floats()", + "name": "CE::Text::Numbers::parse_floats", + "kind": "operation", + "brief": "Parse a complete token.", + "file": "projects/engine/include/cheryl/math/string-numbers.h", + "line": 24, + "key": "Text::Numbers::parse_floats()", + "system": "utilities", + "instance": "" + }, + "Text::Numbers::parse_integers()": { + "symbol": "Text::Numbers::parse_integers()", + "label": "Text::Numbers::parse_integers()", + "name": "CE::Text::Numbers::parse_integers", + "kind": "operation", + "brief": "Parses one complete base-10 integer token into its smallest fitting alternative.", + "file": "projects/engine/include/cheryl/math/string-numbers.h", + "line": 51, + "key": "Text::Numbers::parse_integers()", + "system": "utilities", + "instance": "" + }, + "Time::DeltaTime": { + "name": "CE::Time::DeltaTime", + "label": "Time::DeltaTime", + "kind": "class", + "file": "projects/engine/include/cheryl/templates/delta.h", + "line": 15, + "brief": "Caller-owned interval timer, without synchronization.", + "symbol": "Time::DeltaTime", + "key": "Time::DeltaTime", + "system": "utilities", + "instance": "" + }, + "Math::BitArray": { + "name": "CE::Math::BitArray", + "label": "Math::BitArray", + "kind": "class", + "file": "projects/engine/include/cheryl/math/binary.h", + "line": 67, + "brief": "Fixed-size packed bit array.", + "symbol": "Math::BitArray", + "key": "Math::BitArray", + "system": "utilities", + "instance": "" + }, + "@host": { + "key": "@host", + "label": "Application composition", + "kind": "role", + "system": "application", + "symbol": null + }, + "Application::GLFWOpenGL::make_context()": { + "symbol": "Application::GLFWOpenGL::make_context()", + "label": "Application::GLFWOpenGL::make_context()", + "name": "CE::Application::GLFWOpenGL::make_context", + "kind": "operation", + "brief": "Assemble the GLFW display/window, its OpenGL presentation context, the OpenGL renderer and resource provider, and the selected input adapter.", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-backend.h", + "line": 33, + "key": "Application::GLFWOpenGL::make_context()", + "system": "runtime", + "instance": "" + }, + "@world": { + "key": "@world", + "label": "Application-owned game objects", + "kind": "role", + "system": "application", + "symbol": null + }, + "@update": { + "key": "@update", + "label": "Application update()", + "kind": "role", + "system": "application", + "symbol": null + }, + "@prepare": { + "key": "@prepare", + "label": "Application prepare_render_frame()", + "kind": "role", + "system": "application", + "symbol": null + }, + "Runtime::ShutdownPolicy": { + "name": "CE::Runtime::ShutdownPolicy", + "label": "Runtime::ShutdownPolicy", + "kind": "class", + "file": "projects/engine/include/cheryl/core/game-framework/shutdown.h", + "line": 110, + "brief": "Owns the single host authority.", + "symbol": "Runtime::ShutdownPolicy", + "key": "Runtime::ShutdownPolicy", + "system": "runtime", + "instance": "" + }, + "@producer": { + "key": "@producer", + "label": "Application producer", + "kind": "role", + "system": "application", + "symbol": null + }, + "Runtime::ShutdownEndpoint": { + "name": "CE::Runtime::ShutdownEndpoint", + "label": "Runtime::ShutdownEndpoint", + "kind": "class", + "file": "projects/engine/include/cheryl/core/game-framework/shutdown.h", + "line": 69, + "brief": "Copyable command access, without a borrowed Runtime.", + "symbol": "Runtime::ShutdownEndpoint", + "key": "Runtime::ShutdownEndpoint", + "system": "runtime", + "instance": "" + }, + "@report": { + "key": "@report", + "label": "Application report", + "kind": "role", + "system": "application", + "symbol": null + }, + "@init": { + "key": "@init", + "label": "Application init_platform()", + "kind": "role", + "system": "application", + "symbol": null + }, + "@native": { + "key": "@native", + "label": "Application platform-owner work", + "kind": "role", + "system": "application", + "symbol": null + }, + "Platform::Monitor": { + "name": "CE::Platform::Monitor", + "label": "Platform::Monitor", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/display/monitor.h", + "line": 13, + "brief": "Value snapshot of monitor video-mode dimensions with a display-defined ID.", + "symbol": "Platform::Monitor", + "key": "Platform::Monitor", + "system": "display", + "instance": "" + }, + "Platform::WindowMode": { + "name": "CE::Platform::WindowMode", + "label": "Platform::WindowMode", + "kind": "enum", + "file": "projects/engine/include/cheryl/enums/window-mode.h", + "line": 5, + "brief": "Window presentation choice interpreted by the selected platform backend.", + "symbol": "Platform::WindowMode", + "key": "Platform::WindowMode", + "system": "display", + "instance": "" + }, + "Input::Control": { + "name": "CE::Input::Control", + "label": "Input::Control", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/controls/device-binding.h", + "line": 10, + "brief": "Physical (device, control) identity for bindings.", + "symbol": "Input::Control", + "key": "Input::Control", + "system": "input", + "instance": "" + }, + "Input::KeyboardKey": { + "name": "CE::Input::KeyboardKey", + "label": "Input::KeyboardKey", + "kind": "enum", + "file": "projects/engine/include/cheryl/core/controls/button-codes.h", + "line": 14, + "brief": "Portable physical key positions/functions, independent of native tokens.", + "symbol": "Input::KeyboardKey", + "key": "Input::KeyboardKey", + "system": "input", + "instance": "" + }, + "@controller": { + "key": "@controller", + "label": "Application-owned controller", + "kind": "role", + "system": "application", + "symbol": null + }, + "@load": { + "key": "@load", + "label": "Application loading work", + "kind": "role", + "system": "application", + "symbol": null + }, + "Assets::Sprites::Animation": { + "name": "CE::Assets::Sprites::Animation", + "label": "Assets::Sprites::Animation", + "kind": "class", + "file": "projects/engine/include/cheryl/assets/types/2d/sprite.h", + "line": 28, + "brief": "Independent cursor retaining shared immutable clip metadata, even after its CE::Assets::Sprites::Asset is released.", + "symbol": "Assets::Sprites::Animation", + "key": "Assets::Sprites::Animation", + "system": "assets", + "instance": "" + }, + "Assets::Images::decode()": { + "symbol": "Assets::Images::decode()", + "label": "Assets::Images::decode()", + "name": "CE::Assets::Images::decode", + "kind": "operation", + "brief": "Decode a file into owned RGBA8 pixels without uploading or publishing a cache entry.", + "file": "projects/engine/include/cheryl/assets/resources/decoded-image.h", + "line": 22, + "key": "Assets::Images::decode()", + "system": "assets", + "instance": "" + }, + "Assets::Images::Decoded": { + "name": "CE::Assets::Images::Decoded", + "label": "Assets::Images::Decoded", + "kind": "struct", + "file": "projects/engine/include/cheryl/assets/resources/decoded-image.h", + "line": 16, + "brief": "Owned top-to-bottom RGBA8 pixels, independent of any graphics context.", + "symbol": "Assets::Images::Decoded", + "key": "Assets::Images::Decoded", + "system": "resources", + "instance": "" + }, + "Rendering::DrawStyle2D": { + "name": "CE::Rendering::DrawStyle2D", + "label": "Rendering::DrawStyle2D", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 18, + "brief": "CPU-owned style copied during packet resolution; no entity or live transform is retained.", + "symbol": "Rendering::DrawStyle2D", + "key": "Rendering::DrawStyle2D", + "system": "rendering", + "instance": "" + }, + "@ui": { + "key": "@ui", + "label": "Application-owned UI", + "kind": "role", + "system": "application", + "symbol": null + }, + "Text::Rendering::Request": { + "name": "CE::Text::Rendering::Request", + "label": "Text::Rendering::Request", + "kind": "struct", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 18, + "brief": "Owned desired text and retained font/material handles, compared by content/options and snapshot identity.", + "symbol": "Text::Rendering::Request", + "key": "Text::Rendering::Request", + "system": "text", + "instance": "" + }, + "Rendering::Submission::resolve_text()": { + "symbol": "Rendering::Submission::resolve_text()", + "label": "Rendering::Submission::resolve_text()", + "name": "CE::Rendering::Submission::resolve_text", + "kind": "operation", + "brief": "Resolve retained text generations or legacy-font layout into retained CPU draw packets.", + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 70, + "key": "Rendering::Submission::resolve_text()", + "system": "rendering", + "instance": "" + }, + "Input::Events::Record": { + "name": "CE::Input::Events::Record", + "label": "Input::Events::Record", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/controls/input-record.h", + "line": 77, + "brief": "A backend-delivered observation.", + "symbol": "Input::Events::Record", + "key": "Input::Events::Record", + "system": "input", + "instance": "" + }, + "Input::Routing::KeyboardPolicy": { + "name": "CE::Input::Routing::KeyboardPolicy", + "label": "Input::Routing::KeyboardPolicy", + "kind": "enum", + "file": "projects/engine/include/cheryl/core/controls/input-types.h", + "line": 18, + "brief": "Gameplay admission for focused keyboard records; pointer selection is separate.", + "symbol": "Input::Routing::KeyboardPolicy", + "key": "Input::Routing::KeyboardPolicy", + "system": "input", + "instance": "" + }, + "@audio": { + "key": "@audio", + "label": "Application sound controller", + "kind": "role", + "system": "application", + "symbol": null + }, + "@listener": { + "key": "@listener", + "label": "Application listener setup", + "kind": "role", + "system": "application", + "symbol": null + }, + "@cleanup": { + "key": "@cleanup", + "label": "Application cleanup / quiesce()", + "kind": "role", + "system": "application", + "symbol": null + }, + "@callback": { + "key": "@callback", + "label": "Application callback", + "kind": "role", + "system": "application", + "symbol": null + }, + "@sim": { + "key": "@sim", + "label": "Application simulation-owner work", + "kind": "role", + "system": "application", + "symbol": null + }, + "@cpu": { + "key": "@cpu", + "label": "Application CPU preparation", + "kind": "role", + "system": "application", + "symbol": null + }, + "@storage": { + "key": "@storage", + "label": "Application storage owner", + "kind": "role", + "system": "application", + "symbol": null + }, + "@objects": { + "key": "@objects", + "label": "Application object type", + "kind": "role", + "system": "application", + "symbol": null + }, + "@model": { + "key": "@model", + "label": "Application state model", + "kind": "role", + "system": "application", + "symbol": null + }, + "@writer": { + "key": "@writer", + "label": "Application state producer", + "kind": "role", + "system": "application", + "symbol": null + }, + "@reader": { + "key": "@reader", + "label": "Application snapshot reader", + "kind": "role", + "system": "application", + "symbol": null + }, + "@hooks": { + "key": "@hooks", + "label": "Application transition / observer hooks", + "kind": "role", + "system": "application", + "symbol": null + }, + "Logging::ProcessLifetime": { + "name": "CE::Logging::ProcessLifetime", + "label": "Logging::ProcessLifetime", + "kind": "class", + "file": "projects/engine/include/cheryl/core/logging/process-lifetime.h", + "line": 16, + "brief": "Owns the process logging lifetime on an application thread.", + "symbol": "Logging::ProcessLifetime", + "key": "Logging::ProcessLifetime", + "system": "logging", + "instance": "" + }, + "Logging::Profile": { + "name": "CE::Logging::Profile", + "label": "Logging::Profile", + "kind": "enum", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 12, + "brief": "Runtime threshold defaults, independent of which severities were compiled.", + "symbol": "Logging::Profile", + "key": "Logging::Profile", + "system": "logging", + "instance": "" + }, + "@live": { + "key": "@live", + "label": "Application live observations", + "kind": "role", + "system": "application", + "symbol": null + }, + "@tools": { + "key": "@tools", + "label": "Application loading tool", + "kind": "role", + "system": "application", + "symbol": null + }, + "@bits": { + "key": "@bits", + "label": "Application bit bookkeeping", + "kind": "role", + "system": "application", + "symbol": null + }, + "@placement": { + "key": "@placement", + "label": "Application geometry preparation", + "kind": "role", + "system": "application", + "symbol": null + }, + "@config": { + "key": "@config", + "label": "Application configuration parsing", + "kind": "role", + "system": "application", + "symbol": null + }, + "Runtime::ShutdownTicket": { + "name": "CE::Runtime::ShutdownTicket", + "label": "Runtime::ShutdownTicket", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/game-framework/shutdown.h", + "line": 51, + "brief": "Duplicate requests share this ID/result until a decision or forced stop.", + "symbol": "Runtime::ShutdownTicket", + "key": "Runtime::ShutdownTicket", + "system": "runtime", + "instance": "" + }, + "Runtime::ShutdownDecision": { + "name": "CE::Runtime::ShutdownDecision", + "label": "Runtime::ShutdownDecision", + "kind": "class", + "file": "projects/engine/include/cheryl/core/game-framework/shutdown.h", + "line": 83, + "brief": "Private decision capability supplied only to the installed host policy.", + "symbol": "Runtime::ShutdownDecision", + "key": "Runtime::ShutdownDecision", + "system": "runtime", + "instance": "" + }, + "Runtime::ShutdownRequest": { + "name": "CE::Runtime::ShutdownRequest", + "label": "Runtime::ShutdownRequest", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/game-framework/runtime-lifecycle.h", + "line": 62, + "brief": "Owned request identity and origin, with no decision authority.", + "symbol": "Runtime::ShutdownRequest", + "key": "Runtime::ShutdownRequest", + "system": "runtime", + "instance": "" + }, + "Runtime::RuntimeLifecycleSnapshot": { + "name": "CE::Runtime::RuntimeLifecycleSnapshot", + "label": "Runtime::RuntimeLifecycleSnapshot", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/game-framework/runtime-lifecycle.h", + "line": 97, + "brief": "Current milestone and retained terminal outcome, safe after Runtime destruction.", + "symbol": "Runtime::RuntimeLifecycleSnapshot", + "key": "Runtime::RuntimeLifecycleSnapshot", + "system": "runtime", + "instance": "" + }, + "Input::Actions::ButtonTickState": { + "name": "CE::Input::Actions::ButtonTickState", + "label": "Input::Actions::ButtonTickState", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 20, + "brief": "Button activity since the previous simulation consumption.", + "symbol": "Input::Actions::ButtonTickState", + "key": "Input::Actions::ButtonTickState", + "system": "input", + "instance": "" + }, + "Input::Capture::Lease": { + "name": "CE::Input::Capture::Lease", + "label": "Input::Capture::Lease", + "kind": "class", + "file": "projects/engine/include/cheryl/core/controls/input-capture.h", + "line": 25, + "brief": "A scoped request to collect a channel.", + "symbol": "Input::Capture::Lease", + "key": "Input::Capture::Lease", + "system": "input", + "instance": "" + }, + "Input::Routing::Lease": { + "name": "CE::Input::Routing::Lease", + "label": "Input::Routing::Lease", + "kind": "class", + "file": "projects/engine/include/cheryl/core/controls/input-routing.h", + "line": 31, + "brief": "Scoped keyboard-focus ownership.", + "symbol": "Input::Routing::Lease", + "key": "Input::Routing::Lease", + "system": "input", + "instance": "" + }, + "Input::Events::Payload": { + "name": "CE::Input::Events::Payload", + "label": "Input::Events::Payload", + "kind": "alias", + "file": "projects/engine/include/cheryl/core/controls/input-record.h", + "line": 69, + "brief": "Owned physical observation or committed scalar, preserving their common record ordering.", + "symbol": "Input::Events::Payload", + "key": "Input::Events::Payload", + "system": "input", + "instance": "" + }, + "Assets::Sprites::Clip": { + "name": "CE::Assets::Sprites::Clip", + "label": "Assets::Sprites::Clip", + "kind": "struct", + "file": "projects/engine/include/cheryl/assets/definitions/animation.h", + "line": 25, + "brief": "Concrete ordered sprite animation, optionally qualified by facing; owns metadata, no resource or clock.", + "symbol": "Assets::Sprites::Clip", + "key": "Assets::Sprites::Clip", + "system": "assets", + "instance": "" + }, + "Runtime::Context#context A": { + "name": "CE::Runtime::Context", + "label": "Runtime::Context", + "kind": "class", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 60, + "brief": "Owns a compatible set of platform, presentation, rendering, and resource adapters.", + "symbol": "Runtime::Context", + "key": "Runtime::Context#context A", + "system": "runtime", + "instance": "context A" + }, + "Runtime::Context#context B": { + "name": "CE::Runtime::Context", + "label": "Runtime::Context", + "kind": "class", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 60, + "brief": "Owns a compatible set of platform, presentation, rendering, and resource adapters.", + "symbol": "Runtime::Context", + "key": "Runtime::Context#context B", + "system": "runtime", + "instance": "context B" + }, + "Workers::Group#group A": { + "name": "CE::Workers::Group", + "label": "Workers::Group", + "kind": "class", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 127, + "brief": "A workload handle sharing physical capacity with other groups in its pool.", + "symbol": "Workers::Group", + "key": "Workers::Group#group A", + "system": "workers", + "instance": "group A" + }, + "Workers::Group#group B": { + "name": "CE::Workers::Group", + "label": "Workers::Group", + "kind": "class", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 127, + "brief": "A workload handle sharing physical capacity with other groups in its pool.", + "symbol": "Workers::Group", + "key": "Workers::Group#group B", + "system": "workers", + "instance": "group B" + }, + "%runtime": { + "key": "%runtime", + "label": "Runtime", + "kind": "system", + "system": "runtime", + "symbol": null + }, + "%display": { + "key": "%display", + "label": "Display", + "kind": "system", + "system": "display", + "symbol": null + }, + "%input": { + "key": "%input", + "label": "Input", + "kind": "system", + "system": "input", + "symbol": null + }, + "%rendering": { + "key": "%rendering", + "label": "Rendering", + "kind": "system", + "system": "rendering", + "symbol": null + }, + "%resources": { + "key": "%resources", + "label": "Resources", + "kind": "system", + "system": "resources", + "symbol": null + }, + "%text": { + "key": "%text", + "label": "Text", + "kind": "system", + "system": "text", + "symbol": null + }, + "%events": { + "key": "%events", + "label": "Events", + "kind": "system", + "system": "events", + "symbol": null + }, + "%dispatch": { + "key": "%dispatch", + "label": "Dispatch", + "kind": "system", + "system": "dispatch", + "symbol": null + }, + "%workers": { + "key": "%workers", + "label": "Workers", + "kind": "system", + "system": "workers", + "symbol": null + }, + "%assets": { + "key": "%assets", + "label": "Assets", + "kind": "system", + "system": "assets", + "symbol": null + }, + "%gui": { + "key": "%gui", + "label": "GUI", + "kind": "system", + "system": "gui", + "symbol": null + }, + "%memory": { + "key": "%memory", + "label": "Memory", + "kind": "system", + "system": "memory", + "symbol": null + }, + "%utilities": { + "key": "%utilities", + "label": "Utilities", + "kind": "system", + "system": "utilities", + "symbol": null + }, + "%state": { + "key": "%state", + "label": "State", + "kind": "system", + "system": "state", + "symbol": null + }, + "%audio": { + "key": "%audio", + "label": "Audio", + "kind": "system", + "system": "audio", + "symbol": null + }, + "%logging": { + "key": "%logging", + "label": "Logging", + "kind": "system", + "system": "logging", + "symbol": null + }, + "%diagnostics": { + "key": "%diagnostics", + "label": "Diagnostics", + "kind": "system", + "system": "diagnostics", + "symbol": null + }, + "Rendering::Geometry::Anchor": { + "name": "CE::Rendering::Geometry::Anchor", + "label": "Rendering::Geometry::Anchor", + "kind": "struct", + "file": "projects/engine/include/cheryl/math/anchor.h", + "line": 45, + "brief": "Builds local Y-up CPU quad geometry from a top-left-origin image rectangle and normalized pivot.", + "symbol": "Rendering::Geometry::Anchor", + "key": "Rendering::Geometry::Anchor", + "system": "utilities", + "instance": "" + }, + "Audio::Miniaudio::decode_file()": { + "name": "CE::Audio::Miniaudio::decode_file", + "label": "Audio::Miniaudio::decode_file()", + "kind": "operation", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 26, + "brief": "CPU-only file decoding into owned immutable PCM without opening output.", + "symbol": "Audio::Miniaudio::decode_file()", + "key": "Audio::Miniaudio::decode_file()", + "system": "audio", + "instance": "" + }, + "Workers::GroupPolicy": { + "name": "CE::Workers::GroupPolicy", + "label": "Workers::GroupPolicy", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 91, + "brief": "Immutable resolved policy reported at group creation, distinct from per-job native observations.", + "symbol": "Workers::GroupPolicy", + "key": "Workers::GroupPolicy", + "system": "workers", + "instance": "" + }, + "Logging::BackendStats": { + "name": "CE::Logging::BackendStats", + "label": "Logging::BackendStats", + "kind": "struct", + "file": "projects/engine/include/cheryl/core/logging/backend-guard.h", + "line": 12, + "brief": "Cumulative observations for one Log, including failed openings and all generations.", + "symbol": "Logging::BackendStats", + "key": "Logging::BackendStats", + "system": "logging", + "instance": "" + } + }, + "diagrams": [ + { + "id": "runtime-internal", + "system": "runtime", + "view": "internal", + "title": "Runtime \u00b7 engine integration", + "question": "Which engine contracts does one runtime session coordinate?", + "edges": [ + { + "source": "Runtime::GameRuntime", + "target": "Runtime::Context", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 61, + "member": "CE::Runtime::GameRuntime::engine_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 94, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 61, + "member": "CE::Runtime::GameRuntime::engine_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 95, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 95, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 70, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 70, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 107, + "member": "CE::Runtime::GameRuntime::run", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 79 + }, + { + "source": "Runtime::GameRuntime", + "target": "Game::AbstractGame", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 62, + "member": "CE::Runtime::GameRuntime::game_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 94, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 62, + "member": "CE::Runtime::GameRuntime::game_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 96, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 96, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 71, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 71, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 486, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 21 + }, + { + "source": "Runtime::GameRuntime", + "target": "Rendering::Frame", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 447, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 586, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 633, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 702, + "member": "CE::Runtime::GameRuntime::run_concurrent", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 702, + "member": "CE::Runtime::GameRuntime::run_concurrent", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 880, + "member": "CE::Runtime::GameRuntime::run_concurrent", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 1028, + "member": "CE::Runtime::GameRuntime::run_concurrent", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 1044, + "member": "CE::Runtime::GameRuntime::run_concurrent", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 10 + }, + { + "source": "Runtime::GameRuntime", + "target": "Rendering::Renderer", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 401, + "member": "CE::Runtime::GameRuntime::bind_renderer_maintenance", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 445, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 460, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 500, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 594, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 595, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 640, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 647, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 16 + }, + { + "source": "Runtime::GameRuntime", + "target": "Input::System", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 237, + "member": "CE::Runtime::GameRuntime::observe_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 238, + "member": "CE::Runtime::GameRuntime::observe_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 240, + "member": "CE::Runtime::GameRuntime::observe_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 446, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 476, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 478, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 494, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 530, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 17 + }, + { + "source": "Runtime::GameRuntime", + "target": "Runtime::RuntimeLifecycle", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 88, + "member": "CE::Runtime::GameRuntime::lifecycle_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 112, + "member": "CE::Runtime::GameRuntime::lifecycle", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 88, + "member": "CE::Runtime::GameRuntime::lifecycle_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 112, + "member": "CE::Runtime::GameRuntime::lifecycle", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 112, + "member": "CE::Runtime::GameRuntime::lifecycle", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 93, + "member": "CE::Runtime::GameRuntime::~GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 110, + "member": "CE::Runtime::GameRuntime::run", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 132, + "member": "CE::Runtime::GameRuntime::run", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 16 + }, + { + "source": "Runtime::Context", + "target": "Platform::DisplaySystem", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 65, + "member": "CE::Runtime::Context::display_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 93, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 108, + "member": "CE::Runtime::Context::display", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 65, + "member": "CE::Runtime::Context::display_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 86, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 86, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 94, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 21 + }, + { + "source": "Runtime::Context", + "target": "Rendering::PresentationSurface", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 66, + "member": "CE::Runtime::Context::surface_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 93, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 110, + "member": "CE::Runtime::Context::surface", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 66, + "member": "CE::Runtime::Context::surface_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 87, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 87, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 95, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 20 + }, + { + "source": "Runtime::Context", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 68, + "member": "CE::Runtime::Context::resources_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 93, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 112, + "member": "CE::Runtime::Context::resources", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 68, + "member": "CE::Runtime::Context::resources_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 89, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 89, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 97, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 21 + }, + { + "source": "Runtime::Context", + "target": "Runtime::PlatformDispatcher", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 73, + "member": "CE::Runtime::Context::platform_dispatcher_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 115, + "member": "CE::Runtime::Context::platform_dispatcher", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 73, + "member": "CE::Runtime::Context::platform_dispatcher_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 115, + "member": "CE::Runtime::Context::platform_dispatcher", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 115, + "member": "CE::Runtime::Context::platform_dispatcher", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 91, + "member": "CE::Runtime::Context::begin_session", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "Runtime::Context", + "target": "Workers::Group", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 78, + "member": "CE::Runtime::Context::worker_groups_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 78, + "member": "CE::Runtime::Context::worker_groups_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 115, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 115, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 118, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 23 + }, + { + "source": "Runtime::Context", + "target": "Events::EventBus", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 61, + "member": "CE::Runtime::Context::events_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 116, + "member": "CE::Runtime::Context::events", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 61, + "member": "CE::Runtime::Context::events_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 116, + "member": "CE::Runtime::Context::events", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 116, + "member": "CE::Runtime::Context::events", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 84, + "member": "CE::Runtime::Context::begin_session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 85, + "member": "CE::Runtime::Context::begin_session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 86, + "member": "CE::Runtime::Context::begin_session", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 9 + }, + { + "source": "Runtime::Context", + "target": "Text::FontService", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 82, + "member": "CE::Runtime::Context::fonts_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 114, + "member": "CE::Runtime::Context::fonts", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 82, + "member": "CE::Runtime::Context::fonts_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 114, + "member": "CE::Runtime::Context::fonts", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 114, + "member": "CE::Runtime::Context::fonts", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 5 + }, + { + "source": "Game::AbstractGame", + "target": "Game::TickContext", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 33, + "member": "CE::Game::AbstractGame::update", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 33, + "member": "CE::Game::AbstractGame::update", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 33, + "member": "CE::Game::AbstractGame::update", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Game::AbstractGame", + "target": "Rendering::FrameWriter", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 34, + "member": "CE::Game::AbstractGame::prepare_render_frame", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 34, + "member": "CE::Game::AbstractGame::prepare_render_frame", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 34, + "member": "CE::Game::AbstractGame::prepare_render_frame", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Game::TickContext", + "target": "Input::Tick", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 23, + "member": "CE::Game::TickContext::input", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 23, + "member": "CE::Game::TickContext::input", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 31, + "member": "CE::Game::TickContext::observed_seconds", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/tick-context.cpp", + "line": 19, + "member": "CE::Game::TickContext::button_simulation_seconds", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 4 + } + ], + "notes": [ + "GameRuntime borrows Context and AbstractGame for one session. Context owns compatible adapters and tracks its worker groups.", + "Audio and toolkit sessions have separate lifetimes; they are not implicit Context adapters." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/runtime.md", + "projects/engine/src/core/game-framework/game-runtime.cpp" + ], + "nodes": [ + "Runtime::GameRuntime", + "Runtime::Context", + "Game::AbstractGame", + "Rendering::Frame", + "Rendering::Renderer", + "Input::System", + "Runtime::RuntimeLifecycle", + "Platform::DisplaySystem", + "Rendering::PresentationSurface", + "Rendering::Resources::Provider", + "Runtime::PlatformDispatcher", + "Workers::Group", + "Events::EventBus", + "Text::FontService", + "Game::TickContext", + "Rendering::FrameWriter", + "Input::Tick" + ], + "namespace": "Runtime" + }, + { + "id": "display-internal", + "system": "display", + "view": "internal", + "title": "Display \u00b7 engine integration", + "question": "Who consumes the live window and who consumes copied dimensions?", + "edges": [ + { + "source": "Runtime::Context", + "target": "Platform::DisplaySystem", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 65, + "member": "CE::Runtime::Context::display_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 93, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 108, + "member": "CE::Runtime::Context::display", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 65, + "member": "CE::Runtime::Context::display_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 86, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 86, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 94, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 21 + }, + { + "source": "Platform::GLFW::DisplaySystem", + "target": "Platform::DisplaySystem", + "optional": false, + "evidence": [ + { + "kind": "inherits", + "file": "projects/modules/platform/native-glfw/include/cheryl/core/display/display-system.h", + "line": 39, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/modules/platform/native-glfw/include/cheryl/core/display/display-system.h", + "line": 39, + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Platform::DisplaySystem", + "target": "Platform::Window", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/display/display-system-interface.h", + "line": 23, + "member": "CE::Platform::DisplaySystem::active_window", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/display/display-system-interface.h", + "line": 26, + "member": "CE::Platform::DisplaySystem::create_window", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/display/display-system-interface.h", + "line": 27, + "member": "CE::Platform::DisplaySystem::activate_window", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/display/display-system-interface.h", + "line": 23, + "member": "CE::Platform::DisplaySystem::active_window", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/display/display-system-interface.h", + "line": 23, + "member": "CE::Platform::DisplaySystem::active_window", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/display/display-system-interface.h", + "line": 26, + "member": "CE::Platform::DisplaySystem::create_window", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/display/display-system-interface.h", + "line": 25, + "member": "CE::Platform::DisplaySystem::create_window", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/display/display-system-interface.h", + "line": 27, + "member": "CE::Platform::DisplaySystem::activate_window", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 9 + }, + { + "source": "Platform::GLFW::Window", + "target": "Platform::Window", + "optional": false, + "evidence": [ + { + "kind": "inherits", + "file": "projects/modules/platform/native-glfw/include/cheryl/core/display/window.h", + "line": 25, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/modules/platform/native-glfw/include/cheryl/core/display/window.h", + "line": 25, + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Input::System", + "target": "Platform::Window", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 32, + "member": "CE::Input::System::initialize", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 32, + "member": "CE::Input::System::initialize", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 32, + "member": "CE::Input::System::initialize", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Rendering::OpenGL::GLFWContext", + "target": "Platform::GLFW::Window", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-context.h", + "line": 26, + "member": "CE::Rendering::OpenGL::GLFWContext::window_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-context.h", + "line": 17, + "member": "CE::Rendering::OpenGL::GLFWContext::GLFWContext", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-context.h", + "line": 17, + "member": "CE::Rendering::OpenGL::GLFWContext::GLFWContext", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-context.h", + "line": 17, + "member": "CE::Rendering::OpenGL::GLFWContext::GLFWContext", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-context.h", + "line": 26, + "member": "CE::Rendering::OpenGL::GLFWContext::window_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/graphics/opengl/src/backends/opengl/glfw-context.cpp", + "line": 10, + "member": "CE::Rendering::OpenGL::GLFWContext::GLFWContext", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/src/backends/opengl/glfw-context.cpp", + "line": 10, + "member": "CE::Rendering::OpenGL::GLFWContext::GLFWContext", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/src/backends/opengl/glfw-context.cpp", + "line": 14, + "member": "CE::Rendering::OpenGL::GLFWContext::GLFWContext", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 13 + }, + { + "source": "Platform::Window", + "target": "Platform::Dimensions", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/display/window-interface.h", + "line": 23, + "member": "CE::Platform::Window::logical_size", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/display/window-interface.h", + "line": 23, + "member": "CE::Platform::Window::logical_size", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/display/window-interface.h", + "line": 23, + "member": "CE::Platform::Window::logical_size", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Platform::Window", + "target": "Platform::FramebufferSize", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/display/window-interface.h", + "line": 24, + "member": "CE::Platform::Window::framebuffer_size", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/display/window-interface.h", + "line": 24, + "member": "CE::Platform::Window::framebuffer_size", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/display/window-interface.h", + "line": 24, + "member": "CE::Platform::Window::framebuffer_size", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Platform::GLFW::Window", + "target": "Platform::WindowResized", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/display/window.cpp", + "line": 172, + "member": "CE::Platform::GLFW::Window::update_framebuffer_size", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 1 + }, + { + "source": "Platform::GLFW::Window", + "target": "Events::EventSystem", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/display/window.cpp", + "line": 173, + "member": "CE::Platform::GLFW::Window::update_framebuffer_size", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/display/window.cpp", + "line": 174, + "member": "CE::Platform::GLFW::Window::update_framebuffer_size", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/display/window.cpp", + "line": 175, + "member": "CE::Platform::GLFW::Window::update_framebuffer_size", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Game::TickContext", + "target": "Platform::FramebufferSize", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 24, + "member": "CE::Game::TickContext::framebuffer_size", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 24, + "member": "CE::Game::TickContext::framebuffer_size", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Rendering::Camera2D", + "target": "Platform::FramebufferSize", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/rendering/camera.cpp", + "line": 42, + "member": "CE::Rendering::Camera2D::recalculate_projection", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/rendering/camera.cpp", + "line": 43, + "member": "CE::Rendering::Camera2D::recalculate_projection", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 2 + } + ], + "notes": [ + "Display owns its windows. Input and presentation borrow them; a resize payload does not extend their lifetime.", + "Native window calls belong on platform. Simulation and cameras use copied tick dimensions." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/display.md" + ], + "nodes": [ + "Runtime::Context", + "Platform::DisplaySystem", + "Platform::GLFW::DisplaySystem", + "Platform::Window", + "Platform::GLFW::Window", + "Input::System", + "Rendering::OpenGL::GLFWContext", + "Platform::Dimensions", + "Platform::FramebufferSize", + "Platform::WindowResized", + "Events::EventSystem", + "Game::TickContext", + "Rendering::Camera2D" + ], + "namespace": "Platform" + }, + { + "id": "input-internal", + "system": "input", + "view": "internal", + "title": "Input \u00b7 engine integration", + "question": "How do native polling, action contexts and tick consumers connect?", + "edges": [ + { + "source": "Runtime::GameRuntime", + "target": "Input::System", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 237, + "member": "CE::Runtime::GameRuntime::observe_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 238, + "member": "CE::Runtime::GameRuntime::observe_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 240, + "member": "CE::Runtime::GameRuntime::observe_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 446, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 476, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 478, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 494, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 530, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 17 + }, + { + "source": "Platform::GLFW::InputSystem", + "target": "Input::System", + "optional": false, + "evidence": [ + { + "kind": "inherits", + "file": "projects/modules/platform/native-glfw/include/cheryl/core/controls/input-system.h", + "line": 53, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/modules/platform/native-glfw/include/cheryl/core/controls/input-system.h", + "line": 53, + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 272, + "member": "CE::Platform::GLFW::InputSystem::~InputSystem", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 299, + "member": "CE::Platform::GLFW::InputSystem::initialize", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 370, + "member": "CE::Platform::GLFW::InputSystem::initialize", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 432, + "member": "CE::Platform::GLFW::InputSystem::update", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 445, + "member": "CE::Platform::GLFW::InputSystem::update", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 467, + "member": "CE::Platform::GLFW::InputSystem::update", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 17 + }, + { + "source": "Input::System", + "target": "Platform::Window", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 32, + "member": "CE::Input::System::initialize", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 32, + "member": "CE::Input::System::initialize", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 32, + "member": "CE::Input::System::initialize", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Input::System", + "target": "Input::Actions::Bindings", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 36, + "member": "CE::Input::System::bindings", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 36, + "member": "CE::Input::System::bindings", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 36, + "member": "CE::Input::System::bindings", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 38, + "member": "CE::Input::System::action_contexts", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 39, + "member": "CE::Input::System::action_snapshot", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 17, + "member": "CE::Input::System::begin_input_poll", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 18, + "member": "CE::Input::System::begin_input_poll", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 35, + "member": "CE::Input::System::publish_input", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 8 + }, + { + "source": "Input::System", + "target": "Input::Actions::ContextSubmission", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 38, + "member": "CE::Input::System::action_contexts", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 38, + "member": "CE::Input::System::action_contexts", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 38, + "member": "CE::Input::System::action_contexts", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Input::Actions::ContextSubmission", + "target": "Input::Actions::ContextDefinition", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/action-context.h", + "line": 72, + "member": "CE::Input::Actions::ContextSubmission::attach", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/action-context.h", + "line": 72, + "member": "CE::Input::Actions::ContextSubmission::attach", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/action-context.h", + "line": 72, + "member": "CE::Input::Actions::ContextSubmission::attach", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/src/core/controls/action-context.cpp", + "line": 38, + "member": "CE::Input::Actions::ContextSubmission::attach", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/action-context.cpp", + "line": 38, + "member": "CE::Input::Actions::ContextSubmission::attach", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/action-context.cpp", + "line": 42, + "member": "CE::Input::Actions::ContextSubmission::attach", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/action-context.cpp", + "line": 44, + "member": "CE::Input::Actions::ContextSubmission::attach", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 7 + }, + { + "source": "Input::Actions::ContextSubmission", + "target": "Input::Actions::Context", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/action-context.h", + "line": 72, + "member": "CE::Input::Actions::ContextSubmission::attach", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/action-context.h", + "line": 72, + "member": "CE::Input::Actions::ContextSubmission::attach", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/action-context.h", + "line": 72, + "member": "CE::Input::Actions::ContextSubmission::attach", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/controls/action-context.cpp", + "line": 38, + "member": "CE::Input::Actions::ContextSubmission::attach", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/action-context.cpp", + "line": 38, + "member": "CE::Input::Actions::ContextSubmission::attach", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/action-context.cpp", + "line": 51, + "member": "CE::Input::Actions::ContextSubmission::attach", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "Input::System", + "target": "Input::Capture::Collector", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 80, + "member": "CE::Input::System::capture_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 67, + "member": "CE::Input::System::capture_buffer", + "access": "protected", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 67, + "member": "CE::Input::System::capture_buffer", + "access": "protected", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 67, + "member": "CE::Input::System::capture_buffer", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 80, + "member": "CE::Input::System::capture_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 16, + "member": "CE::Input::System::begin_input_poll", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 24, + "member": "CE::Input::System::capture", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 36, + "member": "CE::Input::System::publish_input", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 9 + }, + { + "source": "Input::System", + "target": "Input::Routing::Manager", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 81, + "member": "CE::Input::System::routing_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 48, + "member": "CE::Input::System::routing", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 48, + "member": "CE::Input::System::routing", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 48, + "member": "CE::Input::System::routing", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 81, + "member": "CE::Input::System::routing_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 15, + "member": "CE::Input::System::begin_input_poll", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 27, + "member": "CE::Input::System::routing", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 27, + "member": "CE::Input::System::routing", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 10 + }, + { + "source": "Input::System", + "target": "Input::PollSnapshot", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 82, + "member": "CE::Input::System::published_poll_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 51, + "member": "CE::Input::System::poll_snapshot", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 69, + "member": "CE::Input::System::publish_input", + "access": "protected", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 90, + "member": "CE::Input::System::commit_gamepad_availability", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 51, + "member": "CE::Input::System::poll_snapshot", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 51, + "member": "CE::Input::System::poll_snapshot", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 69, + "member": "CE::Input::System::publish_input", + "access": "protected", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 68, + "member": "CE::Input::System::publish_input", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 29 + }, + { + "source": "Runtime::GameRuntime", + "target": "Input::PollingBacklog", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 78, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 516, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 528, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 538, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 539, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 555, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 603, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 706, + "member": "CE::Runtime::GameRuntime::run_concurrent", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 17 + }, + { + "source": "Input::PollingBacklog", + "target": "Input::PollSnapshot", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/controls/polling-backlog.h", + "line": 48, + "member": "CE::Input::PollingBacklog::polls_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/polling-backlog.h", + "line": 39, + "member": "CE::Input::PollingBacklog::complete", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/polling-backlog.h", + "line": 42, + "member": "CE::Input::PollingBacklog::consume", + "access": "public", + "via": "member declaration" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/polling-backlog.h", + "line": 35, + "member": "CE::Input::PollingBacklog::completed_polls", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/polling-backlog.h", + "line": 39, + "member": "CE::Input::PollingBacklog::complete", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/polling-backlog.h", + "line": 39, + "member": "CE::Input::PollingBacklog::complete", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/polling-backlog.h", + "line": 42, + "member": "CE::Input::PollingBacklog::consume", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/polling-backlog.h", + "line": 42, + "member": "CE::Input::PollingBacklog::consume", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 20 + }, + { + "source": "Game::TickContext", + "target": "Input::Tick", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 23, + "member": "CE::Game::TickContext::input", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 23, + "member": "CE::Game::TickContext::input", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 31, + "member": "CE::Game::TickContext::observed_seconds", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/tick-context.cpp", + "line": 19, + "member": "CE::Game::TickContext::button_simulation_seconds", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 4 + }, + { + "source": "Input::Tick", + "target": "Input::Actions::View", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 74, + "member": "CE::Input::Tick::actions", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 75, + "member": "CE::Input::Tick::actions", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 74, + "member": "CE::Input::Tick::actions", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 74, + "member": "CE::Input::Tick::actions", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 75, + "member": "CE::Input::Tick::actions", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 75, + "member": "CE::Input::Tick::actions", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/controls/tick-input.cpp", + "line": 42, + "member": "CE::Input::Tick::actions", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/tick-input.cpp", + "line": 42, + "member": "CE::Input::Tick::actions", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 12 + }, + { + "source": "UI::TGUI::Session", + "target": "Input::System", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 58, + "member": "CE::UI::TGUI::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 59, + "member": "CE::UI::TGUI::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 58, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 58, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 60, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 60, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 175, + "member": "CE::UI::TGUI::Session::State::input", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 195, + "member": "CE::UI::TGUI::Session::State", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 18 + }, + { + "source": "UI::RmlUi::Session", + "target": "Input::System", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 60, + "member": "CE::UI::RmlUi::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 61, + "member": "CE::UI::RmlUi::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 60, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 60, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 62, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 62, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 162, + "member": "CE::UI::RmlUi::Session::State::input", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 186, + "member": "CE::UI::RmlUi::Session::State", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 18 + } + ], + "notes": [ + "Polling/direct binding changes run on platform; complete immutable observations cross into the tick.", + "Context attachments, capture and focus are separate contracts. A focus request does not enable record capture." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/input.md" + ], + "nodes": [ + "Runtime::GameRuntime", + "Input::System", + "Platform::GLFW::InputSystem", + "Platform::Window", + "Input::Actions::Bindings", + "Input::Actions::ContextSubmission", + "Input::Actions::ContextDefinition", + "Input::Actions::Context", + "Input::Capture::Collector", + "Input::Routing::Manager", + "Input::PollSnapshot", + "Input::PollingBacklog", + "Game::TickContext", + "Input::Tick", + "Input::Actions::View", + "UI::TGUI::Session", + "UI::RmlUi::Session" + ], + "namespace": "Input" + }, + { + "id": "assets-internal", + "system": "assets", + "view": "internal", + "title": "Assets \u00b7 engine integration", + "question": "How do loading, shared caches and drawable resources depend on each other?", + "edges": [ + { + "source": "Assets::Loading::Loader", + "target": "Assets::Loading::FileRegistry", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 173, + "member": "CE::Assets::Loading::Loader::prepare_selected", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 174, + "member": "CE::Assets::Loading::Loader::prepare_selected", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 375, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 417, + "member": "CE::Assets::Loading::Loader::register_files", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 4 + }, + { + "source": "Assets::Loading::Loader", + "target": "Files::FileIndex", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 172, + "member": "CE::Assets::Loading::Loader::prepare_selected", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 193, + "member": "CE::Assets::Loading::Loader::prepare_selected", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 416, + "member": "CE::Assets::Loading::Loader::register_files", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Assets::Loading::Loader", + "target": "Assets::Loading::Prepared", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 105, + "member": "CE::Assets::Loading::Loader::prepare_selected", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 112, + "member": "CE::Assets::Loading::Loader::prepare", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 113, + "member": "CE::Assets::Loading::Loader::prepare_graphics", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 114, + "member": "CE::Assets::Loading::Loader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 105, + "member": "CE::Assets::Loading::Loader::prepare_selected", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 105, + "member": "CE::Assets::Loading::Loader::prepare_selected", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 112, + "member": "CE::Assets::Loading::Loader::prepare", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 112, + "member": "CE::Assets::Loading::Loader::prepare", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 47 + }, + { + "source": "Assets::Loading::Loader", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 114, + "member": "CE::Assets::Loading::Loader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 115, + "member": "CE::Assets::Loading::Loader::load_assets", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 114, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 114, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 115, + "member": "CE::Assets::Loading::Loader::load_assets", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 115, + "member": "CE::Assets::Loading::Loader::load_assets", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 296, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 296, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 11 + }, + { + "source": "Assets::Loading::Loader", + "target": "Assets::Sprites::Manager", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 308, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 388, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Assets::Loading::Loader", + "target": "Assets::Tilesets::Manager", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 310, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 389, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Assets::Sprites::Manager", + "target": "Assets::Cache::Store", + "optional": false, + "evidence": [ + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/sprite-mgr.h", + "line": 25, + "via": "base declaration" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/sprite-mgr.cpp", + "line": 16, + "member": "CE::Assets::Sprites::Manager::get_asset", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/sprite-mgr.cpp", + "line": 21, + "member": "CE::Assets::Sprites::Manager::get_assets", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/sprite-mgr.cpp", + "line": 22, + "member": "CE::Assets::Sprites::Manager::get_assets", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/sprite-mgr.cpp", + "line": 31, + "member": "CE::Assets::Sprites::Manager::load", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/sprite-mgr.cpp", + "line": 35, + "member": "CE::Assets::Sprites::Manager::load", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/sprite-mgr.cpp", + "line": 37, + "member": "CE::Assets::Sprites::Manager::load", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/sprite-mgr.cpp", + "line": 42, + "member": "CE::Assets::Sprites::Manager::load", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 9 + }, + { + "source": "Assets::Tilesets::Manager", + "target": "Assets::Cache::Store", + "optional": false, + "evidence": [ + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/tileset-mgr.h", + "line": 22, + "via": "base declaration" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/tileset-mgr.cpp", + "line": 16, + "member": "CE::Assets::Tilesets::Manager::load", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/tileset-mgr.cpp", + "line": 20, + "member": "CE::Assets::Tilesets::Manager::load", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/tileset-mgr.cpp", + "line": 22, + "member": "CE::Assets::Tilesets::Manager::load", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/tileset-mgr.cpp", + "line": 27, + "member": "CE::Assets::Tilesets::Manager::load", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/tileset-mgr.cpp", + "line": 60, + "member": "CE::Assets::Tilesets::Manager::load", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "Assets::Cache::Store", + "target": "Assets::Cache::AssetCatalogue", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 120, + "member": "Assets::Cache::Store::Catalogue", + "via": "API declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Assets::Cache::Store", + "target": "Memory::Objects::Reservation", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 313, + "member": "CE::Assets::Cache::Store::reserve", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 1 + }, + { + "source": "Memory::Objects::Reservation", + "target": "Memory::Allocators::ObjectPoolAllocator", + "optional": false, + "evidence": [ + { + "kind": "defaults", + "file": "projects/engine/include/cheryl/core/resources/objects/object-reservation.hpp", + "line": 52, + "via": "template default" + } + ], + "evidence_count": 1 + }, + { + "source": "Memory::Allocators::ObjectPoolAllocator", + "target": "Memory::Objects::Pool", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/resources/memory/allocators/object-pool-allocator.hpp", + "line": 22, + "member": "Memory::Allocators::ObjectPoolAllocator::manager_type", + "via": "API declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Assets::Sprites::Asset", + "target": "Rendering::Resources::Image", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/assets/types/2d/sprite.cpp", + "line": 68, + "member": "CE::Assets::Sprites::Asset::Asset", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 1 + }, + { + "source": "Assets::Sprites::Asset", + "target": "Rendering::Geometry::Buffer2D", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/assets/types/2d/sprite.cpp", + "line": 68, + "member": "CE::Assets::Sprites::Asset::Asset", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Assets::Sprites::Asset", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Submission::resolve_tile()", + "target": "Assets::Tilesets::Asset", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 48, + "member": "Rendering::Submission::resolve_tile()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Rendering::DrawPacket2D", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Submission::resolve_tile()", + "target": "Rendering::DrawPacket2D", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 48, + "member": "Rendering::Submission::resolve_tile()", + "via": "operation signature" + }, + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 55, + "member": "Rendering::Submission::resolve_tile()", + "via": "operation signature" + }, + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 57, + "member": "Rendering::Submission::resolve_tile()", + "via": "operation signature" + } + ], + "evidence_count": 3 + } + ], + "notes": [ + "Preparation is CPU-only; upload/cache publication follows the provider owner and can publish entries incrementally.", + "Loader does not schedule worker jobs. Worker/dispatch wiring belongs in application composition." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/assets.md" + ], + "nodes": [ + "Assets::Loading::Loader", + "Assets::Loading::FileRegistry", + "Files::FileIndex", + "Assets::Loading::Prepared", + "Rendering::Resources::Provider", + "Assets::Sprites::Manager", + "Assets::Tilesets::Manager", + "Assets::Cache::Store", + "Assets::Cache::AssetCatalogue", + "Memory::Objects::Reservation", + "Memory::Allocators::ObjectPoolAllocator", + "Memory::Objects::Pool", + "Assets::Sprites::Asset", + "Rendering::Resources::Image", + "Rendering::Geometry::Buffer2D", + "Rendering::Submission::resolve_sprite()", + "Rendering::Submission::resolve_tile()", + "Assets::Tilesets::Asset", + "Rendering::DrawPacket2D" + ], + "namespace": "Assets" + }, + { + "id": "resources-internal", + "system": "resources", + "view": "internal", + "title": "Resources \u00b7 engine integration", + "question": "Who creates or retains graphics generations, and what implements their lifetime?", + "edges": [ + { + "source": "Runtime::Context", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 68, + "member": "CE::Runtime::Context::resources_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 93, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 112, + "member": "CE::Runtime::Context::resources", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 68, + "member": "CE::Runtime::Context::resources_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 89, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 89, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 97, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 21 + }, + { + "source": "Assets::Loading::Loader", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 114, + "member": "CE::Assets::Loading::Loader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 115, + "member": "CE::Assets::Loading::Loader::load_assets", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 114, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 114, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 115, + "member": "CE::Assets::Loading::Loader::load_assets", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 115, + "member": "CE::Assets::Loading::Loader::load_assets", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 296, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 296, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 11 + }, + { + "source": "UI::TGUI::SceneUploader", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 56, + "member": "CE::UI::TGUI::SceneUploader::SceneUploader", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 61, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 56, + "member": "CE::UI::TGUI::SceneUploader::SceneUploader", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 56, + "member": "CE::UI::TGUI::SceneUploader::SceneUploader", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 61, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 26 + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 51, + "member": "CE::UI::RmlUi::SceneUploader::SceneUploader", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 56, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 51, + "member": "CE::UI::RmlUi::SceneUploader::SceneUploader", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 51, + "member": "CE::UI::RmlUi::SceneUploader::SceneUploader", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 56, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 25 + }, + { + "source": "Text::Rendering::upload()", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 116, + "member": "Text::Rendering::upload()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::OpenGL::ResourceProvider", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "kind": "inherits", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/resource-provider.h", + "line": 23, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/resource-provider.h", + "line": 23, + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Rendering::Resources::Provider", + "target": "Rendering::Resources::Image", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 63, + "member": "CE::Rendering::Resources::Provider::load_image", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 66, + "member": "CE::Rendering::Resources::Provider::create_image", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 74, + "member": "CE::Rendering::Resources::Provider::create_font_atlas", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 63, + "member": "CE::Rendering::Resources::Provider::load_image", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 63, + "member": "CE::Rendering::Resources::Provider::load_image", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 66, + "member": "CE::Rendering::Resources::Provider::create_image", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 66, + "member": "CE::Rendering::Resources::Provider::create_image", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 74, + "member": "CE::Rendering::Resources::Provider::create_font_atlas", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 12 + }, + { + "source": "Rendering::Resources::Provider", + "target": "Rendering::Geometry::Buffer2D", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 77, + "member": "CE::Rendering::Resources::Provider::upload_geometry", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 81, + "member": "CE::Rendering::Resources::Provider::upload_geometry", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 83, + "member": "CE::Rendering::Resources::Provider::upload_geometry", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 77, + "member": "CE::Rendering::Resources::Provider::upload_geometry", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 76, + "member": "CE::Rendering::Resources::Provider::upload_geometry", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 81, + "member": "CE::Rendering::Resources::Provider::upload_geometry", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 80, + "member": "CE::Rendering::Resources::Provider::upload_geometry", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 83, + "member": "CE::Rendering::Resources::Provider::upload_geometry", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 14 + }, + { + "source": "Rendering::Resources::Provider", + "target": "Rendering::Resources::Program", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 89, + "member": "CE::Rendering::Resources::Provider::link_program", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 89, + "member": "CE::Rendering::Resources::Provider::link_program", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 89, + "member": "CE::Rendering::Resources::Provider::link_program", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Rendering::Resources::Provider", + "target": "Rendering::GraphicsDomain", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 59, + "member": "CE::Rendering::Resources::Provider::graphics_domain", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 59, + "member": "CE::Rendering::Resources::Provider::graphics_domain", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 59, + "member": "CE::Rendering::Resources::Provider::graphics_domain", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Rendering::OpenGL::ResourceProvider", + "target": "Rendering::OpenGL::ResourceLifetime", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/src/backends/opengl/builtin-materials.cpp", + "line": 82, + "member": "CE::Rendering::OpenGL::ResourceProvider::build_builtin_material", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/src/backends/opengl/resource-provider.cpp", + "line": 29, + "member": "CE::Rendering::OpenGL::ResourceProvider::create_image", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/src/backends/opengl/resource-provider.cpp", + "line": 42, + "member": "CE::Rendering::OpenGL::ResourceProvider::create_font_atlas", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/src/backends/opengl/resource-provider.cpp", + "line": 49, + "member": "CE::Rendering::OpenGL::ResourceProvider::create_sampler", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/src/backends/opengl/resource-provider.cpp", + "line": 50, + "member": "CE::Rendering::OpenGL::ResourceProvider::create_sampler", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/src/backends/opengl/resource-provider.cpp", + "line": 66, + "member": "CE::Rendering::OpenGL::ResourceProvider::upload_geometry", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/src/backends/opengl/resource-provider.cpp", + "line": 70, + "member": "CE::Rendering::OpenGL::ResourceProvider::link_program", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/src/backends/opengl/resource-provider.cpp", + "line": 77, + "member": "CE::Rendering::OpenGL::ResourceProvider::upload_geometry", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 12 + }, + { + "source": "Rendering::DrawPacket2D", + "target": "Rendering::Resources::Material", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 34, + "member": "CE::Rendering::DrawPacket2D::material", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 34, + "member": "CE::Rendering::DrawPacket2D::material", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Rendering::DrawPacket2D", + "target": "Rendering::Geometry::Buffer2D", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 33, + "member": "CE::Rendering::DrawPacket2D::geometry", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 33, + "member": "CE::Rendering::DrawPacket2D::geometry", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Rendering::Resources::Material", + "target": "Rendering::Resources::Pipeline", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/assets/resources/pipeline.cpp", + "line": 61, + "member": "CE::Rendering::Resources::Material::Material", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/assets/resources/pipeline.cpp", + "line": 63, + "member": "CE::Rendering::Resources::Material::Material", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/assets/resources/pipeline.cpp", + "line": 77, + "member": "CE::Rendering::Resources::Material::resolve", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Rendering::OpenGL::Pipeline", + "target": "Rendering::Resources::Pipeline", + "optional": false, + "evidence": [ + { + "kind": "inherits", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/pipeline.h", + "line": 39, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/pipeline.h", + "line": 39, + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/src/backends/opengl/pipeline.cpp", + "line": 82, + "member": "CE::Rendering::OpenGL::Pipeline::Pipeline", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/src/backends/opengl/pipeline.cpp", + "line": 86, + "member": "CE::Rendering::OpenGL::Pipeline::Pipeline", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/src/backends/opengl/pipeline.cpp", + "line": 102, + "member": "CE::Rendering::OpenGL::Pipeline::Pipeline", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/src/backends/opengl/pipeline.cpp", + "line": 180, + "member": "CE::Rendering::OpenGL::Pipeline::prepare_parameters", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/src/backends/opengl/pipeline.cpp", + "line": 197, + "member": "CE::Rendering::OpenGL::Pipeline::apply_fixed_state", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/src/backends/opengl/pipeline.cpp", + "line": 250, + "member": "CE::Rendering::OpenGL::Pipeline::draw", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 8 + }, + { + "source": "Rendering::OpenGL::Pipeline", + "target": "Rendering::OpenGL::Program", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/pipeline.h", + "line": 46, + "member": "CE::Rendering::OpenGL::Pipeline::program_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/pipeline.h", + "line": 50, + "member": "CE::Rendering::OpenGL::Pipeline::Pipeline", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/pipeline.h", + "line": 46, + "member": "CE::Rendering::OpenGL::Pipeline::program_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/pipeline.h", + "line": 50, + "member": "CE::Rendering::OpenGL::Pipeline::Pipeline", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/pipeline.h", + "line": 50, + "member": "CE::Rendering::OpenGL::Pipeline::Pipeline", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/pipeline.h", + "line": 51, + "member": "CE::Rendering::OpenGL::Pipeline::resource_domain", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/graphics/opengl/src/backends/opengl/pipeline.cpp", + "line": 81, + "member": "CE::Rendering::OpenGL::Pipeline::Pipeline", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/src/backends/opengl/pipeline.cpp", + "line": 81, + "member": "CE::Rendering::OpenGL::Pipeline::Pipeline", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 15 + }, + { + "source": "Rendering::OpenGL::Program", + "target": "Rendering::Resources::Program", + "optional": false, + "evidence": [ + { + "kind": "inherits", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glslprogram.h", + "line": 47, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glslprogram.h", + "line": 47, + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + } + ], + "notes": [ + "Logical handles retain immutable generations. They do not keep a closed native graphics domain usable.", + "OpenGL retirement is collected on graphics; final handle release alone does not perform a GL deletion." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/resources.md" + ], + "nodes": [ + "Runtime::Context", + "Rendering::Resources::Provider", + "Assets::Loading::Loader", + "UI::TGUI::SceneUploader", + "UI::RmlUi::SceneUploader", + "Text::Rendering::upload()", + "Rendering::OpenGL::ResourceProvider", + "Rendering::Resources::Image", + "Rendering::Geometry::Buffer2D", + "Rendering::Resources::Program", + "Rendering::GraphicsDomain", + "Rendering::OpenGL::ResourceLifetime", + "Rendering::DrawPacket2D", + "Rendering::Resources::Material", + "Rendering::Resources::Pipeline", + "Rendering::OpenGL::Pipeline", + "Rendering::OpenGL::Program" + ], + "namespace": "Rendering::Resources" + }, + { + "id": "rendering-internal", + "system": "rendering", + "view": "internal", + "title": "Rendering \u00b7 engine integration", + "question": "What does playback consume, and what prepares the immutable frame?", + "edges": [ + { + "source": "Runtime::GameRuntime", + "target": "Rendering::Renderer", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 401, + "member": "CE::Runtime::GameRuntime::bind_renderer_maintenance", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 445, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 460, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 500, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 594, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 595, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 640, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 647, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 16 + }, + { + "source": "Runtime::GameRuntime", + "target": "Rendering::PresentationSurface", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 596, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 1054, + "member": "CE::Runtime::GameRuntime::run_concurrent", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Runtime::GameRuntime", + "target": "Rendering::Frame", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 447, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 586, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 633, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 702, + "member": "CE::Runtime::GameRuntime::run_concurrent", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 702, + "member": "CE::Runtime::GameRuntime::run_concurrent", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 880, + "member": "CE::Runtime::GameRuntime::run_concurrent", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 1028, + "member": "CE::Runtime::GameRuntime::run_concurrent", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 1044, + "member": "CE::Runtime::GameRuntime::run_concurrent", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 10 + }, + { + "source": "Rendering::OpenGL::Renderer", + "target": "Rendering::Renderer", + "optional": false, + "evidence": [ + { + "kind": "inherits", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/renderer.h", + "line": 23, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/renderer.h", + "line": 23, + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/renderer.h", + "line": 40, + "member": "CE::Rendering::OpenGL::Renderer::~DomainPublication", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Rendering::OpenGL::GLFWContext", + "target": "Rendering::OpenGL::Context", + "optional": false, + "evidence": [ + { + "kind": "inherits", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-context.h", + "line": 15, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-context.h", + "line": 15, + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Rendering::OpenGL::Context", + "target": "Rendering::PresentationSurface", + "optional": false, + "evidence": [ + { + "kind": "inherits", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/context.h", + "line": 11, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/context.h", + "line": 11, + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Rendering::FrameWriter", + "target": "Rendering::Frame", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 121, + "member": "CE::Rendering::FrameWriter::frame_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 124, + "member": "CE::Rendering::FrameWriter::FrameWriter", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 121, + "member": "CE::Rendering::FrameWriter::frame_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 124, + "member": "CE::Rendering::FrameWriter::FrameWriter", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 124, + "member": "CE::Rendering::FrameWriter::FrameWriter", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 126, + "member": "CE::Rendering::FrameWriter::FrameWriter", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 130, + "member": "CE::Rendering::FrameWriter::reserve_passes", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 138, + "member": "CE::Rendering::FrameWriter::begin_pass", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 13 + }, + { + "source": "Rendering::FrameWriter", + "target": "Rendering::PassWriter", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 132, + "member": "CE::Rendering::FrameWriter::begin_pass", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 132, + "member": "CE::Rendering::FrameWriter::begin_pass", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 132, + "member": "CE::Rendering::FrameWriter::begin_pass", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 147, + "member": "CE::Rendering::FrameWriter::begin_pass", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 4 + }, + { + "source": "Rendering::PassWriter", + "target": "Rendering::DrawPacket2D", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 88, + "member": "CE::Rendering::PassWriter::add", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 95, + "member": "CE::Rendering::PassWriter::add", + "access": "public", + "via": "member declaration" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 76, + "member": "CE::Rendering::PassWriter::reserve_draws", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 88, + "member": "CE::Rendering::PassWriter::add", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 88, + "member": "CE::Rendering::PassWriter::add", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 91, + "member": "CE::Rendering::PassWriter::add", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 92, + "member": "CE::Rendering::PassWriter::add", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 95, + "member": "CE::Rendering::PassWriter::add", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 13 + }, + { + "source": "Rendering::Frame", + "target": "Rendering::Pass", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 40, + "member": "CE::Rendering::Frame::passes_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 52, + "member": "CE::Rendering::Frame::passes", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 40, + "member": "CE::Rendering::Frame::passes_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 52, + "member": "CE::Rendering::Frame::passes", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 52, + "member": "CE::Rendering::Frame::passes", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 58, + "member": "CE::Rendering::Frame::recycle", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 59, + "member": "CE::Rendering::Frame::recycle", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 7 + }, + { + "source": "Rendering::Pass", + "target": "Rendering::DrawPacket2D", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 24, + "member": "CE::Rendering::Pass::draws", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 24, + "member": "CE::Rendering::Pass::draws", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Rendering::Renderer", + "target": "Rendering::Frame", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/renderer.h", + "line": 46, + "member": "CE::Rendering::Renderer::render", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/renderer.h", + "line": 46, + "member": "CE::Rendering::Renderer::render", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/renderer.h", + "line": 46, + "member": "CE::Rendering::Renderer::render", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Rendering::DrawPacket2D", + "target": "Rendering::Resources::Material", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 34, + "member": "CE::Rendering::DrawPacket2D::material", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 34, + "member": "CE::Rendering::DrawPacket2D::material", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Rendering::DrawPacket2D", + "target": "Rendering::Geometry::Buffer2D", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 33, + "member": "CE::Rendering::DrawPacket2D::geometry", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 33, + "member": "CE::Rendering::DrawPacket2D::geometry", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Assets::Sprites::Asset", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Rendering::Submission::Context2D", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Rendering::DrawPacket2D", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "Preparation resolves copied visual state. Playback consumes complete frames and retained resources rather than mutable game objects.", + "Arrows describe dependencies, not the chronological direction in which a frame is published." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/rendering.md" + ], + "nodes": [ + "Runtime::GameRuntime", + "Rendering::Renderer", + "Rendering::PresentationSurface", + "Rendering::Frame", + "Rendering::OpenGL::Renderer", + "Rendering::OpenGL::GLFWContext", + "Rendering::OpenGL::Context", + "Rendering::FrameWriter", + "Rendering::PassWriter", + "Rendering::DrawPacket2D", + "Rendering::Pass", + "Rendering::Resources::Material", + "Rendering::Geometry::Buffer2D", + "Rendering::Submission::resolve_sprite()", + "Assets::Sprites::Asset", + "Rendering::Submission::Context2D" + ], + "namespace": "Rendering" + }, + { + "id": "text-internal", + "system": "text", + "view": "internal", + "title": "Text \u00b7 engine integration", + "question": "Where do CPU font/layout work and graphics publication meet?", + "edges": [ + { + "source": "Runtime::Context", + "target": "Text::FontService", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 82, + "member": "CE::Runtime::Context::fonts_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 114, + "member": "CE::Runtime::Context::fonts", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 82, + "member": "CE::Runtime::Context::fonts_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 114, + "member": "CE::Runtime::Context::fonts", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 114, + "member": "CE::Runtime::Context::fonts", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 5 + }, + { + "source": "Text::FontService", + "target": "Text::Font", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 54, + "member": "CE::Text::FontService::default_font", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 55, + "member": "CE::Text::FontService::get", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 56, + "member": "CE::Text::FontService::all", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 57, + "member": "CE::Text::FontService::fallbacks", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 58, + "member": "CE::Text::FontService::choose_random", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 59, + "member": "CE::Text::FontService::choose_random", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 60, + "member": "CE::Text::FontService::restore_or_choose", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 62, + "member": "CE::Text::FontService::restore_or_choose", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 93 + }, + { + "source": "Text::Font", + "target": "Text::FontCollection", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font.h", + "line": 48, + "member": "CE::Text::Font::collection", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font.h", + "line": 48, + "member": "CE::Text::Font::collection", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/text/font.h", + "line": 48, + "member": "CE::Text::Font::collection", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/text/font-service.cpp", + "line": 53, + "member": "CE::Text::Font::collection", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/font-service.cpp", + "line": 53, + "member": "CE::Text::Font::collection", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/font-service.cpp", + "line": 75, + "member": "CE::Text::Font::collection", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "Text::layout_text()", + "target": "Text::Font", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/text/layout.h", + "line": 84, + "member": "Text::layout_text()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Text::layout_text()", + "target": "Text::ShapedText", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/text/layout.h", + "line": 83, + "member": "Text::layout_text()", + "via": "operation signature" + }, + { + "file": "projects/engine/include/cheryl/text/layout.h", + "line": 84, + "member": "Text::layout_text()", + "via": "operation signature" + } + ], + "evidence_count": 2 + }, + { + "source": "Text::Rendering::prepare()", + "target": "Text::ShapedText", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 115, + "member": "Text::Rendering::prepare()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Text::Rendering::prepare()", + "target": "Text::Rendering::Prepared", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 115, + "member": "Text::Rendering::prepare()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Text::Rendering::upload()", + "target": "Text::Rendering::Prepared", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 116, + "member": "Text::Rendering::upload()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Text::Rendering::upload()", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 116, + "member": "Text::Rendering::upload()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Text::Rendering::upload()", + "target": "Text::Rendering::Generation", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 116, + "member": "Text::Rendering::upload()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Text::Rendering::Generation", + "target": "Rendering::Resources::Material", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 89, + "member": "CE::Text::Rendering::Generation::material_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 91, + "member": "CE::Text::Rendering::Generation::Generation", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 112, + "member": "CE::Text::Rendering::Generation::material", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 89, + "member": "CE::Text::Rendering::Generation::material_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 98, + "member": "CE::Text::Rendering::Generation::Generation", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 98, + "member": "CE::Text::Rendering::Generation::Generation", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 112, + "member": "CE::Text::Rendering::Generation::material", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 112, + "member": "CE::Text::Rendering::Generation::material", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 11 + }, + { + "source": "Text::Rendering::Updater", + "target": "Text::Rendering::Generation", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 57, + "member": "CE::Text::Rendering::Updater::current", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 57, + "member": "CE::Text::Rendering::Updater::current", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 57, + "member": "CE::Text::Rendering::Updater::current", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 46, + "member": "CE::Text::Rendering::Updater::State::uploading", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 47, + "member": "CE::Text::Rendering::Updater::State::current", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 66, + "member": "CE::Text::Rendering::Updater::fail", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 72, + "member": "CE::Text::Rendering::Updater::pending", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 83, + "member": "CE::Text::Rendering::Updater::Updater", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 18 + }, + { + "source": "Text::Rendering::Updater", + "target": "Workers::Group", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 36, + "member": "CE::Text::Rendering::Updater::State::workers", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 52, + "member": "CE::Text::Rendering::Updater::State", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 146, + "member": "CE::Text::Rendering::Updater::update", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Text::Rendering::Updater", + "target": "Runtime::PlatformDispatcher::Submission", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 37, + "member": "CE::Text::Rendering::Updater::State::platform", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 53, + "member": "CE::Text::Rendering::Updater::State", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 133, + "member": "CE::Text::Rendering::Updater::update", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "UI::TGUI::Session", + "target": "Text::FontService", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 59, + "member": "CE::UI::TGUI::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 61, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 61, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 228, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 228, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 233, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 235, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 245, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 8 + }, + { + "source": "UI::RmlUi::Session", + "target": "Text::FontService", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 61, + "member": "CE::UI::RmlUi::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 63, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 63, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 276, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 276, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 281, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 284, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 285, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 9 + } + ], + "notes": [ + "Font lookup/layout/preparation are CPU stages. Upload follows graphics affinity; published frames can keep an older Generation.", + "Updater owns replacement scheduling and adoption. Toolkit authoring remains within each toolkit integration." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/text.md" + ], + "nodes": [ + "Runtime::Context", + "Text::FontService", + "Text::Font", + "Text::FontCollection", + "Text::layout_text()", + "Text::ShapedText", + "Text::Rendering::prepare()", + "Text::Rendering::Prepared", + "Text::Rendering::upload()", + "Rendering::Resources::Provider", + "Text::Rendering::Generation", + "Rendering::Resources::Material", + "Text::Rendering::Updater", + "Workers::Group", + "Runtime::PlatformDispatcher::Submission", + "UI::TGUI::Session", + "UI::RmlUi::Session" + ], + "namespace": "Text" + }, + { + "id": "gui-internal", + "system": "gui", + "view": "internal", + "title": "GUI \u00b7 engine integration", + "question": "How do the two toolkit modules connect input, fonts, uploads and retained passes?", + "edges": [ + { + "source": "UI::TGUI::Session", + "target": "Input::System", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 58, + "member": "CE::UI::TGUI::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 59, + "member": "CE::UI::TGUI::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 58, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 58, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 60, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 60, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 175, + "member": "CE::UI::TGUI::Session::State::input", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 195, + "member": "CE::UI::TGUI::Session::State", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 18 + }, + { + "source": "UI::RmlUi::Session", + "target": "Input::System", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 60, + "member": "CE::UI::RmlUi::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 61, + "member": "CE::UI::RmlUi::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 60, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 60, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 62, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 62, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 162, + "member": "CE::UI::RmlUi::Session::State::input", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 186, + "member": "CE::UI::RmlUi::Session::State", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 18 + }, + { + "source": "UI::TGUI::Session", + "target": "Text::FontService", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 59, + "member": "CE::UI::TGUI::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 61, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 61, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 228, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 228, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 233, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 235, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 245, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 8 + }, + { + "source": "UI::RmlUi::Session", + "target": "Text::FontService", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 61, + "member": "CE::UI::RmlUi::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 63, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 63, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 276, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 276, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 281, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 284, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 285, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 9 + }, + { + "source": "UI::TGUI::Session", + "target": "UI::TGUI::RecordedScene", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 104, + "member": "CE::UI::TGUI::Session::record", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 104, + "member": "CE::UI::TGUI::Session::record", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 104, + "member": "CE::UI::TGUI::Session::record", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 456, + "member": "CE::UI::TGUI::Session::record", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 456, + "member": "CE::UI::TGUI::Session::record", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 463, + "member": "CE::UI::TGUI::Session::record", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "UI::RmlUi::Session", + "target": "UI::RmlUi::RecordedScene", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 102, + "member": "CE::UI::RmlUi::Session::record", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 102, + "member": "CE::UI::RmlUi::Session::record", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 102, + "member": "CE::UI::RmlUi::Session::record", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 513, + "member": "CE::UI::RmlUi::Session::record", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 513, + "member": "CE::UI::RmlUi::Session::record", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 521, + "member": "CE::UI::RmlUi::Session::record", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "UI::TGUI::SceneUploader", + "target": "UI::TGUI::RecordedScene", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 61, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 65, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 61, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 61, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 26 + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "UI::RmlUi::RecordedScene", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 56, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 60, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 56, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 56, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 26 + }, + { + "source": "UI::TGUI::SceneUploader", + "target": "Runtime::PlatformDispatcher::Submission", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 65, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 65, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 65, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/src/scene.cpp", + "line": 169, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/scene.cpp", + "line": 169, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 12 + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "Runtime::PlatformDispatcher::Submission", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 60, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 60, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 60, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 152, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 152, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 12 + }, + { + "source": "UI::TGUI::SceneUploader", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 56, + "member": "CE::UI::TGUI::SceneUploader::SceneUploader", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 61, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 56, + "member": "CE::UI::TGUI::SceneUploader::SceneUploader", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 56, + "member": "CE::UI::TGUI::SceneUploader::SceneUploader", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 61, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 26 + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 51, + "member": "CE::UI::RmlUi::SceneUploader::SceneUploader", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 56, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 51, + "member": "CE::UI::RmlUi::SceneUploader::SceneUploader", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 51, + "member": "CE::UI::RmlUi::SceneUploader::SceneUploader", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 56, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 25 + }, + { + "source": "UI::TGUI::SceneUploader", + "target": "UI::TGUI::Scene", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 61, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 65, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 61, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 60, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 36 + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "UI::RmlUi::Scene", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 56, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 60, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 56, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 55, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 36 + }, + { + "source": "UI::TGUI::Scene", + "target": "Rendering::PassWriter", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/scene.cpp", + "line": 186, + "member": "CE::UI::TGUI::Scene::write", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/scene.cpp", + "line": 187, + "member": "CE::UI::TGUI::Scene::write", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "UI::RmlUi::Scene", + "target": "Rendering::PassWriter", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 169, + "member": "CE::UI::RmlUi::Scene::write", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 170, + "member": "CE::UI::RmlUi::Scene::write", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 2 + } + ], + "notes": [ + "TGUI and RmlUi are alternatives with separate native authoring APIs; there is no common Engine widget/session manager.", + "Sessions mutate/record on UI or simulation. Uploaders publish on graphics; frames retain scenes without live widgets." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/gui.md" + ], + "nodes": [ + "UI::TGUI::Session", + "Input::System", + "UI::RmlUi::Session", + "Text::FontService", + "UI::TGUI::RecordedScene", + "UI::RmlUi::RecordedScene", + "UI::TGUI::SceneUploader", + "UI::RmlUi::SceneUploader", + "Runtime::PlatformDispatcher::Submission", + "Rendering::Resources::Provider", + "UI::TGUI::Scene", + "UI::RmlUi::Scene", + "Rendering::PassWriter" + ], + "namespace": "UI" + }, + { + "id": "audio-internal", + "system": "audio", + "view": "internal", + "title": "Audio \u00b7 engine integration", + "question": "What is the audio implementation, and how do explicit notification pumps reach engine owners?", + "edges": [ + { + "source": "Audio::Miniaudio::System", + "target": "Audio::System", + "optional": false, + "evidence": [ + { + "kind": "inherits", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 31, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 31, + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Audio::System", + "target": "Audio::Clip", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 75, + "member": "CE::Audio::System::play", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 75, + "member": "CE::Audio::System::play", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 75, + "member": "CE::Audio::System::play", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Audio::System", + "target": "Audio::PlaybackOptions", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 75, + "member": "CE::Audio::System::play", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 77, + "member": "CE::Audio::System::stream", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 75, + "member": "CE::Audio::System::play", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 75, + "member": "CE::Audio::System::play", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 77, + "member": "CE::Audio::System::stream", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 77, + "member": "CE::Audio::System::stream", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "Audio::System", + "target": "Audio::Voice", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 75, + "member": "CE::Audio::System::play", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 77, + "member": "CE::Audio::System::stream", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 75, + "member": "CE::Audio::System::play", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 74, + "member": "CE::Audio::System::play", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 77, + "member": "CE::Audio::System::stream", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 76, + "member": "CE::Audio::System::stream", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "Audio::Voice", + "target": "Audio::VoiceSnapshot", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 52, + "member": "CE::Audio::Voice::snapshot", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 52, + "member": "CE::Audio::Voice::snapshot", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 52, + "member": "CE::Audio::Voice::snapshot", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Audio::Miniaudio::System", + "target": "Audio::PlaybackNotifications", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 58, + "member": "CE::Audio::Miniaudio::System::notification_snapshot", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 58, + "member": "CE::Audio::Miniaudio::System::notification_snapshot", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 58, + "member": "CE::Audio::Miniaudio::System::notification_snapshot", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 608, + "member": "CE::Audio::Miniaudio::System::notification_snapshot", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 608, + "member": "CE::Audio::Miniaudio::System::notification_snapshot", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 610, + "member": "CE::Audio::Miniaudio::System::notification_snapshot", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "Audio::PlaybackEnded", + "target": "Audio::PlaybackTerminal", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/audio/playback-events.h", + "line": 33, + "member": "CE::Audio::PlaybackEnded::terminal", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/audio/playback-events.h", + "line": 33, + "member": "CE::Audio::PlaybackEnded::terminal", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Audio::VoiceSnapshot", + "target": "Audio::PlaybackTerminal", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 35, + "member": "CE::Audio::VoiceSnapshot::terminal", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 35, + "member": "CE::Audio::VoiceSnapshot::terminal", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Audio::Miniaudio::System", + "target": "Events::EventBus::Publisher", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 56, + "member": "CE::Audio::Miniaudio::System::publish_events_to", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 56, + "member": "CE::Audio::Miniaudio::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 56, + "member": "CE::Audio::Miniaudio::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 600, + "member": "CE::Audio::Miniaudio::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 600, + "member": "CE::Audio::Miniaudio::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 603, + "member": "CE::Audio::Miniaudio::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "Audio::Miniaudio::System", + "target": "Runtime::RuntimeServices", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 59, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 59, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 59, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 613, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 613, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 618, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "Audio::Miniaudio::System", + "target": "Runtime::ServiceAttachment", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 59, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 59, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 59, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 613, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 613, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 618, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + } + ], + "notes": [ + "Audio is not a Context adapter. Playback runs on its own output clock, and output lifetime is separately owned.", + "Notification delivery uses explicit maintenance/pumping; attaching a runtime pump does not transfer ownership of output." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/audio.md" + ], + "nodes": [ + "Audio::Miniaudio::System", + "Audio::System", + "Audio::Clip", + "Audio::PlaybackOptions", + "Audio::Voice", + "Audio::VoiceSnapshot", + "Audio::PlaybackNotifications", + "Audio::PlaybackEnded", + "Audio::PlaybackTerminal", + "Events::EventBus::Publisher", + "Runtime::RuntimeServices", + "Runtime::ServiceAttachment" + ], + "namespace": "Audio" + }, + { + "id": "events-internal", + "system": "events", + "view": "internal", + "title": "Events \u00b7 engine integration", + "question": "Which engine producers use the bus, and where are optional delivery services connected?", + "edges": [ + { + "source": "Runtime::Context", + "target": "Events::EventBus", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 61, + "member": "CE::Runtime::Context::events_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 116, + "member": "CE::Runtime::Context::events", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 61, + "member": "CE::Runtime::Context::events_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 116, + "member": "CE::Runtime::Context::events", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 116, + "member": "CE::Runtime::Context::events", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 84, + "member": "CE::Runtime::Context::begin_session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 85, + "member": "CE::Runtime::Context::begin_session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 86, + "member": "CE::Runtime::Context::begin_session", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 9 + }, + { + "source": "Input::System", + "target": "Events::EventBus::Publisher", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 87, + "member": "CE::Input::System::gamepad_publisher_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 57, + "member": "CE::Input::System::publish_events_to", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 57, + "member": "CE::Input::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 57, + "member": "CE::Input::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 87, + "member": "CE::Input::System::gamepad_publisher_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 61, + "member": "CE::Input::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 61, + "member": "CE::Input::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 62, + "member": "CE::Input::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 10 + }, + { + "source": "Platform::GLFW::Window", + "target": "Events::EventSystem", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/display/window.cpp", + "line": 173, + "member": "CE::Platform::GLFW::Window::update_framebuffer_size", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/display/window.cpp", + "line": 174, + "member": "CE::Platform::GLFW::Window::update_framebuffer_size", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/display/window.cpp", + "line": 175, + "member": "CE::Platform::GLFW::Window::update_framebuffer_size", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Runtime::RuntimeServices", + "target": "Events::EventBus", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/runtime-services.h", + "line": 76, + "member": "CE::Runtime::RuntimeServices::RuntimeServices", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/runtime-services.h", + "line": 76, + "member": "CE::Runtime::RuntimeServices::RuntimeServices", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/runtime-services.h", + "line": 76, + "member": "CE::Runtime::RuntimeServices::RuntimeServices", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/src/core/engine/runtime-services.cpp", + "line": 120, + "member": "CE::Runtime::RuntimeServices::RuntimeServices", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/runtime-services.cpp", + "line": 120, + "member": "CE::Runtime::RuntimeServices::RuntimeServices", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/runtime-services.cpp", + "line": 127, + "member": "CE::Runtime::RuntimeServices::on_maintenance", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/runtime-services.cpp", + "line": 135, + "member": "CE::Runtime::RuntimeServices::on_step", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 7 + }, + { + "source": "Events::EventBus", + "target": "Events::EventChannel", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 206, + "member": "CE::Events::EventBus::register_listener", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 227, + "member": "CE::Events::EventBus::dispatch", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 233, + "member": "CE::Events::EventBus::notify", + "access": "public", + "via": "member declaration" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 207, + "member": "CE::Events::EventBus::register_listener", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 208, + "member": "CE::Events::EventBus::register_listener", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 227, + "member": "CE::Events::EventBus::dispatch", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 234, + "member": "CE::Events::EventBus::notify", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 7 + }, + { + "source": "Events::EventBus", + "target": "Events::EventBus::Registration", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 194, + "member": "CE::Events::EventBus::register_listener", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 206, + "member": "CE::Events::EventBus::register_listener", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 246, + "member": "CE::Events::EventBus::unregister_listener", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 248, + "member": "CE::Events::EventBus::wait_for_listener", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 249, + "member": "CE::Events::EventBus::unregister_and_wait", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 260, + "member": "CE::Events::EventBus::register_channel", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 194, + "member": "CE::Events::EventBus::register_listener", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 194, + "member": "CE::Events::EventBus::register_listener", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 34 + }, + { + "source": "Events::EventBus", + "target": "Events::EventBus::Delivery", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 194, + "member": "Events::EventBus::register_listener", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 206, + "member": "Events::EventBus::register_listener", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 260, + "member": "Events::EventBus::register_channel", + "via": "API declaration" + } + ], + "evidence_count": 3 + }, + { + "source": "Events::EventBus", + "target": "Events::EventBus::ErrorHandler", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 194, + "member": "Events::EventBus::register_listener", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 206, + "member": "Events::EventBus::register_listener", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 233, + "member": "Events::EventBus::notify", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 260, + "member": "Events::EventBus::register_channel", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 262, + "member": "Events::EventBus::notify_channel", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 268, + "member": "Events::EventBus::notify_state", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 275, + "member": "Events::EventBus::report_notification_failure", + "via": "API declaration" + } + ], + "evidence_count": 7 + }, + { + "source": "Events::platform_delivery()", + "target": "Runtime::PlatformDispatcher::Submission", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 15, + "member": "Events::platform_delivery()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Events::platform_delivery()", + "target": "Events::EventBus::Delivery", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 15, + "member": "Events::platform_delivery()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Events::simulation_delivery()", + "target": "Runtime::SimulationDispatcher::Submission", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 16, + "member": "Events::simulation_delivery()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Events::simulation_delivery()", + "target": "Events::EventBus::Delivery", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 16, + "member": "Events::simulation_delivery()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Events::worker_delivery()", + "target": "Workers::Group", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 19, + "member": "Events::worker_delivery()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Events::worker_delivery()", + "target": "Events::EventBus::Delivery", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 19, + "member": "Events::worker_delivery()", + "via": "operation signature" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "The bus itself is immediate by default and includes no dispatcher or worker service. Delivery factories are explicit composition adapters.", + "Queued errors are owned by the registration error sink, not by a discarded dispatcher future." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/events.md" + ], + "nodes": [ + "Runtime::Context", + "Events::EventBus", + "Input::System", + "Events::EventBus::Publisher", + "Platform::GLFW::Window", + "Events::EventSystem", + "Runtime::RuntimeServices", + "Events::EventChannel", + "Events::EventBus::Registration", + "Events::EventBus::Delivery", + "Events::EventBus::ErrorHandler", + "Events::platform_delivery()", + "Runtime::PlatformDispatcher::Submission", + "Events::simulation_delivery()", + "Runtime::SimulationDispatcher::Submission", + "Events::worker_delivery()", + "Workers::Group" + ], + "namespace": "Events" + }, + { + "id": "dispatch-internal", + "system": "dispatch", + "view": "internal", + "title": "Dispatch \u00b7 engine integration", + "question": "Which owners drain queues and which engine services retain producer endpoints?", + "edges": [ + { + "source": "Runtime::Context", + "target": "Runtime::PlatformDispatcher", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 73, + "member": "CE::Runtime::Context::platform_dispatcher_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 115, + "member": "CE::Runtime::Context::platform_dispatcher", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 73, + "member": "CE::Runtime::Context::platform_dispatcher_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 115, + "member": "CE::Runtime::Context::platform_dispatcher", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 115, + "member": "CE::Runtime::Context::platform_dispatcher", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 91, + "member": "CE::Runtime::Context::begin_session", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "Runtime::GameRuntime", + "target": "Runtime::PlatformDispatcher", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 464, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 489, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 522, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 579, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 607, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 631, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 737, + "member": "CE::Runtime::GameRuntime::run_concurrent", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 762, + "member": "CE::Runtime::GameRuntime::run_concurrent", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 17 + }, + { + "source": "Runtime::GameRuntime", + "target": "Runtime::SimulationDispatcher", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 84, + "member": "CE::Runtime::GameRuntime::simulation_dispatcher_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 119, + "member": "CE::Runtime::GameRuntime::simulation_dispatcher", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 84, + "member": "CE::Runtime::GameRuntime::simulation_dispatcher_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 119, + "member": "CE::Runtime::GameRuntime::simulation_dispatcher", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 119, + "member": "CE::Runtime::GameRuntime::simulation_dispatcher", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 468, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 472, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 552, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 15 + }, + { + "source": "Runtime::PlatformDispatcher", + "target": "Runtime::Context", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 97, + "member": "CE::Runtime::PlatformDispatcher::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 109, + "member": "CE::Runtime::PlatformDispatcher::drain", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 35, + "member": "CE::Runtime::PlatformDispatcher::State::pending", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 97, + "member": "CE::Runtime::PlatformDispatcher::submit", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 97, + "member": "CE::Runtime::PlatformDispatcher::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 106, + "member": "CE::Runtime::PlatformDispatcher::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 109, + "member": "CE::Runtime::PlatformDispatcher::drain", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 109, + "member": "CE::Runtime::PlatformDispatcher::drain", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 20 + }, + { + "source": "Runtime::PlatformDispatcher", + "target": "Runtime::PlatformDispatcher::Submission", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 95, + "member": "CE::Runtime::PlatformDispatcher::submission", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 95, + "member": "CE::Runtime::PlatformDispatcher::submission", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 95, + "member": "CE::Runtime::PlatformDispatcher::submission", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 98, + "member": "CE::Runtime::PlatformDispatcher::submit", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 4 + }, + { + "source": "Runtime::SimulationDispatcher", + "target": "Runtime::SimulationDispatcher::Submission", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 95, + "member": "CE::Runtime::SimulationDispatcher::submission", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 95, + "member": "CE::Runtime::SimulationDispatcher::submission", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 95, + "member": "CE::Runtime::SimulationDispatcher::submission", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 97, + "member": "CE::Runtime::SimulationDispatcher::submit", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 4 + }, + { + "source": "Runtime::PlatformDispatcher", + "target": "Diagnostics::DispatchStats", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 103, + "member": "CE::Runtime::PlatformDispatcher::diagnostics", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 41, + "member": "CE::Runtime::PlatformDispatcher::State::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 103, + "member": "CE::Runtime::PlatformDispatcher::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 103, + "member": "CE::Runtime::PlatformDispatcher::diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/platform-dispatcher.cpp", + "line": 22, + "member": "CE::Runtime::PlatformDispatcher::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/platform-dispatcher.cpp", + "line": 23, + "member": "CE::Runtime::PlatformDispatcher::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/engine/platform-dispatcher.cpp", + "line": 36, + "member": "CE::Runtime::PlatformDispatcher::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/platform-dispatcher.cpp", + "line": 36, + "member": "CE::Runtime::PlatformDispatcher::diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 21 + }, + { + "source": "Runtime::SimulationDispatcher", + "target": "Diagnostics::DispatchStats", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 102, + "member": "CE::Runtime::SimulationDispatcher::diagnostics", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 41, + "member": "CE::Runtime::SimulationDispatcher::State::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 102, + "member": "CE::Runtime::SimulationDispatcher::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 102, + "member": "CE::Runtime::SimulationDispatcher::diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/simulation-dispatcher.cpp", + "line": 22, + "member": "CE::Runtime::SimulationDispatcher::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/simulation-dispatcher.cpp", + "line": 23, + "member": "CE::Runtime::SimulationDispatcher::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/engine/simulation-dispatcher.cpp", + "line": 36, + "member": "CE::Runtime::SimulationDispatcher::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/simulation-dispatcher.cpp", + "line": 36, + "member": "CE::Runtime::SimulationDispatcher::diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 22 + }, + { + "source": "Text::Rendering::Updater", + "target": "Runtime::PlatformDispatcher::Submission", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 37, + "member": "CE::Text::Rendering::Updater::State::platform", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 53, + "member": "CE::Text::Rendering::Updater::State", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 133, + "member": "CE::Text::Rendering::Updater::update", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "UI::TGUI::SceneUploader", + "target": "Runtime::PlatformDispatcher::Submission", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 65, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 65, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 65, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/src/scene.cpp", + "line": 169, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/scene.cpp", + "line": 169, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 12 + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "Runtime::PlatformDispatcher::Submission", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 60, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 60, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 60, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 152, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 152, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 12 + }, + { + "source": "Events::platform_delivery()", + "target": "Runtime::PlatformDispatcher::Submission", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 15, + "member": "Events::platform_delivery()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Events::simulation_delivery()", + "target": "Runtime::SimulationDispatcher::Submission", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 16, + "member": "Events::simulation_delivery()", + "via": "operation signature" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "Runtime opens/drains/closes each queue on its selected owner. Submission endpoints retain queue state, not a borrowed facade.", + "Dispatch is owner-affine; it is not the general CPU pool. A detached batch can still be running when the queue is empty." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/dispatch.md" + ], + "nodes": [ + "Runtime::Context", + "Runtime::PlatformDispatcher", + "Runtime::GameRuntime", + "Runtime::SimulationDispatcher", + "Runtime::PlatformDispatcher::Submission", + "Runtime::SimulationDispatcher::Submission", + "Diagnostics::DispatchStats", + "Text::Rendering::Updater", + "UI::TGUI::SceneUploader", + "UI::RmlUi::SceneUploader", + "Events::platform_delivery()", + "Events::simulation_delivery()" + ], + "namespace": "Runtime" + }, + { + "id": "workers-internal", + "system": "workers", + "view": "internal", + "title": "Workers \u00b7 engine integration", + "question": "How does shared physical capacity serve Context groups and internal consumers?", + "edges": [ + { + "source": "Runtime::Context", + "target": "Runtime::ExecutionOptions", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 74, + "member": "CE::Runtime::Context::execution_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 93, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 74, + "member": "CE::Runtime::Context::execution_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 91, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 91, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 99, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 99, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 18 + }, + { + "source": "Runtime::ExecutionOptions", + "target": "Workers::Pool", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 49, + "member": "CE::Runtime::ExecutionOptions::shared_pool", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 49, + "member": "CE::Runtime::ExecutionOptions::shared_pool", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Runtime::Context", + "target": "Workers::Pool", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 76, + "member": "CE::Runtime::Context::owned_worker_factory_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 77, + "member": "CE::Runtime::Context::owned_workers_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 76, + "member": "CE::Runtime::Context::owned_worker_factory_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 77, + "member": "CE::Runtime::Context::owned_workers_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 116, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 122, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 125, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 126, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 16 + }, + { + "source": "Runtime::Context", + "target": "Workers::Group", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 78, + "member": "CE::Runtime::Context::worker_groups_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 78, + "member": "CE::Runtime::Context::worker_groups_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 115, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 115, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 118, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 23 + }, + { + "source": "Workers::Pool", + "target": "Workers::PoolOptions", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 188, + "member": "CE::Workers::Pool::Pool", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 193, + "member": "CE::Workers::Pool::Pool", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 188, + "member": "CE::Workers::Pool::Pool", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 188, + "member": "CE::Workers::Pool::Pool", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 193, + "member": "CE::Workers::Pool::Pool", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 193, + "member": "CE::Workers::Pool::Pool", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 278, + "member": "CE::Workers::Pool::Pool", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 280, + "member": "CE::Workers::Pool::Pool", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 14 + }, + { + "source": "Workers::Pool", + "target": "Workers::Group", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 200, + "member": "CE::Workers::Pool::make_group", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 200, + "member": "CE::Workers::Pool::make_group", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 200, + "member": "CE::Workers::Pool::make_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 363, + "member": "CE::Workers::Pool::make_group", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 363, + "member": "CE::Workers::Pool::make_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 380, + "member": "CE::Workers::Pool::make_group", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "Workers::Pool", + "target": "Workers::TopologyProvider", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 286, + "member": "CE::Workers::Pool::Pool", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 288, + "member": "CE::Workers::Pool::Pool", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 289, + "member": "CE::Workers::Pool::Pool", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 291, + "member": "CE::Workers::Pool::Pool", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 292, + "member": "CE::Workers::Pool::Pool", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 365, + "member": "CE::Workers::Pool::make_group", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "Workers::TopologyProvider", + "target": "Workers::Topology", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/worker-topology.h", + "line": 52, + "member": "CE::Workers::TopologyProvider::topology", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/worker-topology.h", + "line": 52, + "member": "CE::Workers::TopologyProvider::topology", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/worker-topology.h", + "line": 52, + "member": "CE::Workers::TopologyProvider::topology", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Workers::Group", + "target": "Workers::GroupOptions", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 260, + "member": "CE::Workers::Group::report_diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 1 + }, + { + "source": "Workers::Group", + "target": "Workers::GroupStatus", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 168, + "member": "CE::Workers::Group::status", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 168, + "member": "CE::Workers::Group::status", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 168, + "member": "CE::Workers::Group::status", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 190, + "member": "CE::Workers::Group::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 194, + "member": "CE::Workers::Group::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 195, + "member": "CE::Workers::Group::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 196, + "member": "CE::Workers::Group::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 207, + "member": "CE::Workers::Group::close", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 23 + }, + { + "source": "Workers::GroupOptions", + "target": "Workers::CpuPolicy", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 83, + "member": "CE::Workers::GroupOptions::cpu", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 83, + "member": "CE::Workers::GroupOptions::cpu", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Workers::CpuPolicy", + "target": "Workers::PolicyStrength", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 50, + "member": "CE::Workers::CpuPolicy::strength", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 50, + "member": "CE::Workers::CpuPolicy::strength", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Text::Rendering::Updater", + "target": "Workers::Group", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 36, + "member": "CE::Text::Rendering::Updater::State::workers", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 52, + "member": "CE::Text::Rendering::Updater::State", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 146, + "member": "CE::Text::Rendering::Updater::update", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Events::worker_delivery()", + "target": "Workers::Group", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 19, + "member": "Events::worker_delivery()", + "via": "operation signature" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "Groups share physical worker capacity. Runtime simulation has its own owner; it is not a pool job.", + "General jobs can request platform work, but Pool itself has no graphics or dispatcher dependency." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/workers.md" + ], + "nodes": [ + "Runtime::Context", + "Runtime::ExecutionOptions", + "Workers::Pool", + "Workers::Group", + "Workers::PoolOptions", + "Workers::TopologyProvider", + "Workers::Topology", + "Workers::GroupOptions", + "Workers::GroupStatus", + "Workers::CpuPolicy", + "Workers::PolicyStrength", + "Text::Rendering::Updater", + "Events::worker_delivery()" + ], + "namespace": "Workers" + }, + { + "id": "memory-internal", + "system": "memory", + "view": "internal", + "title": "Memory \u00b7 engine integration", + "question": "Which storage, construction and release contracts support engine caches?", + "edges": [ + { + "source": "Assets::Cache::Store", + "target": "Memory::Objects::Reservation", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 313, + "member": "CE::Assets::Cache::Store::reserve", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 1 + }, + { + "source": "Memory::Objects::Reservation", + "target": "Memory::Allocators::ObjectPoolAllocator", + "optional": false, + "evidence": [ + { + "kind": "defaults", + "file": "projects/engine/include/cheryl/core/resources/objects/object-reservation.hpp", + "line": 52, + "via": "template default" + } + ], + "evidence_count": 1 + }, + { + "source": "Memory::Allocators::ObjectPoolAllocator", + "target": "Memory::Objects::Pool", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/resources/memory/allocators/object-pool-allocator.hpp", + "line": 22, + "member": "Memory::Allocators::ObjectPoolAllocator::manager_type", + "via": "API declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Assets::Grids::make_geometry()", + "target": "Memory::ExactManager", + "optional": false, + "evidence": [ + { + "file": "projects/engine/src/assets/geometry/grid-geometry.cpp", + "line": 32, + "via": "CPU grid builder checks out managed byte backing" + } + ], + "evidence_count": 1 + }, + { + "source": "Memory::ExactManager", + "target": "Memory::Manager", + "optional": false, + "evidence": [ + { + "kind": "aliases", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 90, + "via": "alias declaration" + }, + { + "kind": "aliases", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 90, + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Memory::Objects::Pool", + "target": "Memory::Objects::PoolState", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/resources/objects/pool.h", + "line": 45, + "member": "Memory::Objects::Pool::release_context_type", + "via": "API declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Memory::Objects::PoolState", + "target": "Memory::Objects::Constructor::Context", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/resources/objects/pool.h", + "line": 19, + "via": "retained public nested construction context" + } + ], + "evidence_count": 1 + }, + { + "source": "Memory::Objects::Reservation", + "target": "Memory::Block", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/resources/objects/object-reservation.hpp", + "line": 72, + "member": "CE::Memory::Objects::Reservation::remaining_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/objects/object-reservation.hpp", + "line": 99, + "member": "CE::Memory::Objects::Reservation::remaining_ranges", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/resources/objects/object-reservation.hpp", + "line": 72, + "member": "CE::Memory::Objects::Reservation::remaining_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/objects/object-reservation.hpp", + "line": 80, + "member": "CE::Memory::Objects::Reservation::Reservation", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/objects/object-reservation.hpp", + "line": 83, + "member": "CE::Memory::Objects::Reservation::Reservation", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/objects/object-reservation.hpp", + "line": 90, + "member": "CE::Memory::Objects::Reservation::Reservation", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/objects/object-reservation.hpp", + "line": 94, + "member": "CE::Memory::Objects::Reservation::~Reservation", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/objects/object-reservation.hpp", + "line": 99, + "member": "CE::Memory::Objects::Reservation::remaining_ranges", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 19 + }, + { + "source": "Memory::Manager", + "target": "Memory::BlockTransactions", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 96, + "member": "CE::Memory::Manager::return_ptr", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 108, + "member": "CE::Memory::Manager::return_portion", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 143, + "member": "CE::Memory::Manager::checkout_chunk", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 174, + "member": "CE::Memory::Manager::preallocate", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 4 + }, + { + "source": "Memory::Manager", + "target": "Memory::ByteReleaseContext", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 34, + "member": "CE::Memory::Manager::release_context_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 34, + "member": "CE::Memory::Manager::release_context_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 45, + "member": "CE::Memory::Manager::release_context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 119, + "member": "CE::Memory::Manager::return_chunk", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 4 + }, + { + "source": "Memory::BlockTransactions", + "target": "Memory::Block", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/templates/block-transactions.h", + "line": 16, + "member": "Memory::BlockTransactions::BlockType", + "via": "API declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Memory::Manager", + "target": "State::Singleton", + "optional": false, + "evidence": [ + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 27, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 28, + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Memory::Manager", + "target": "Memory::Stats", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 49, + "member": "CE::Memory::Manager::diagnostics", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 49, + "member": "CE::Memory::Manager::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 49, + "member": "CE::Memory::Manager::diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 19, + "member": "CE::Memory::Manager::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 19, + "member": "CE::Memory::Manager::diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 20, + "member": "CE::Memory::Manager::diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 27, + "member": "CE::Memory::Manager::diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 29, + "member": "CE::Memory::Manager::diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 12 + } + ], + "notes": [ + "Captured release contexts can outlive a facade. Storage bookkeeping and live object state remain distinct.", + "These are CPU contracts. They do not replace graphics-domain retirement or synchronize arbitrary object members." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/memory.md" + ], + "nodes": [ + "Assets::Cache::Store", + "Memory::Objects::Reservation", + "Memory::Allocators::ObjectPoolAllocator", + "Memory::Objects::Pool", + "Assets::Grids::make_geometry()", + "Memory::ExactManager", + "Memory::Manager", + "Memory::Objects::PoolState", + "Memory::Objects::Constructor::Context", + "Memory::Block", + "Memory::BlockTransactions", + "Memory::ByteReleaseContext", + "State::Singleton", + "Memory::Stats" + ], + "namespace": "Memory" + }, + { + "id": "state-internal", + "system": "state", + "view": "internal", + "title": "State \u00b7 engine integration", + "question": "Which helpers are used by engine services, and which remain standalone alternatives?", + "edges": [ + { + "source": "Assets::Loading::Loader", + "target": "State::Singleton", + "optional": false, + "evidence": [ + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 88, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 88, + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 136, + "member": "CE::Assets::Loading::Loader::get", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 143, + "member": "CE::Assets::Loading::Loader::get", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 306, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 308, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 310, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 313, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 15 + }, + { + "source": "Events::EventSystem", + "target": "State::Singleton", + "optional": false, + "evidence": [ + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/subsystems/event-system.h", + "line": 13, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/subsystems/event-system.h", + "line": 13, + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Memory::Objects::Pool", + "target": "State::Singleton", + "optional": false, + "evidence": [ + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/objects/pool.h", + "line": 42, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/objects/pool.h", + "line": 43, + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "State::ObservedVariable", + "target": "State::VersionedVariable", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/templates/observed-variables.h", + "line": 28, + "member": "CE::State::ObservedVariable::value_", + "access": "private", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "State::VersionedVariable", + "target": "State::VersionedSnapshot", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/templates/versioned-variable.h", + "line": 51, + "member": "CE::State::VersionedVariable::snapshot", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/templates/versioned-variable.h", + "line": 66, + "member": "CE::State::VersionedVariable::wait_for_change", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 2 + } + ], + "notes": [ + "StateTracker and StateMachine are independent templates; isolated boxes here do not imply an engine-owned world/state model.", + "Singleton helpers describe existing service construction. They are not a recommendation to make application state global." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/state.md" + ], + "nodes": [ + "Assets::Loading::Loader", + "State::Singleton", + "Events::EventSystem", + "Memory::Objects::Pool", + "State::ObservedVariable", + "State::VersionedVariable", + "State::VersionedSnapshot", + "State::StateTracker", + "State::StateMachine" + ], + "namespace": "State" + }, + { + "id": "logging-internal", + "system": "logging", + "view": "internal", + "title": "Logging \u00b7 engine integration", + "question": "How do subsystem writers reach named logging configuration and lifetime?", + "edges": [ + { + "source": "Runtime::GameRuntime", + "target": "Logging::Logger", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 112, + "member": "CE::Runtime::GameRuntime::run", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 182, + "member": "CE::Runtime::GameRuntime::process_shutdown", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 198, + "member": "CE::Runtime::GameRuntime::report_session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 202, + "member": "CE::Runtime::GameRuntime::report_session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 208, + "member": "CE::Runtime::GameRuntime::report_session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 216, + "member": "CE::Runtime::GameRuntime::report_session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 244, + "member": "CE::Runtime::GameRuntime::observe_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 266, + "member": "CE::Runtime::GameRuntime::observe_timing", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 20 + }, + { + "source": "Assets::Loading::Loader", + "target": "Logging::Logger", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 166, + "member": "CE::Assets::Loading::Loader::prepare_selected", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 169, + "member": "CE::Assets::Loading::Loader::prepare_selected", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 210, + "member": "CE::Assets::Loading::Loader::prepare_selected", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 284, + "member": "CE::Assets::Loading::Loader::publish_pending_events", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 343, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 354, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 362, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 7 + }, + { + "source": "Workers::Pool", + "target": "Logging::Logger", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 355, + "member": "CE::Workers::Pool::report_diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 1 + }, + { + "source": "Memory::Manager", + "target": "Logging::Logger", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 41, + "member": "CE::Memory::Manager::report_diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 177, + "member": "CE::Memory::Manager::preallocate", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 184, + "member": "CE::Memory::Manager::preallocate", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Logging::Logger", + "target": "Logging::Log", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 19, + "member": "CE::Logging::Logger::published_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 35, + "member": "CE::Logging::Logger::get", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 43, + "member": "CE::Logging::Logger::get", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 51, + "member": "CE::Logging::Logger::initialize", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 19, + "member": "CE::Logging::Logger::published_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 21, + "member": "CE::Logging::Logger::Facade::value", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 35, + "member": "CE::Logging::Logger::get", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 35, + "member": "CE::Logging::Logger::get", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 12 + }, + { + "source": "Logging::Log", + "target": "Logging::Config", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 119, + "member": "CE::Logging::Log::Log", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 119, + "member": "CE::Logging::Log::Log", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 119, + "member": "CE::Logging::Log::Log", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Logging::LogHandle", + "target": "Logging::QueueStats", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 67, + "member": "CE::Logging::LogHandle::shared_queue_stats", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 67, + "member": "CE::Logging::LogHandle::shared_queue_stats", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 67, + "member": "CE::Logging::LogHandle::shared_queue_stats", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/logging/log.cpp", + "line": 157, + "member": "CE::Logging::LogHandle::shared_queue_stats", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/logging/log.cpp", + "line": 157, + "member": "CE::Logging::LogHandle::shared_queue_stats", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/logging/log.cpp", + "line": 158, + "member": "CE::Logging::LogHandle::shared_queue_stats", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "Logging::Log", + "target": "Logging::FileEventHandlers", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 119, + "member": "CE::Logging::Log::Log", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 119, + "member": "CE::Logging::Log::Log", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 119, + "member": "CE::Logging::Log::Log", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Logging::Config", + "target": "Logging::Level", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 43, + "member": "CE::Logging::Config::logger_level", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 44, + "member": "CE::Logging::Config::file_level", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 45, + "member": "CE::Logging::Config::console_level", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 43, + "member": "CE::Logging::Config::logger_level", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 44, + "member": "CE::Logging::Config::file_level", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 45, + "member": "CE::Logging::Config::console_level", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 55, + "member": "CE::Logging::Config::for_logger", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 56, + "member": "CE::Logging::Config::for_logger", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 14 + }, + { + "source": "Logging::Config", + "target": "Logging::OverflowPolicy", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 46, + "member": "CE::Logging::Config::overflow_policy", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 46, + "member": "CE::Logging::Config::overflow_policy", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Logging::Log", + "target": "Logging::LogHandle", + "optional": false, + "evidence": [ + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 113, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 113, + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 116, + "member": "CE::Logging::Log::Log", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 120, + "member": "CE::Logging::Log::Log", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 4 + } + ], + "notes": [ + "Named categories share private backend state. Any asynchronous logging queue is separate from Workers::Pool.", + "Ordinary logging is separate from required exception/future propagation and bounded emergency reporting." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/logging.md" + ], + "nodes": [ + "Runtime::GameRuntime", + "Logging::Logger", + "Assets::Loading::Loader", + "Workers::Pool", + "Memory::Manager", + "Logging::Log", + "Logging::Config", + "Logging::LogHandle", + "Logging::QueueStats", + "Logging::FileEventHandlers", + "Logging::Level", + "Logging::OverflowPolicy" + ], + "namespace": "Logging" + }, + { + "id": "diagnostics-internal", + "system": "diagnostics", + "view": "internal", + "title": "Diagnostics \u00b7 owner-specific observations", + "question": "Which engine owner produces each copied observation?", + "edges": [ + { + "source": "Runtime::GameRuntime", + "target": "Runtime::RuntimeStats", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 66, + "member": "CE::Runtime::GameRuntime::diagnostics_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 111, + "member": "CE::Runtime::GameRuntime::diagnostics", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 66, + "member": "CE::Runtime::GameRuntime::diagnostics_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 111, + "member": "CE::Runtime::GameRuntime::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 111, + "member": "CE::Runtime::GameRuntime::diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 112, + "member": "CE::Runtime::GameRuntime::run", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 143, + "member": "CE::Runtime::GameRuntime::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 143, + "member": "CE::Runtime::GameRuntime::diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 49 + }, + { + "source": "Workers::Group", + "target": "Workers::GroupStatus", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 168, + "member": "CE::Workers::Group::status", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 168, + "member": "CE::Workers::Group::status", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 168, + "member": "CE::Workers::Group::status", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 190, + "member": "CE::Workers::Group::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 194, + "member": "CE::Workers::Group::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 195, + "member": "CE::Workers::Group::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 196, + "member": "CE::Workers::Group::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 207, + "member": "CE::Workers::Group::close", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 23 + }, + { + "source": "Runtime::PlatformDispatcher", + "target": "Diagnostics::DispatchStats", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 103, + "member": "CE::Runtime::PlatformDispatcher::diagnostics", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 41, + "member": "CE::Runtime::PlatformDispatcher::State::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 103, + "member": "CE::Runtime::PlatformDispatcher::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 103, + "member": "CE::Runtime::PlatformDispatcher::diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/platform-dispatcher.cpp", + "line": 22, + "member": "CE::Runtime::PlatformDispatcher::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/platform-dispatcher.cpp", + "line": 23, + "member": "CE::Runtime::PlatformDispatcher::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/engine/platform-dispatcher.cpp", + "line": 36, + "member": "CE::Runtime::PlatformDispatcher::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/platform-dispatcher.cpp", + "line": 36, + "member": "CE::Runtime::PlatformDispatcher::diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 21 + }, + { + "source": "Runtime::SimulationDispatcher", + "target": "Diagnostics::DispatchStats", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 102, + "member": "CE::Runtime::SimulationDispatcher::diagnostics", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 41, + "member": "CE::Runtime::SimulationDispatcher::State::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 102, + "member": "CE::Runtime::SimulationDispatcher::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 102, + "member": "CE::Runtime::SimulationDispatcher::diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/simulation-dispatcher.cpp", + "line": 22, + "member": "CE::Runtime::SimulationDispatcher::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/simulation-dispatcher.cpp", + "line": 23, + "member": "CE::Runtime::SimulationDispatcher::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/engine/simulation-dispatcher.cpp", + "line": 36, + "member": "CE::Runtime::SimulationDispatcher::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/simulation-dispatcher.cpp", + "line": 36, + "member": "CE::Runtime::SimulationDispatcher::diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 22 + }, + { + "source": "Events::EventBus", + "target": "Events::EventStats", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 253, + "member": "CE::Events::EventBus::diagnostics", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 253, + "member": "CE::Events::EventBus::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 253, + "member": "CE::Events::EventBus::diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/subsystems/event-bus.cpp", + "line": 319, + "member": "CE::Events::EventBus::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/subsystems/event-bus.cpp", + "line": 319, + "member": "CE::Events::EventBus::diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/subsystems/event-bus.cpp", + "line": 329, + "member": "CE::Events::EventBus::report_diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/subsystems/event-bus.cpp", + "line": 330, + "member": "CE::Events::EventBus::report_diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 7 + }, + { + "source": "Assets::Loading::Loader", + "target": "Assets::Loading::UploadStats", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 99, + "member": "CE::Assets::Loading::Loader::last_upload_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 107, + "member": "CE::Assets::Loading::Loader::settle_upload", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 130, + "member": "CE::Assets::Loading::Loader::diagnostics", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 99, + "member": "CE::Assets::Loading::Loader::last_upload_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 107, + "member": "CE::Assets::Loading::Loader::settle_upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 107, + "member": "CE::Assets::Loading::Loader::settle_upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 130, + "member": "CE::Assets::Loading::Loader::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 130, + "member": "CE::Assets::Loading::Loader::diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 30 + }, + { + "source": "Memory::Manager", + "target": "Memory::Stats", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 49, + "member": "CE::Memory::Manager::diagnostics", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 49, + "member": "CE::Memory::Manager::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 49, + "member": "CE::Memory::Manager::diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 19, + "member": "CE::Memory::Manager::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 19, + "member": "CE::Memory::Manager::diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 20, + "member": "CE::Memory::Manager::diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 27, + "member": "CE::Memory::Manager::diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 29, + "member": "CE::Memory::Manager::diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 12 + }, + { + "source": "Rendering::OpenGL::ResourceLifetime", + "target": "Rendering::OpenGL::ResourceStats", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/resource-lifetime.h", + "line": 70, + "member": "CE::Rendering::OpenGL::ResourceLifetime::diagnostics_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/resource-lifetime.h", + "line": 98, + "member": "CE::Rendering::OpenGL::ResourceLifetime::diagnostics", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/resource-lifetime.h", + "line": 70, + "member": "CE::Rendering::OpenGL::ResourceLifetime::diagnostics_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/resource-lifetime.h", + "line": 98, + "member": "CE::Rendering::OpenGL::ResourceLifetime::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/resource-lifetime.h", + "line": 98, + "member": "CE::Rendering::OpenGL::ResourceLifetime::diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/modules/graphics/opengl/src/backends/opengl/resource-lifetime.cpp", + "line": 85, + "member": "CE::Rendering::OpenGL::ResourceLifetime::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/src/backends/opengl/resource-lifetime.cpp", + "line": 85, + "member": "CE::Rendering::OpenGL::ResourceLifetime::diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/graphics/opengl/src/backends/opengl/resource-lifetime.cpp", + "line": 87, + "member": "CE::Rendering::OpenGL::ResourceLifetime::diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 27 + } + ], + "notes": [ + "There is no central diagnostics manager in this map: each snapshot follows its own owner and access rules.", + "Runtime statistics require synchronization with run completion. Counts and empty queues are not completion barriers." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/diagnostics.md" + ], + "nodes": [ + "Runtime::GameRuntime", + "Runtime::RuntimeStats", + "Workers::Group", + "Workers::GroupStatus", + "Runtime::PlatformDispatcher", + "Diagnostics::DispatchStats", + "Runtime::SimulationDispatcher", + "Events::EventBus", + "Events::EventStats", + "Assets::Loading::Loader", + "Assets::Loading::UploadStats", + "Memory::Manager", + "Memory::Stats", + "Rendering::OpenGL::ResourceLifetime", + "Rendering::OpenGL::ResourceStats" + ] + }, + { + "id": "utilities-internal", + "system": "utilities", + "view": "internal", + "title": "Utilities \u00b7 engine consumers", + "question": "Where do local discovery, fit, numeric and placement helpers enter engine work?", + "edges": [ + { + "source": "Assets::Loading::Loader", + "target": "Files::FileIndex", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 172, + "member": "CE::Assets::Loading::Loader::prepare_selected", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 193, + "member": "CE::Assets::Loading::Loader::prepare_selected", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 416, + "member": "CE::Assets::Loading::Loader::register_files", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Memory::Manager", + "target": "Memory::adjust_length()", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 137, + "via": "implementation calls the fit helper" + } + ], + "evidence_count": 1 + }, + { + "source": "Memory::adjust_length()", + "target": "Memory::FitPolicy", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/math/fit.h", + "line": 19, + "member": "Memory::adjust_length()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Assets::Grids::make_geometry()", + "target": "Rendering::Geometry::Pivot", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/geometry/grid-geometry.h", + "line": 25, + "member": "Assets::Grids::make_geometry()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Assets::Sprites::Definition", + "target": "Rendering::Geometry::Pivot", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/definitions/sprite.h", + "line": 24, + "member": "CE::Assets::Sprites::Definition::pivot", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/definitions/sprite.h", + "line": 24, + "member": "CE::Assets::Sprites::Definition::pivot", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Text::Numbers::string_to_number()", + "target": "Text::Numbers::parse_floats()", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/math/string-numbers.h", + "line": 104, + "via": "inline implementation call" + } + ], + "evidence_count": 1 + }, + { + "source": "Text::Numbers::string_to_number()", + "target": "Text::Numbers::parse_integers()", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/math/string-numbers.h", + "line": 107, + "via": "inline implementation call" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "These are local values/functions, not one initialized engine service. No std or primitive dependency boxes are needed.", + "Time::DeltaTime measures local elapsed time; runtime simulation keeps its own selected tick-time policy." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/utilities.md" + ], + "nodes": [ + "Assets::Loading::Loader", + "Files::FileIndex", + "Memory::Manager", + "Memory::adjust_length()", + "Memory::FitPolicy", + "Assets::Grids::make_geometry()", + "Rendering::Geometry::Pivot", + "Assets::Sprites::Definition", + "Text::Numbers::string_to_number()", + "Text::Numbers::parse_floats()", + "Text::Numbers::parse_integers()", + "Time::DeltaTime", + "Math::BitArray" + ] + }, + { + "id": "runtime-application", + "system": "runtime", + "view": "application", + "title": "Runtime \u00b7 application composition", + "question": "What does an application create, implement and retain for one session?", + "edges": [ + { + "source": "@host", + "target": "Application::GLFWOpenGL::make_context()", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/runtime.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Application::GLFWOpenGL::make_context()", + "target": "Runtime::Context", + "optional": false, + "evidence": [ + { + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-backend.h", + "line": 33, + "member": "Application::GLFWOpenGL::make_context()", + "via": "operation signature" + }, + { + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-backend.h", + "line": 35, + "member": "Application::GLFWOpenGL::make_context()", + "via": "operation signature" + }, + { + "kind": "returns", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-backend.h", + "line": 33, + "member": "CE::Application::GLFWOpenGL::make_context", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 3 + }, + { + "source": "@host", + "target": "Runtime::GameRuntime", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/runtime.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@world", + "target": "Game::AbstractGame", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/runtime.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Runtime::GameRuntime", + "target": "Game::AbstractGame", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 62, + "member": "CE::Runtime::GameRuntime::game_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 94, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 62, + "member": "CE::Runtime::GameRuntime::game_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 96, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 96, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 71, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 71, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 486, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 21 + }, + { + "source": "Runtime::GameRuntime", + "target": "Runtime::Context", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 61, + "member": "CE::Runtime::GameRuntime::engine_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 94, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 61, + "member": "CE::Runtime::GameRuntime::engine_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 95, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 95, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 70, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 70, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 107, + "member": "CE::Runtime::GameRuntime::run", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 79 + }, + { + "source": "@update", + "target": "Game::TickContext", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/runtime.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@prepare", + "target": "Rendering::FrameWriter", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/runtime.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@host", + "target": "Runtime::ShutdownPolicy", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/runtime.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@producer", + "target": "Runtime::ShutdownEndpoint", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/runtime.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Runtime::GameRuntime", + "target": "Runtime::ShutdownEndpoint", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 113, + "member": "CE::Runtime::GameRuntime::shutdown", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 113, + "member": "CE::Runtime::GameRuntime::shutdown", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 113, + "member": "CE::Runtime::GameRuntime::shutdown", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 149, + "member": "CE::Runtime::GameRuntime::shutdown", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 149, + "member": "CE::Runtime::GameRuntime::shutdown", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 150, + "member": "CE::Runtime::GameRuntime::shutdown", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "Runtime::GameRuntime", + "target": "Runtime::RuntimeLifecycle", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 88, + "member": "CE::Runtime::GameRuntime::lifecycle_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 112, + "member": "CE::Runtime::GameRuntime::lifecycle", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 88, + "member": "CE::Runtime::GameRuntime::lifecycle_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 112, + "member": "CE::Runtime::GameRuntime::lifecycle", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 112, + "member": "CE::Runtime::GameRuntime::lifecycle", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 93, + "member": "CE::Runtime::GameRuntime::~GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 110, + "member": "CE::Runtime::GameRuntime::run", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 132, + "member": "CE::Runtime::GameRuntime::run", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 16 + }, + { + "source": "@report", + "target": "Runtime::RuntimeLifecycle", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/runtime.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Runtime::GameRuntime", + "target": "Runtime::ShutdownPolicy", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 117, + "member": "CE::Runtime::GameRuntime::set_shutdown_policy", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Game::AbstractGame", + "target": "Game::TickContext", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 33, + "member": "CE::Game::AbstractGame::update", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Game::AbstractGame", + "target": "Rendering::FrameWriter", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 34, + "member": "CE::Game::AbstractGame::prepare_render_frame", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "The composition role owns Context, the game and GameRuntime. The game supplies init/update/frame/quiesce/deinit hooks.", + "Use the supplied compatible factory for the normal native/OpenGL assembly; manual adapters are shown in the focused composition view." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/runtime.md" + ], + "nodes": [ + "@host", + "Application::GLFWOpenGL::make_context()", + "Runtime::Context", + "Runtime::GameRuntime", + "@world", + "Game::AbstractGame", + "@update", + "Game::TickContext", + "@prepare", + "Rendering::FrameWriter", + "Runtime::ShutdownPolicy", + "@producer", + "Runtime::ShutdownEndpoint", + "Runtime::RuntimeLifecycle", + "@report" + ], + "operations": { + "Runtime::GameRuntime": [ + { + "name": "CE::Runtime::GameRuntime::run", + "label": "run()", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 106, + "brief": "Run one blocking session on the platform owner; retain Context and game through return or throw.", + "role": "call" + }, + { + "name": "CE::Runtime::GameRuntime::shutdown", + "label": "shutdown(): Runtime::ShutdownEndpoint", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 113, + "brief": "Retain a copyable ordinary shutdown-request endpoint; use stop() or tick.request_stop() for unconditional shutdown.", + "role": "call" + }, + { + "name": "CE::Runtime::GameRuntime::lifecycle", + "label": "lifecycle(): Runtime::RuntimeLifecycle", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 112, + "brief": "Retain synchronized live and terminal observations without borrowing GameRuntime.", + "role": "call" + }, + { + "name": "CE::Runtime::GameRuntime::set_shutdown_policy", + "label": "set_shutdown_policy(Runtime::ShutdownPolicyCallback callback, Runtime::ShutdownPolicyOptions options, Events::EventBus::Delivery delivery): Runtime::ShutdownPolicy", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 117, + "brief": "Optionally install host shutdown negotiation before run(); retain the returned policy token while its callbacks are wanted.", + "role": "call" + } + ], + "Game::AbstractGame": [ + { + "name": "CE::Game::AbstractGame::init_platform", + "label": "init_platform()", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 28, + "brief": "Override to bind input, upload initial resources and acquire producer endpoints on platform. init_simulation() and cleanup hooks remain available; Runtime invokes all hooks.", + "role": "override" + }, + { + "name": "CE::Game::AbstractGame::init_simulation", + "label": "init_simulation()", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 29, + "brief": "Override for simulation-owned setup after the initial view is available; create Context-aware GUI sessions here when they need that view.", + "role": "override" + }, + { + "name": "CE::Game::AbstractGame::update", + "label": "update(const Game::TickContext& tick)", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 33, + "brief": "Override to advance simulation using the borrowed TickContext; no native/GPU mutation from the concurrent simulation owner.", + "role": "override" + }, + { + "name": "CE::Game::AbstractGame::prepare_render_frame", + "label": "prepare_render_frame(Rendering::FrameWriter& frame)", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 34, + "brief": "Override to copy render state into the lent frame writer; preparation may be skipped when no slot is free.", + "role": "override" + } + ], + "Game::TickContext": [ + { + "name": "CE::Game::TickContext::request_stop", + "label": "request_stop()", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 30, + "brief": "Request unconditional shutdown; finish or explicitly return from the current update before normal teardown.", + "role": "call" + } + ], + "Rendering::FrameWriter": [ + { + "name": "CE::Rendering::FrameWriter::begin_pass", + "label": "begin_pass(const glm::mat4& projection, const glm::mat4& view, Rendering::Pipelines::PassConstraints2D constraints, Rendering::Parameters::Set parameters): Rendering::PassWriter [borrowed]", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 132, + "brief": "Copy camera matrices and pass options into a new retained render pass during this preparation call only.", + "role": "call" + } + ], + "Runtime::ShutdownPolicy": [ + { + "name": "CE::Runtime::ShutdownPolicy::reset", + "label": "reset()", + "file": "projects/engine/include/cheryl/core/game-framework/shutdown.h", + "line": 122, + "brief": "Withdraw the host policy before destroying borrowed callback targets; reset can cancel an unresolved request.", + "role": "call" + } + ], + "Runtime::ShutdownEndpoint": [ + { + "name": "CE::Runtime::ShutdownEndpoint::request", + "label": "request(Runtime::ShutdownReason reason): Runtime::ShutdownTicket", + "file": "projects/engine/include/cheryl/core/game-framework/shutdown.h", + "line": 76, + "brief": "Stage an ordinary request for a safe platform pass; do not wait for its ticket on an owner needed by the policy.", + "role": "call" + } + ], + "Runtime::RuntimeLifecycle": [ + { + "name": "CE::Runtime::RuntimeLifecycle::snapshot", + "label": "snapshot(): Runtime::RuntimeLifecycleSnapshot", + "file": "projects/engine/include/cheryl/core/game-framework/runtime-lifecycle.h", + "line": 163, + "brief": "Copy synchronized current/terminal lifecycle state from any thread, including after Runtime destruction.", + "role": "call" + } + ] + }, + "operation_labels": { + "Application::GLFWOpenGL::make_context()": "Application::GLFWOpenGL::make_context(const Application::GLFWOpenGL::Options& config): Runtime::Context [unique]" + }, + "namespace": "Runtime" + }, + { + "id": "display-application", + "system": "display", + "view": "application", + "title": "Display \u00b7 application access", + "question": "What is safe from platform hooks, and what should update read instead?", + "edges": [ + { + "source": "@init", + "target": "Runtime::Context", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/display.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@native", + "target": "Runtime::PlatformDispatcher::Submission", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/display.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@native", + "target": "Platform::Window", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/display.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Runtime::Context", + "target": "Platform::DisplaySystem", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 65, + "member": "CE::Runtime::Context::display_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 93, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 108, + "member": "CE::Runtime::Context::display", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 65, + "member": "CE::Runtime::Context::display_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 86, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 86, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 94, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 21 + }, + { + "source": "Runtime::Context", + "target": "Platform::Window", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 109, + "member": "CE::Runtime::Context::window", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 109, + "member": "CE::Runtime::Context::window", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 109, + "member": "CE::Runtime::Context::window", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 199, + "member": "CE::Runtime::Context::window", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 199, + "member": "CE::Runtime::Context::window", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 200, + "member": "CE::Runtime::Context::window", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "Platform::DisplaySystem", + "target": "Platform::Monitor", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/display/display-system-interface.h", + "line": 20, + "member": "CE::Platform::DisplaySystem::monitors", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/display/display-system-interface.h", + "line": 22, + "member": "CE::Platform::DisplaySystem::primary_monitor", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/display/display-system-interface.h", + "line": 24, + "member": "CE::Platform::DisplaySystem::content_scale", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/display/display-system-interface.h", + "line": 26, + "member": "CE::Platform::DisplaySystem::create_window", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/display/display-system-interface.h", + "line": 20, + "member": "CE::Platform::DisplaySystem::monitors", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/display/display-system-interface.h", + "line": 20, + "member": "CE::Platform::DisplaySystem::monitors", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/display/display-system-interface.h", + "line": 22, + "member": "CE::Platform::DisplaySystem::primary_monitor", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/display/display-system-interface.h", + "line": 22, + "member": "CE::Platform::DisplaySystem::primary_monitor", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 12 + }, + { + "source": "Platform::Window", + "target": "Platform::Dimensions", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/display/window-interface.h", + "line": 23, + "member": "CE::Platform::Window::logical_size", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/display/window-interface.h", + "line": 23, + "member": "CE::Platform::Window::logical_size", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/display/window-interface.h", + "line": 23, + "member": "CE::Platform::Window::logical_size", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Platform::Window", + "target": "Platform::WindowMode", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/display/window-interface.h", + "line": 25, + "member": "CE::Platform::Window::mode", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/display/window-interface.h", + "line": 34, + "member": "CE::Platform::Window::set_mode", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/display/window-interface.h", + "line": 25, + "member": "CE::Platform::Window::mode", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/display/window-interface.h", + "line": 25, + "member": "CE::Platform::Window::mode", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/display/window-interface.h", + "line": 34, + "member": "CE::Platform::Window::set_mode", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/display/window-interface.h", + "line": 34, + "member": "CE::Platform::Window::set_mode", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "@update", + "target": "Game::TickContext", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/display.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Game::TickContext", + "target": "Platform::FramebufferSize", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 24, + "member": "CE::Game::TickContext::framebuffer_size", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 24, + "member": "CE::Game::TickContext::framebuffer_size", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "@update", + "target": "Rendering::Camera2D", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/display.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Camera2D", + "target": "Platform::FramebufferSize", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/rendering/camera.cpp", + "line": 42, + "member": "CE::Rendering::Camera2D::recalculate_projection", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/rendering/camera.cpp", + "line": 43, + "member": "CE::Rendering::Camera2D::recalculate_projection", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/camera.h", + "line": 22, + "member": "CE::Rendering::CameraBase::set_framebuffer_size", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 3 + } + ], + "notes": [ + "Direct window and monitor operations require the platform owner; cross-owner requests use a saved platform endpoint.", + "Use copied tick dimensions for simulation layout. Monitor content scale and framebuffer pixels are distinct observations." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/display.md" + ], + "nodes": [ + "@init", + "Runtime::Context", + "@native", + "Runtime::PlatformDispatcher::Submission", + "Platform::Window", + "Platform::DisplaySystem", + "Platform::Monitor", + "Platform::Dimensions", + "Platform::WindowMode", + "@update", + "Game::TickContext", + "Platform::FramebufferSize", + "Rendering::Camera2D" + ], + "operations": { + "Runtime::Context": [ + { + "name": "CE::Runtime::Context::display", + "label": "display(): Platform::DisplaySystem&", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 108, + "brief": "Borrow monitor/window inventory on the platform owner.", + "role": "call" + }, + { + "name": "CE::Runtime::Context::window", + "label": "window(): Platform::Window&", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 109, + "brief": "Borrow the selected live window on platform; throws if none exists and does not marshal to that owner.", + "role": "call" + }, + { + "name": "CE::Runtime::Context::platform_dispatcher", + "label": "platform_dispatcher(): Runtime::PlatformDispatcher&", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 115, + "brief": "Acquire a platform submission endpoint during initialization for later owner-affine window changes.", + "role": "call" + } + ], + "Runtime::PlatformDispatcher::Submission": [ + { + "name": "CE::Runtime::PlatformDispatcher::Submission::submit", + "label": "submit(Work&& work): Result [future]", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 63, + "brief": "Queue an owned callable work(Runtime::Context&) for later platform execution. Result is the callable return type, delivered through its future; retain captures and never wait from the owner needed to execute the work.", + "role": "call" + } + ], + "Platform::Window": [ + { + "name": "CE::Platform::Window::resize", + "label": "resize(width, height)", + "file": "projects/engine/include/cheryl/core/display/window-interface.h", + "line": 33, + "brief": "Request a positive logical size on platform; runtime later copies observed dimensions into ticks.", + "role": "call" + }, + { + "name": "CE::Platform::Window::set_mode", + "label": "set_mode(Platform::WindowMode mode)", + "file": "projects/engine/include/cheryl/core/display/window-interface.h", + "line": 34, + "brief": "Request a backend-supported mode on the platform owner.", + "role": "call" + }, + { + "name": "CE::Platform::Window::hide_cursor", + "label": "hide_cursor(hide)", + "file": "projects/engine/include/cheryl/core/display/window-interface.h", + "line": 35, + "brief": "Change native cursor visibility on platform; this does not request relative pointer capture.", + "role": "call" + } + ], + "Platform::DisplaySystem": [ + { + "name": "CE::Platform::DisplaySystem::monitors", + "label": "monitors(): const Platform::Monitor[]&", + "file": "projects/engine/include/cheryl/core/display/display-system-interface.h", + "line": 20, + "brief": "Borrow current monitor inventory on platform; copy values before handing them to another thread.", + "role": "call" + }, + { + "name": "CE::Platform::DisplaySystem::primary_monitor", + "label": "primary_monitor(): const Platform::Monitor&", + "file": "projects/engine/include/cheryl/core/display/display-system-interface.h", + "line": 22, + "brief": "Borrow the current primary-monitor snapshot under the display inventory lifetime.", + "role": "call" + }, + { + "name": "CE::Platform::DisplaySystem::content_scale", + "label": "content_scale(const Platform::Monitor& monitor)", + "file": "projects/engine/include/cheryl/core/display/display-system-interface.h", + "line": 24, + "brief": "Read a recognized monitor snapshot's X/Y scale on platform; no automatic per-window scale following is implied.", + "role": "call" + } + ], + "Rendering::Camera2D": [ + { + "name": "CE::Rendering::CameraBase::set_framebuffer_size", + "label": "set_framebuffer_size(Platform::FramebufferSize size)", + "file": "projects/engine/include/cheryl/core/rendering/camera.h", + "line": 22, + "brief": "Inherited from CameraBase: apply tick.framebuffer_size on the camera's simulation owner; zero extent is retained.", + "role": "call" + } + ] + }, + "namespace": "Platform" + }, + { + "id": "input-application", + "system": "input", + "view": "application", + "title": "Input \u00b7 application actions and controllers", + "question": "What must initialization bind, and what does each update consume?", + "edges": [ + { + "source": "Input::System", + "target": "Input::Actions::Bindings", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 36, + "member": "CE::Input::System::bindings", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 36, + "member": "CE::Input::System::bindings", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 36, + "member": "CE::Input::System::bindings", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 38, + "member": "CE::Input::System::action_contexts", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 39, + "member": "CE::Input::System::action_snapshot", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 17, + "member": "CE::Input::System::begin_input_poll", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 18, + "member": "CE::Input::System::begin_input_poll", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 35, + "member": "CE::Input::System::publish_input", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 8 + }, + { + "source": "Input::Actions::Bindings", + "target": "Input::Control", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/controls/input-bindings.h", + "line": 67, + "member": "CE::Input::Actions::Bindings::held_buttons_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/controls/input-bindings.h", + "line": 69, + "member": "CE::Input::Actions::Bindings::physical_axes_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-bindings.h", + "line": 93, + "member": "CE::Input::Actions::Bindings::bind_button", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-bindings.h", + "line": 97, + "member": "CE::Input::Actions::Bindings::bind_axis", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-bindings.h", + "line": 98, + "member": "CE::Input::Actions::Bindings::bind_axis", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-bindings.h", + "line": 109, + "member": "CE::Input::Actions::Bindings::on_axis", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-bindings.h", + "line": 111, + "member": "CE::Input::Actions::Bindings::on_delta", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-bindings.h", + "line": 112, + "member": "CE::Input::Actions::Bindings::on_button", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 63 + }, + { + "source": "Input::Control", + "target": "Input::KeyboardKey", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/device-binding.h", + "line": 17, + "member": "CE::Input::Control::Control", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/device-binding.h", + "line": 17, + "member": "CE::Input::Control::Control", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/device-binding.h", + "line": 17, + "member": "CE::Input::Control::Control", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "@controller", + "target": "Input::Actions::ContextDefinition", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/input.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@controller", + "target": "Input::Actions::ContextSubmission", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/input.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Input::Actions::ContextSubmission", + "target": "Input::Actions::Context", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/action-context.h", + "line": 72, + "member": "CE::Input::Actions::ContextSubmission::attach", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/action-context.h", + "line": 72, + "member": "CE::Input::Actions::ContextSubmission::attach", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/action-context.h", + "line": 72, + "member": "CE::Input::Actions::ContextSubmission::attach", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/controls/action-context.cpp", + "line": 38, + "member": "CE::Input::Actions::ContextSubmission::attach", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/action-context.cpp", + "line": 38, + "member": "CE::Input::Actions::ContextSubmission::attach", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/action-context.cpp", + "line": 51, + "member": "CE::Input::Actions::ContextSubmission::attach", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "@controller", + "target": "Input::Actions::Context", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/input.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@update", + "target": "Game::TickContext", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/input.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Game::TickContext", + "target": "Input::Tick", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 23, + "member": "CE::Game::TickContext::input", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 23, + "member": "CE::Game::TickContext::input", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 31, + "member": "CE::Game::TickContext::observed_seconds", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/tick-context.cpp", + "line": 19, + "member": "CE::Game::TickContext::button_simulation_seconds", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 4 + }, + { + "source": "Input::Tick", + "target": "Input::Actions::View", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 74, + "member": "CE::Input::Tick::actions", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 75, + "member": "CE::Input::Tick::actions", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 74, + "member": "CE::Input::Tick::actions", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 74, + "member": "CE::Input::Tick::actions", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 75, + "member": "CE::Input::Tick::actions", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 75, + "member": "CE::Input::Tick::actions", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/controls/tick-input.cpp", + "line": 42, + "member": "CE::Input::Tick::actions", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/tick-input.cpp", + "line": 42, + "member": "CE::Input::Tick::actions", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 12 + }, + { + "source": "@controller", + "target": "Input::Actions::View", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/input.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@init", + "target": "Runtime::Context", + "optional": false, + "evidence": [ + { + "file": "projects/apps/first-game/src/first-game.cpp", + "line": 26, + "via": "maintained application calls engine_.input() inside init_platform()" + } + ], + "evidence_count": 1 + }, + { + "source": "Runtime::Context", + "target": "Input::System", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 113, + "member": "CE::Runtime::Context::input", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Input::Actions::ContextSubmission", + "target": "Input::Actions::ContextDefinition", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/action-context.h", + "line": 72, + "member": "CE::Input::Actions::ContextSubmission::attach", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Input::Tick", + "target": "Input::Actions::Context", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 74, + "member": "CE::Input::Tick::actions", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Input::System", + "target": "Input::Actions::ContextSubmission", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 38, + "member": "CE::Input::System::action_contexts", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "attach(Actions::ContextDefinition) returns an Actions::Context lease. Pass that lease to Tick::actions(Actions::Context) to obtain an Actions::View; the alternative overload accepts Actions::ContextId. Actions::ContextDefinition is setup data, not the lookup argument.", + "Direct binding changes run during platform initialization. Reusable controllers retain a context endpoint and their own lease.", + "Updates consume semantic actions and copied observations; text input/focus is a separate focused view.", + "Acquire Runtime::Context::input(), action_contexts() and keyboard_id() on platform during initialization; use retained endpoints and immutable tick actions afterward." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/input.md" + ], + "nodes": [ + "@init", + "Runtime::Context", + "Input::System", + "Input::Actions::Bindings", + "Input::Control", + "Input::KeyboardKey", + "@controller", + "Input::Actions::ContextDefinition", + "Input::Actions::ContextSubmission", + "Input::Actions::Context", + "@update", + "Game::TickContext", + "Input::Tick", + "Input::Actions::View" + ], + "operations": { + "Runtime::Context": [ + { + "name": "CE::Runtime::Context::input", + "label": "input(): Input::System&", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 113, + "brief": "Borrow the initialized input adapter during init_platform(); access does not marshal to platform.", + "role": "call" + } + ], + "Input::System": [ + { + "name": "CE::Input::System::bindings", + "label": "bindings(): Input::Actions::Bindings&", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 36, + "brief": "Borrow mutable direct-action bindings on platform; do not mutate from concurrent simulation.", + "role": "call" + }, + { + "name": "CE::Input::System::action_contexts", + "label": "action_contexts(): Input::Actions::ContextSubmission", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 38, + "brief": "Acquire a retained request endpoint on platform; later attach() calls can run on simulation or other producers.", + "role": "call" + }, + { + "name": "CE::Input::System::keyboard_id", + "label": "keyboard_id(): Input::DeviceId", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 40, + "brief": "Read the keyboard identity on platform and retain that value for controller definitions.", + "role": "call" + } + ], + "Input::Actions::Bindings": [ + { + "name": "CE::Input::Actions::Bindings::bind_button", + "label": "bind_button(Input::Control control, Input::Actions::Id action): Input::Actions::BindingId", + "file": "projects/engine/include/cheryl/core/controls/input-bindings.h", + "line": 93, + "brief": "Map a physical control or chord to a direct action on platform; retain the returned BindingId. The chord overload accepts Input::Actions::Chord chord instead of Input::Control control.", + "role": "call" + }, + { + "name": "CE::Input::Actions::Bindings::bind_axis", + "label": "bind_axis(Input::Control axis, Input::Actions::Id action, Input::Actions::AxisOptions options): Input::Actions::BindingId", + "file": "projects/engine/include/cheryl/core/controls/input-bindings.h", + "line": 97, + "brief": "Map a physical axis with optional modifiers, scale and dead zone on platform. The modified-axis overload additionally accepts Input::Actions::Chord modifiers before axis; retain the returned BindingId.", + "role": "call" + }, + { + "name": "CE::Input::Actions::Bindings::unbind", + "label": "unbind(Input::Actions::BindingId binding)", + "file": "projects/engine/include/cheryl/core/controls/input-bindings.h", + "line": 99, + "brief": "Remove a direct binding by its retained token on platform during cleanup.", + "role": "call" + } + ], + "Input::Actions::ContextSubmission": [ + { + "name": "CE::Input::Actions::ContextSubmission::attach", + "label": "attach(Input::Actions::ContextDefinition definition): Input::Actions::Context", + "file": "projects/engine/include/cheryl/core/controls/action-context.h", + "line": 72, + "brief": "Accept an owned Input::Actions::ContextDefinition and return an Input::Actions::Context lease; mappings apply at the next input poll, not immediately.", + "role": "call" + } + ], + "Input::Actions::Context": [ + { + "name": "CE::Input::Actions::Context::id", + "label": "id(): Input::Actions::ContextId", + "file": "projects/engine/include/cheryl/core/controls/action-context.h", + "line": 57, + "brief": "Copy namespace identity if a retained historical action view should outlive the lease.", + "role": "call" + }, + { + "name": "CE::Input::Actions::Context::reset", + "label": "reset()", + "file": "projects/engine/include/cheryl/core/controls/action-context.h", + "line": 54, + "brief": "Request removal at the next poll with exclusive handle access; retained historical ticks are unchanged.", + "role": "call" + } + ], + "Input::Tick": [ + { + "name": "CE::Input::Tick::actions", + "label": "actions(const Input::Actions::Context& context): Input::Actions::View [borrowed]", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 74, + "brief": "Select one controller's namespace using its Input::Actions::Context lease; the ContextId overload accepts the copied namespace identity. The returned View borrows this update's Tick. ContextDefinition is supplied to attach(), not actions().", + "role": "call" + }, + { + "name": "CE::Input::Tick::button", + "label": "button(Input::Actions::Id action): Input::Actions::ButtonTickState", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 71, + "brief": "Read direct/global button activity and observation durations without polling live devices.", + "role": "call" + }, + { + "name": "CE::Input::Tick::axis", + "label": "axis(Input::Actions::Id action): Input::Actions::AxisTickState", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 72, + "brief": "Read direct/global absolute values or accumulated relative deltas.", + "role": "call" + } + ], + "Input::Actions::View": [ + { + "name": "CE::Input::Actions::View::button", + "label": "button(Input::Actions::Id action): Input::Actions::ButtonTickState", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 116, + "brief": "Read a local button in the selected context; keep the borrowed Tick alive and unmoved.", + "role": "call" + }, + { + "name": "CE::Input::Actions::View::axis", + "label": "axis(Input::Actions::Id action): Input::Actions::AxisTickState", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 117, + "brief": "Read a local axis in the selected context; missing actions return inactive/zero state.", + "role": "call" + } + ] + }, + "namespace": "Input" + }, + { + "id": "assets-application", + "system": "assets", + "view": "application", + "title": "Assets \u00b7 application loading and playback", + "question": "Which values does a game load once, share and advance independently?", + "edges": [ + { + "source": "@load", + "target": "Assets::Loading::Loader", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/assets.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Assets::Loading::Loader", + "target": "Assets::Loading::Prepared", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 105, + "member": "CE::Assets::Loading::Loader::prepare_selected", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 112, + "member": "CE::Assets::Loading::Loader::prepare", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 113, + "member": "CE::Assets::Loading::Loader::prepare_graphics", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 114, + "member": "CE::Assets::Loading::Loader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 105, + "member": "CE::Assets::Loading::Loader::prepare_selected", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 105, + "member": "CE::Assets::Loading::Loader::prepare_selected", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 112, + "member": "CE::Assets::Loading::Loader::prepare", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 112, + "member": "CE::Assets::Loading::Loader::prepare", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 47 + }, + { + "source": "@init", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/assets.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Assets::Loading::Loader", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 114, + "member": "CE::Assets::Loading::Loader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 115, + "member": "CE::Assets::Loading::Loader::load_assets", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 114, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 114, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 115, + "member": "CE::Assets::Loading::Loader::load_assets", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 115, + "member": "CE::Assets::Loading::Loader::load_assets", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 296, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 296, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 11 + }, + { + "source": "@world", + "target": "Assets::Sprites::Manager", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/assets.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@world", + "target": "Assets::Tilesets::Manager", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/assets.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@world", + "target": "Assets::Sprites::Asset", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/assets.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@world", + "target": "Assets::Tilesets::Asset", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/assets.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@update", + "target": "Assets::Sprites::Animation", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/assets.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@prepare", + "target": "Rendering::Submission::resolve_sprite()", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/assets.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Assets::Sprites::Asset", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "CE::Rendering::Submission::resolve_sprite", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 2 + }, + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Rendering::DrawPacket2D", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "CE::Rendering::Submission::resolve_sprite", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 2 + }, + { + "source": "Assets::Sprites::Manager", + "target": "Assets::Sprites::Asset", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/asset-management/sprite-mgr.h", + "line": 30, + "member": "CE::Assets::Sprites::Manager::get_asset", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Assets::Tilesets::Manager", + "target": "Assets::Tilesets::Asset", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 149, + "member": "CE::Assets::Cache::Store::get_asset", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Assets::Sprites::Asset", + "target": "Assets::Sprites::Animation", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/sprite.h", + "line": 62, + "member": "CE::Assets::Sprites::Asset::animation", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "Shared asset metadata stays immutable; application objects own transforms, gameplay and playback state.", + "A failed upload may leave earlier successful entries published. Validate looked-up handles and release game copies before backend closure." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/assets.md" + ], + "nodes": [ + "@load", + "Assets::Loading::Loader", + "Assets::Loading::Prepared", + "@init", + "Rendering::Resources::Provider", + "@world", + "Assets::Sprites::Manager", + "Assets::Tilesets::Manager", + "Assets::Sprites::Asset", + "Assets::Tilesets::Asset", + "@update", + "Assets::Sprites::Animation", + "@prepare", + "Rendering::Submission::resolve_sprite()", + "Rendering::DrawPacket2D" + ], + "operations": { + "Assets::Loading::Loader": [ + { + "name": "CE::Assets::Loading::Loader::load_assets", + "label": "load_assets(Rendering::Resources::Provider& provider)", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 115, + "brief": "Prepare and upload synchronously on the active provider/loading owner; partial publication is possible on failure.", + "role": "call" + }, + { + "name": "CE::Assets::Loading::Loader::prepare", + "label": "prepare(): Assets::Loading::Prepared", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 112, + "brief": "Own a CPU-only batch without publishing caches; the application may run this on a worker.", + "role": "call" + }, + { + "name": "CE::Assets::Loading::Loader::upload", + "label": "upload(Assets::Loading::Prepared prepared, Rendering::Resources::Provider& provider, replace_shaders)", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 114, + "brief": "Consume the prepared batch on the active provider/loading owner. Successful entries can publish before the whole batch completes.", + "role": "call" + } + ], + "Assets::Sprites::Manager": [ + { + "name": "CE::Assets::Sprites::Manager::get_asset", + "label": "get_asset(name_space, name): Assets::Sprites::Manager::Handle [shared]", + "file": "projects/engine/include/cheryl/core/resources/asset-management/sprite-mgr.h", + "line": 30, + "brief": "Look up an exact namespace/name pair; returns a retained asset or null without loading.", + "role": "call" + } + ], + "Assets::Tilesets::Manager": [ + { + "name": "CE::Assets::Cache::Store::get_asset", + "label": "get_asset(key): Assets::Tilesets::Manager::Handle [shared]", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 149, + "brief": "Inherited Store lookup of an exact qualified namespace:name key. Returns a retained asset or null without loading.", + "role": "call" + } + ], + "Assets::Sprites::Asset": [ + { + "name": "CE::Assets::Sprites::Asset::animation", + "label": "animation(animation, facing): Assets::Sprites::Animation", + "file": "projects/engine/include/cheryl/assets/types/2d/sprite.h", + "line": 62, + "brief": "Create independent CPU playback from an exact clip name and optional facing; missing or ambiguous selection throws.", + "role": "call" + } + ], + "Assets::Tilesets::Asset": [ + { + "name": "CE::Assets::Tilesets::Asset::tile", + "label": "tile(cell): Assets::Tilesets::Tile", + "file": "projects/engine/include/cheryl/assets/types/2d/tileset.h", + "line": 78, + "brief": "Retain one static grid cell and its resources; invalid cell selection throws.", + "role": "call" + }, + { + "name": "CE::Assets::Tilesets::Asset::animation", + "label": "animation(name): Assets::Tilesets::Animation", + "file": "projects/engine/include/cheryl/assets/types/2d/tileset.h", + "line": 79, + "brief": "Create an independent cursor for a named tile animation.", + "role": "call" + }, + { + "name": "CE::Assets::Tilesets::Asset::cell_at", + "label": "cell_at(Assets::Grids::CellIndex target, elapsed): Assets::Grids::CellIndex", + "file": "projects/engine/include/cheryl/assets/types/2d/tileset.h", + "line": 85, + "brief": "Resolve one original target at nonnegative elapsed milliseconds; this neither advances shared data nor recursively animates the result.", + "role": "call" + } + ], + "Assets::Sprites::Animation": [ + { + "name": "CE::Assets::Sprites::Animation::advance", + "label": "advance(elapsed)", + "file": "projects/engine/include/cheryl/assets/types/2d/sprite.h", + "line": 35, + "brief": "Advance the independent cursor on its simulation owner using finite nonnegative elapsed seconds.", + "role": "call" + }, + { + "name": "CE::Assets::Sprites::Animation::cell", + "label": "cell(): Assets::Grids::CellIndex", + "file": "projects/engine/include/cheryl/assets/types/2d/sprite.h", + "line": 37, + "brief": "Read the current grid cell for draw submission; it differs from the clip-frame ordinal.", + "role": "call" + } + ] + }, + "operation_labels": { + "Rendering::Submission::resolve_sprite()": "Rendering::Submission::resolve_sprite(const Assets::Sprites::Asset& sprite, Assets::Grids::CellIndex cell, const Rendering::DrawStyle2D& style, const Rendering::Submission::Context2D& context): Rendering::DrawPacket2D" + }, + "namespace": "Assets" + }, + { + "id": "resources-application", + "system": "resources", + "view": "application", + "title": "Resources \u00b7 application uploads and retained draws", + "question": "What must an upload owner supply, and what do later draws retain?", + "edges": [ + { + "source": "@load", + "target": "Assets::Images::decode()", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/resources.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Assets::Images::decode()", + "target": "Assets::Images::Decoded", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/resources/decoded-image.h", + "line": 22, + "member": "Assets::Images::decode()", + "via": "operation signature" + }, + { + "file": "projects/engine/include/cheryl/assets/resources/decoded-image.h", + "line": 24, + "member": "Assets::Images::decode()", + "via": "operation signature" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/decoded-image.h", + "line": 22, + "member": "CE::Assets::Images::decode", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/decoded-image.h", + "line": 24, + "member": "CE::Assets::Images::decode", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 4 + }, + { + "source": "@init", + "target": "Runtime::Context", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/resources.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Runtime::Context", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 68, + "member": "CE::Runtime::Context::resources_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 93, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 112, + "member": "CE::Runtime::Context::resources", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 68, + "member": "CE::Runtime::Context::resources_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 89, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 89, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 97, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 21 + }, + { + "source": "@native", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/resources.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Resources::Provider", + "target": "Assets::Images::Decoded", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 66, + "member": "CE::Rendering::Resources::Provider::create_image", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 66, + "member": "CE::Rendering::Resources::Provider::create_image", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 66, + "member": "CE::Rendering::Resources::Provider::create_image", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/assets/resources/resource-provider.cpp", + "line": 23, + "member": "CE::Rendering::Resources::Provider::load_image", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 4 + }, + { + "source": "Rendering::Resources::Provider", + "target": "Rendering::Resources::Image", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 63, + "member": "CE::Rendering::Resources::Provider::load_image", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 66, + "member": "CE::Rendering::Resources::Provider::create_image", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 74, + "member": "CE::Rendering::Resources::Provider::create_font_atlas", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 63, + "member": "CE::Rendering::Resources::Provider::load_image", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 63, + "member": "CE::Rendering::Resources::Provider::load_image", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 66, + "member": "CE::Rendering::Resources::Provider::create_image", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 66, + "member": "CE::Rendering::Resources::Provider::create_image", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 74, + "member": "CE::Rendering::Resources::Provider::create_font_atlas", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 12 + }, + { + "source": "Rendering::Resources::Provider", + "target": "Rendering::Geometry::Buffer2D", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 77, + "member": "CE::Rendering::Resources::Provider::upload_geometry", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 81, + "member": "CE::Rendering::Resources::Provider::upload_geometry", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 83, + "member": "CE::Rendering::Resources::Provider::upload_geometry", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 77, + "member": "CE::Rendering::Resources::Provider::upload_geometry", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 76, + "member": "CE::Rendering::Resources::Provider::upload_geometry", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 81, + "member": "CE::Rendering::Resources::Provider::upload_geometry", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 80, + "member": "CE::Rendering::Resources::Provider::upload_geometry", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 83, + "member": "CE::Rendering::Resources::Provider::upload_geometry", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 14 + }, + { + "source": "Rendering::Resources::Material", + "target": "Rendering::Resources::Pipeline", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/assets/resources/pipeline.cpp", + "line": 61, + "member": "CE::Rendering::Resources::Material::Material", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/assets/resources/pipeline.cpp", + "line": 63, + "member": "CE::Rendering::Resources::Material::Material", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/assets/resources/pipeline.cpp", + "line": 77, + "member": "CE::Rendering::Resources::Material::resolve", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "@prepare", + "target": "Rendering::DrawPacket2D", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/resources.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::DrawPacket2D", + "target": "Rendering::Resources::Material", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 34, + "member": "CE::Rendering::DrawPacket2D::material", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 34, + "member": "CE::Rendering::DrawPacket2D::material", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Rendering::DrawPacket2D", + "target": "Rendering::Geometry::Buffer2D", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 33, + "member": "CE::Rendering::DrawPacket2D::geometry", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 33, + "member": "CE::Rendering::DrawPacket2D::geometry", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "@report", + "target": "Rendering::GraphicsDomain", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/resources.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Resources::Provider", + "target": "Rendering::GraphicsDomain", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 59, + "member": "CE::Rendering::Resources::Provider::graphics_domain", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "Decode may run on CPU workers; native upload must use the initialized provider/loading owner.", + "Ordinary sprite/text/UI loading supplies default materials. Direct pipeline/material construction is an advanced option, not a prerequisite." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/resources.md" + ], + "nodes": [ + "@load", + "Assets::Images::decode()", + "Assets::Images::Decoded", + "@init", + "Runtime::Context", + "Rendering::Resources::Provider", + "@native", + "Rendering::Resources::Image", + "Rendering::Geometry::Buffer2D", + "Rendering::Resources::Material", + "Rendering::Resources::Pipeline", + "@prepare", + "Rendering::DrawPacket2D", + "@report", + "Rendering::GraphicsDomain" + ], + "operations": { + "Runtime::Context": [ + { + "name": "CE::Runtime::Context::resources", + "label": "resources(): Rendering::Resources::Provider&", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 112, + "brief": "Borrow the selected resource provider. The getter does not marshal calls to its graphics owner.", + "role": "call" + } + ], + "Rendering::Resources::Provider": [ + { + "name": "CE::Rendering::Resources::Provider::create_image", + "label": "create_image(const Assets::Images::Decoded& image): Rendering::Resources::Image [shared]", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 66, + "brief": "Copy owned decoded RGBA pixels into a fresh immutable image on the backend upload owner/current context.", + "role": "call" + }, + { + "name": "CE::Rendering::Resources::Provider::upload_geometry", + "label": "upload_geometry(const Rendering::Geometry::Vertex2D[] vertices [borrowed], Rendering::Geometry::Topology topology): Rendering::Geometry::Buffer2D [shared]", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 77, + "brief": "Upload the ordinary Vertex2D layout on the active provider owner. Separate overloads accept Vertex2DColor[] or a compatibility shared vertex owner.", + "role": "call" + }, + { + "name": "CE::Rendering::Resources::Provider::graphics_domain", + "label": "graphics_domain(): Rendering::GraphicsDomain", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 59, + "brief": "Copy the retained graphics-domain observation token; empty means unsupported.", + "role": "call" + } + ], + "Rendering::GraphicsDomain": [ + { + "name": "CE::Rendering::GraphicsDomain::snapshot", + "label": "snapshot(): Rendering::GraphicsDomainSnapshot", + "file": "projects/engine/include/cheryl/core/rendering/graphics-domain.h", + "line": 47, + "brief": "Copy native-domain identity, sequence and usability from any thread without retaining native objects.", + "role": "call" + } + ] + }, + "operation_labels": { + "Assets::Images::decode()": "Assets::Images::decode(file): Assets::Images::Decoded" + }, + "namespace": "Rendering::Resources" + }, + { + "id": "rendering-application", + "system": "rendering", + "view": "application", + "title": "Rendering \u00b7 application frame preparation", + "question": "Which real API types does one ordinary sprite draw touch?", + "edges": [ + { + "source": "@update", + "target": "Rendering::Camera2D", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/rendering.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@prepare", + "target": "Rendering::Camera2D", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/rendering.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@prepare", + "target": "Rendering::FrameWriter", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/rendering.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::FrameWriter", + "target": "Rendering::PassWriter", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 132, + "member": "CE::Rendering::FrameWriter::begin_pass", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 132, + "member": "CE::Rendering::FrameWriter::begin_pass", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 132, + "member": "CE::Rendering::FrameWriter::begin_pass", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 147, + "member": "CE::Rendering::FrameWriter::begin_pass", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 4 + }, + { + "source": "@prepare", + "target": "Rendering::Submission::Context2D", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/rendering.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@prepare", + "target": "Rendering::DrawStyle2D", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/rendering.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@prepare", + "target": "Rendering::Submission::resolve_sprite()", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/rendering.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Assets::Sprites::Asset", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "CE::Rendering::Submission::resolve_sprite", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 2 + }, + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Rendering::Submission::Context2D", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "CE::Rendering::Submission::resolve_sprite", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 2 + }, + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Rendering::DrawStyle2D", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "CE::Rendering::Submission::resolve_sprite", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 2 + }, + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Rendering::DrawPacket2D", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "CE::Rendering::Submission::resolve_sprite", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 2 + }, + { + "source": "Rendering::PassWriter", + "target": "Rendering::DrawPacket2D", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 88, + "member": "CE::Rendering::PassWriter::add", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 95, + "member": "CE::Rendering::PassWriter::add", + "access": "public", + "via": "member declaration" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 76, + "member": "CE::Rendering::PassWriter::reserve_draws", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 88, + "member": "CE::Rendering::PassWriter::add", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 88, + "member": "CE::Rendering::PassWriter::add", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 91, + "member": "CE::Rendering::PassWriter::add", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 92, + "member": "CE::Rendering::PassWriter::add", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 95, + "member": "CE::Rendering::PassWriter::add", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 13 + } + ], + "notes": [ + "Advance camera/gameplay/animation in update. Preparation may be skipped; it only snapshots the chosen appearance.", + "Writers borrow one runtime slot for the hook. Native draw/upload work belongs to graphics; loaded assets already supply ordinary materials." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/rendering.md" + ], + "nodes": [ + "@update", + "Rendering::Camera2D", + "@prepare", + "Rendering::FrameWriter", + "Rendering::PassWriter", + "Rendering::Submission::Context2D", + "Rendering::DrawStyle2D", + "Rendering::Submission::resolve_sprite()", + "Assets::Sprites::Asset", + "Rendering::DrawPacket2D" + ], + "operations": { + "Rendering::Camera2D": [ + { + "name": "CE::Rendering::CameraBase::set_framebuffer_size", + "label": "set_framebuffer_size(Platform::FramebufferSize size)", + "file": "projects/engine/include/cheryl/core/rendering/camera.h", + "line": 22, + "brief": "Inherited from CameraBase. Update framebuffer pixel extent and projection on the camera owner.", + "role": "call" + }, + { + "name": "CE::Rendering::CameraBase::set_view_matrix", + "label": "set_view_matrix(const glm::mat4& view)", + "file": "projects/engine/include/cheryl/core/rendering/camera.h", + "line": 25, + "brief": "Inherited from CameraBase. Copy a world-to-view transform on the camera owner.", + "role": "call" + }, + { + "name": "CE::Rendering::CameraBase::projection_matrix", + "label": "projection_matrix(): const glm::mat4&", + "file": "projects/engine/include/cheryl/core/rendering/camera.h", + "line": 28, + "brief": "Inherited from CameraBase. Borrow the projection matrix to copy into begin_pass(); exclude concurrent camera mutation.", + "role": "call" + }, + { + "name": "CE::Rendering::CameraBase::view_matrix", + "label": "view_matrix(): const glm::mat4&", + "file": "projects/engine/include/cheryl/core/rendering/camera.h", + "line": 29, + "brief": "Inherited from CameraBase. Borrow the view matrix to copy into begin_pass(); exclude concurrent camera mutation.", + "role": "call" + } + ], + "Rendering::FrameWriter": [ + { + "name": "CE::Rendering::FrameWriter::begin_pass", + "label": "begin_pass(const glm::mat4& projection, const glm::mat4& view, Rendering::Pipelines::PassConstraints2D constraints, Rendering::Parameters::Set parameters): Rendering::PassWriter [borrowed]", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 132, + "brief": "Append a pass with copied camera matrices, constraints and pass parameters during frame preparation.", + "role": "call" + } + ], + "Rendering::PassWriter": [ + { + "name": "CE::Rendering::PassWriter::add", + "label": "add(Rendering::DrawPacket2D draw)", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 88, + "brief": "Validate and append one retained packet. A separate batch overload accepts DrawPacket2D[]; packet order is preserved.", + "role": "call" + }, + { + "name": "CE::Rendering::PassWriter::semantics", + "label": "semantics(): Rendering::Parameters::PassSemantics", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 78, + "brief": "Copy this pass projection/view semantics for submission resolution.", + "role": "call" + }, + { + "name": "CE::Rendering::PassWriter::parameters", + "label": "parameters(): const Rendering::Parameters::Set&", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 83, + "brief": "Borrow this pass parameter set while creating the submission Context2D; outer frame growth can invalidate references.", + "role": "call" + }, + { + "name": "CE::Rendering::PassWriter::constraints", + "label": "constraints(): const Rendering::Pipelines::PassConstraints2D&", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 84, + "brief": "Borrow this pass pipeline constraints to copy into the submission Context2D; keep the frame stable while copying.", + "role": "call" + } + ] + }, + "operation_labels": { + "Rendering::Submission::resolve_sprite()": "Rendering::Submission::resolve_sprite(const Assets::Sprites::Asset& sprite, Assets::Grids::CellIndex cell, const Rendering::DrawStyle2D& style, const Rendering::Submission::Context2D& context): Rendering::DrawPacket2D" + }, + "namespace": "Rendering" + }, + { + "id": "text-application", + "system": "text", + "view": "application", + "title": "Text \u00b7 application font and label choices", + "question": "What is the normal path for a static label or a changing label?", + "edges": [ + { + "source": "@init", + "target": "Runtime::Context", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/text.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Runtime::Context", + "target": "Text::FontService", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 82, + "member": "CE::Runtime::Context::fonts_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 114, + "member": "CE::Runtime::Context::fonts", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 82, + "member": "CE::Runtime::Context::fonts_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 114, + "member": "CE::Runtime::Context::fonts", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 114, + "member": "CE::Runtime::Context::fonts", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 5 + }, + { + "source": "@ui", + "target": "Text::Font", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/text.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Text::FontService", + "target": "Text::Font", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 54, + "member": "CE::Text::FontService::default_font", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 55, + "member": "CE::Text::FontService::get", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 56, + "member": "CE::Text::FontService::all", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 57, + "member": "CE::Text::FontService::fallbacks", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 58, + "member": "CE::Text::FontService::choose_random", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 59, + "member": "CE::Text::FontService::choose_random", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 60, + "member": "CE::Text::FontService::restore_or_choose", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 62, + "member": "CE::Text::FontService::restore_or_choose", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 93 + }, + { + "source": "@load", + "target": "Text::layout_text()", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/text.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Text::layout_text()", + "target": "Text::Font", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/text/layout.h", + "line": 84, + "member": "Text::layout_text()", + "via": "operation signature" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/text/layout.h", + "line": 84, + "member": "CE::Text::layout_text", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 2 + }, + { + "source": "Text::layout_text()", + "target": "Text::ShapedText", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/text/layout.h", + "line": 83, + "member": "Text::layout_text()", + "via": "operation signature" + }, + { + "file": "projects/engine/include/cheryl/text/layout.h", + "line": 84, + "member": "Text::layout_text()", + "via": "operation signature" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/layout.h", + "line": 83, + "member": "CE::Text::layout_text", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/layout.h", + "line": 84, + "member": "CE::Text::layout_text", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 4 + }, + { + "source": "@load", + "target": "Text::Rendering::prepare()", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/text.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Text::Rendering::prepare()", + "target": "Text::ShapedText", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 115, + "member": "Text::Rendering::prepare()", + "via": "operation signature" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 115, + "member": "CE::Text::Rendering::prepare", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 2 + }, + { + "source": "Text::Rendering::prepare()", + "target": "Text::Rendering::Prepared", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 115, + "member": "Text::Rendering::prepare()", + "via": "operation signature" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 115, + "member": "CE::Text::Rendering::prepare", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 2 + }, + { + "source": "@init", + "target": "Text::Rendering::upload()", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/text.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Text::Rendering::upload()", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 116, + "member": "Text::Rendering::upload()", + "via": "operation signature" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 116, + "member": "CE::Text::Rendering::upload", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 2 + }, + { + "source": "Text::Rendering::upload()", + "target": "Text::Rendering::Prepared", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 116, + "member": "Text::Rendering::upload()", + "via": "operation signature" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 116, + "member": "CE::Text::Rendering::upload", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 2 + }, + { + "source": "Text::Rendering::upload()", + "target": "Text::Rendering::Generation", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 116, + "member": "Text::Rendering::upload()", + "via": "operation signature" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 116, + "member": "CE::Text::Rendering::upload", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 2 + }, + { + "source": "@update", + "target": "Text::Rendering::Updater", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/text.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Text::Rendering::Updater", + "target": "Text::Rendering::Request", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 48, + "member": "CE::Text::Rendering::Updater::Updater", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 53, + "member": "CE::Text::Rendering::Updater::request", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 48, + "member": "CE::Text::Rendering::Updater::Updater", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 48, + "member": "CE::Text::Rendering::Updater::Updater", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 53, + "member": "CE::Text::Rendering::Updater::request", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 53, + "member": "CE::Text::Rendering::Updater::request", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 39, + "member": "CE::Text::Rendering::Updater::State::desired", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 43, + "member": "CE::Text::Rendering::Updater::State::active", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 30 + }, + { + "source": "Text::Rendering::Updater", + "target": "Text::Rendering::Generation", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 57, + "member": "CE::Text::Rendering::Updater::current", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 57, + "member": "CE::Text::Rendering::Updater::current", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 57, + "member": "CE::Text::Rendering::Updater::current", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 46, + "member": "CE::Text::Rendering::Updater::State::uploading", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 47, + "member": "CE::Text::Rendering::Updater::State::current", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 66, + "member": "CE::Text::Rendering::Updater::fail", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 72, + "member": "CE::Text::Rendering::Updater::pending", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 83, + "member": "CE::Text::Rendering::Updater::Updater", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 18 + }, + { + "source": "@prepare", + "target": "Rendering::Submission::resolve_text()", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/text.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Submission::resolve_text()", + "target": "Text::Rendering::Generation", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 70, + "member": "Rendering::Submission::resolve_text()", + "via": "operation signature" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 70, + "member": "CE::Rendering::Submission::resolve_text", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 2 + }, + { + "source": "Rendering::Submission::resolve_text()", + "target": "Rendering::DrawPacket2D", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 70, + "member": "CE::Rendering::Submission::resolve_text", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "Static labels explicitly layout, prepare and upload after graphics initialization. Changing labels can use Updater instead of application futures.", + "Context supplies an embedded default font. Configure named families/defaults before consumers; changing the catalogue does not rebind existing handles." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/text.md" + ], + "nodes": [ + "@init", + "Runtime::Context", + "Text::FontService", + "@ui", + "Text::Font", + "@load", + "Text::layout_text()", + "Text::ShapedText", + "Text::Rendering::prepare()", + "Text::Rendering::Prepared", + "Text::Rendering::upload()", + "Rendering::Resources::Provider", + "Text::Rendering::Generation", + "@update", + "Text::Rendering::Updater", + "Text::Rendering::Request", + "@prepare", + "Rendering::Submission::resolve_text()", + "Rendering::DrawPacket2D" + ], + "operations": { + "Runtime::Context": [ + { + "name": "CE::Runtime::Context::fonts", + "label": "fonts(): Text::FontService&", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 114, + "brief": "Borrow the Context CPU font catalogue; configure before constructing consumers.", + "role": "call" + } + ], + "Text::FontService": [ + { + "name": "CE::Text::FontService::configure", + "label": "configure(const Text::FontConfiguration& configuration)", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 51, + "brief": "Configure the full font catalogue before constructing consumers. The API also offers FontSelection overloads; existing handles keep their previous snapshots.", + "role": "call" + }, + { + "name": "CE::Text::FontService::default_font", + "label": "default_font(): Text::Font", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 54, + "brief": "Retain the configured default, or embedded fallback when the default is unavailable.", + "role": "call" + }, + { + "name": "CE::Text::FontService::get", + "label": "get(reference): Text::Font", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 55, + "brief": "Look up an exact application font reference; missing references return the default rather than an empty handle.", + "role": "call" + } + ], + "Text::Font": [ + { + "name": "CE::Text::Font::reference", + "label": "reference()", + "file": "projects/engine/include/cheryl/text/font.h", + "line": 46, + "brief": "Borrow the stable reference to save a selected font choice; keep this nonempty retained font alive.", + "role": "call" + } + ], + "Text::ShapedText": [ + { + "name": "CE::Text::ShapedText::glyphs", + "label": "glyphs(): const Text::ShapedGlyph[] [borrowed]", + "file": "projects/engine/include/cheryl/text/layout.h", + "line": 78, + "brief": "Inspect visually ordered glyphs and logical shaping-cluster ranges before upload; retain the unmoved layout.", + "role": "call" + }, + { + "name": "CE::Text::ShapedText::lines", + "label": "lines(): const Text::TextLine[] [borrowed]", + "file": "projects/engine/include/cheryl/text/layout.h", + "line": 79, + "brief": "Inspect accepted line records and glyph ranges before upload; retain the unmoved layout.", + "role": "call" + } + ], + "Text::Rendering::Updater": [ + { + "name": "CE::Text::Rendering::Updater::request", + "label": "request(Text::Rendering::Request next)", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 53, + "brief": "Own the latest desired content/options on the serialized simulation owner. Work starts only when update() runs.", + "role": "call" + }, + { + "name": "CE::Text::Rendering::Updater::update", + "label": "update()", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 54, + "brief": "Advance and adopt without waiting, retaining the previous complete generation on failure; inspect failure() and explicitly retry() unchanged failures.", + "role": "call" + }, + { + "name": "CE::Text::Rendering::Updater::current", + "label": "current(): const Text::Rendering::Generation [shared]", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 57, + "brief": "Retain the last complete generation for frame preparation. The empty constructor has no current generation until an update succeeds.", + "role": "call" + } + ] + }, + "operation_labels": { + "Text::layout_text()": "Text::layout_text(const Text::Font& font, text, const Text::LayoutOptions& options): Text::ShapedText", + "Text::Rendering::prepare()": "Text::Rendering::prepare(Text::ShapedText layout, Text::Rendering::RasterOptions options): Text::Rendering::Prepared", + "Text::Rendering::upload()": "Text::Rendering::upload(Text::Rendering::Prepared prepared, Rendering::Resources::Provider& provider, const Rendering::Resources::Material material [optional shared]): Text::Rendering::Generation", + "Rendering::Submission::resolve_text()": "Rendering::Submission::resolve_text(const Text::Rendering::Generation& text, const Rendering::DrawStyle2D& style, const Rendering::Submission::Context2D& context): Rendering::DrawPacket2D[]" + }, + "namespace": "Text" + }, + { + "id": "gui-application", + "system": "gui", + "view": "application", + "title": "GUI \u00b7 application-owned sessions and publication", + "question": "What does application code own when it selects a toolkit?", + "edges": [ + { + "source": "@ui", + "target": "UI::TGUI::Session", + "optional": true, + "evidence": [ + { + "file": "docs/site/engine-systems/gui.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@ui", + "target": "UI::RmlUi::Session", + "optional": true, + "evidence": [ + { + "file": "docs/site/engine-systems/gui.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@ui", + "target": "Input::Events::Record", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/gui.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@ui", + "target": "Input::Routing::KeyboardPolicy", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/gui.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@ui", + "target": "Text::Font", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/gui.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "UI::TGUI::Session", + "target": "UI::TGUI::RecordedScene", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 104, + "member": "CE::UI::TGUI::Session::record", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 104, + "member": "CE::UI::TGUI::Session::record", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 104, + "member": "CE::UI::TGUI::Session::record", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 456, + "member": "CE::UI::TGUI::Session::record", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 456, + "member": "CE::UI::TGUI::Session::record", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 463, + "member": "CE::UI::TGUI::Session::record", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "UI::RmlUi::Session", + "target": "UI::RmlUi::RecordedScene", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 102, + "member": "CE::UI::RmlUi::Session::record", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 102, + "member": "CE::UI::RmlUi::Session::record", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 102, + "member": "CE::UI::RmlUi::Session::record", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 513, + "member": "CE::UI::RmlUi::Session::record", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 513, + "member": "CE::UI::RmlUi::Session::record", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 521, + "member": "CE::UI::RmlUi::Session::record", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "@init", + "target": "UI::TGUI::SceneUploader", + "optional": true, + "evidence": [ + { + "file": "docs/site/engine-systems/gui.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@init", + "target": "UI::RmlUi::SceneUploader", + "optional": true, + "evidence": [ + { + "file": "docs/site/engine-systems/gui.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "UI::TGUI::SceneUploader", + "target": "UI::TGUI::Scene", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 61, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 65, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 61, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 60, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 36 + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "UI::RmlUi::Scene", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 56, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 60, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 56, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 55, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 36 + }, + { + "source": "@prepare", + "target": "UI::TGUI::Scene", + "optional": true, + "evidence": [ + { + "file": "docs/site/engine-systems/gui.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@prepare", + "target": "UI::RmlUi::Scene", + "optional": true, + "evidence": [ + { + "file": "docs/site/engine-systems/gui.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "UI::TGUI::Scene", + "target": "Rendering::FrameWriter", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 43, + "member": "CE::UI::TGUI::Scene::write", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "UI::RmlUi::Scene", + "target": "Rendering::FrameWriter", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 39, + "member": "CE::UI::RmlUi::Scene::write", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "UI::TGUI::SceneUploader", + "target": "UI::TGUI::RecordedScene", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 61, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 65, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 4 + }, + { + "source": "@ui", + "target": "UI::TGUI::SceneUploader", + "optional": true, + "evidence": [ + { + "file": "docs/site/modules/tgui.md", + "via": "illustrative application composition: submit owned recording from UI" + } + ], + "evidence_count": 1 + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "UI::RmlUi::RecordedScene", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 56, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 60, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 4 + }, + { + "source": "@ui", + "target": "UI::RmlUi::SceneUploader", + "optional": true, + "evidence": [ + { + "file": "docs/site/modules/rmlui.md", + "via": "illustrative application composition: submit owned recording from UI" + } + ], + "evidence_count": 1 + }, + { + "source": "UI::TGUI::Session", + "target": "Input::Events::Record", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 95, + "member": "CE::UI::TGUI::Session::handle_input", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 99, + "member": "CE::UI::TGUI::Session::handle_input", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 2 + }, + { + "source": "UI::TGUI::Session", + "target": "Input::Routing::KeyboardPolicy", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 89, + "member": "CE::UI::TGUI::Session::request_keyboard_focus", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "UI::RmlUi::Session", + "target": "Input::Events::Record", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 93, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 97, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 2 + }, + { + "source": "UI::RmlUi::Session", + "target": "Input::Routing::KeyboardPolicy", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 90, + "member": "CE::UI::RmlUi::Session::request_keyboard_focus", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "Dashed choices are alternatives: select a toolkit, do not infer a shared Engine widget API. Its widgets/documents remain application-owned.", + "Application code feeds the complete input batch, selects pointer delivery, requests focus and publishes recordings. Quiesce UI calls before session closure.", + "Sessions need view and time updates. Enable automatic_view/automatic_time when constructing a Context-aware session, or call set_view()/update_time() explicitly; both options default off." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/gui.md" + ], + "nodes": [ + "@ui", + "UI::TGUI::Session", + "UI::RmlUi::Session", + "Input::Events::Record", + "Input::Routing::KeyboardPolicy", + "Text::Font", + "UI::TGUI::RecordedScene", + "UI::RmlUi::RecordedScene", + "@init", + "UI::TGUI::SceneUploader", + "UI::RmlUi::SceneUploader", + "UI::TGUI::Scene", + "UI::RmlUi::Scene", + "@prepare", + "Rendering::FrameWriter" + ], + "operations": { + "UI::TGUI::Session": [ + { + "name": "CE::UI::TGUI::Session::gui", + "label": "gui(): tgui::BackendGui&", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 72, + "brief": "Borrow the native toolkit authoring interface on the UI owner; release native references before session closure.", + "role": "call" + }, + { + "name": "CE::UI::TGUI::Session::handle_input", + "label": "handle_input(const Input::Events::Record[] records [borrowed], pointer_selected)", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 95, + "brief": "Feed the complete ordered input batch once on the UI owner; the application selects pointer delivery.", + "role": "call" + }, + { + "name": "CE::UI::TGUI::Session::request_keyboard_focus", + "label": "request_keyboard_focus(Input::Routing::KeyboardPolicy routing)", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 89, + "brief": "Request session keyboard/text routing for future input polls; native widget or element focus remains separate.", + "role": "call" + }, + { + "name": "CE::UI::TGUI::Session::record", + "label": "record(): UI::TGUI::RecordedScene", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 104, + "brief": "Create a complete owned CPU recording on the UI owner, without graphics upload.", + "role": "call" + } + ], + "UI::TGUI::SceneUploader": [ + { + "name": "CE::UI::TGUI::SceneUploader::upload", + "label": "upload(Rendering::Resources::Provider& provider, const UI::TGUI::RecordedScene& recording): UI::TGUI::Scene", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "brief": "Return an immutable scene on the provider owner using lazy builtin UI materials. A separate overload accepts explicit Materials overrides.", + "role": "call" + }, + { + "name": "CE::UI::TGUI::SceneUploader::submit", + "label": "submit(const Runtime::PlatformDispatcher::Submission& platform, UI::TGUI::RecordedScene recording): UI::TGUI::Scene [future]", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "brief": "Return a future for a complete uploaded scene through the platform submission endpoint; adopt it without blocking. A separate overload accepts explicit Materials overrides.", + "role": "call" + } + ], + "UI::TGUI::Scene": [ + { + "name": "CE::UI::TGUI::Scene::write", + "label": "write(Rendering::FrameWriter& frame)", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 43, + "brief": "Append the retained complete scene to the borrowed FrameWriter at the application-selected pass position; no toolkit or upload calls occur.", + "role": "call" + } + ], + "UI::RmlUi::Session": [ + { + "name": "CE::UI::RmlUi::Session::context", + "label": "context(): Rml::Context&", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 74, + "brief": "Borrow the native toolkit authoring interface on the UI owner; release native references before session closure.", + "role": "call" + }, + { + "name": "CE::UI::RmlUi::Session::handle_input", + "label": "handle_input(const Input::Events::Record[] records [borrowed], pointer_selected)", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 93, + "brief": "Feed the complete ordered input batch once on the UI owner; the application selects pointer delivery.", + "role": "call" + }, + { + "name": "CE::UI::RmlUi::Session::request_keyboard_focus", + "label": "request_keyboard_focus(Input::Routing::KeyboardPolicy routing)", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 90, + "brief": "Request session keyboard/text routing for future input polls; native widget or element focus remains separate.", + "role": "call" + }, + { + "name": "CE::UI::RmlUi::Session::record", + "label": "record(): UI::RmlUi::RecordedScene", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 102, + "brief": "Create a complete owned CPU recording on the UI owner, without graphics upload.", + "role": "call" + } + ], + "UI::RmlUi::SceneUploader": [ + { + "name": "CE::UI::RmlUi::SceneUploader::upload", + "label": "upload(Rendering::Resources::Provider& provider, const UI::RmlUi::RecordedScene& recording): UI::RmlUi::Scene", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "brief": "Return an immutable scene on the provider owner using lazy builtin UI materials. A separate overload accepts explicit Materials overrides.", + "role": "call" + }, + { + "name": "CE::UI::RmlUi::SceneUploader::submit", + "label": "submit(const Runtime::PlatformDispatcher::Submission& platform, UI::RmlUi::RecordedScene recording): UI::RmlUi::Scene [future]", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "brief": "Return a future for a complete uploaded scene through the platform submission endpoint; adopt it without blocking. A separate overload accepts explicit Materials overrides.", + "role": "call" + } + ], + "UI::RmlUi::Scene": [ + { + "name": "CE::UI::RmlUi::Scene::write", + "label": "write(Rendering::FrameWriter& frame)", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 39, + "brief": "Append the retained complete scene to the borrowed FrameWriter at the application-selected pass position; no toolkit or upload calls occur.", + "role": "call" + } + ] + }, + "namespace": "UI" + }, + { + "id": "audio-application", + "system": "audio", + "view": "application", + "title": "Audio \u00b7 application-owned playback and outcomes", + "question": "What remains application-owned around playback, notification pumping and shutdown?", + "edges": [ + { + "source": "@host", + "target": "Audio::Miniaudio::System", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/audio.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@load", + "target": "Audio::Clip", + "optional": true, + "evidence": [ + { + "file": "docs/site/engine-systems/audio.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@audio", + "target": "Audio::System", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/audio.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@audio", + "target": "Audio::PlaybackOptions", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/audio.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Audio::System", + "target": "Audio::Clip", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 75, + "member": "CE::Audio::System::play", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 75, + "member": "CE::Audio::System::play", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 75, + "member": "CE::Audio::System::play", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Audio::System", + "target": "Audio::Voice", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 75, + "member": "CE::Audio::System::play", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 77, + "member": "CE::Audio::System::stream", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 75, + "member": "CE::Audio::System::play", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 74, + "member": "CE::Audio::System::play", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 77, + "member": "CE::Audio::System::stream", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 76, + "member": "CE::Audio::System::stream", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "@audio", + "target": "Audio::Voice", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/audio.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Audio::Voice", + "target": "Audio::VoiceSnapshot", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 52, + "member": "CE::Audio::Voice::snapshot", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 52, + "member": "CE::Audio::Voice::snapshot", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 52, + "member": "CE::Audio::Voice::snapshot", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "@listener", + "target": "Events::EventBus", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/audio.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@listener", + "target": "Audio::PlaybackEnded", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/audio.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@init", + "target": "Audio::Miniaudio::System", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/audio.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Audio::Miniaudio::System", + "target": "Runtime::RuntimeServices", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 59, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 59, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 59, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 613, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 613, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 618, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "Audio::Miniaudio::System", + "target": "Runtime::ServiceAttachment", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 59, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 59, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 59, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 613, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 613, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 618, + "member": "CE::Audio::Miniaudio::System::pump_with", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "@init", + "target": "Runtime::ServiceAttachment", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/audio.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@cleanup", + "target": "Audio::System", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/audio.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@load", + "target": "Audio::Miniaudio::decode_file()", + "optional": false, + "evidence": [ + { + "file": "docs/site/modules/miniaudio.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Audio::Miniaudio::decode_file()", + "target": "Audio::Clip", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 26, + "member": "CE::Audio::Miniaudio::decode_file", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Audio::System", + "target": "Audio::PlaybackOptions", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 75, + "member": "CE::Audio::System::play", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 77, + "member": "CE::Audio::System::stream", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 2 + }, + { + "source": "Events::EventBus", + "target": "Events::EventBus::Publisher", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 184, + "member": "CE::Events::EventBus::publisher", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Audio::Miniaudio::System", + "target": "Events::EventBus::Publisher", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 56, + "member": "CE::Audio::Miniaudio::System::publish_events_to", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "Output lifetime is independent of Context. Keep the source file available for streaming, and close output after its producers settle.", + "Dropping a Voice handle does not stop playback. maintain()/notification pumping is explicit and does not advance the audio clock.", + "decode_file() is the ordinary whole-file loading path. Clip::from_samples() is the alternative for application-supplied PCM; stream() keeps its source file available." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/audio.md" + ], + "nodes": [ + "@host", + "Audio::Miniaudio::System", + "@load", + "Audio::Miniaudio::decode_file()", + "Audio::Clip", + "@audio", + "Audio::System", + "Audio::PlaybackOptions", + "Audio::Voice", + "Audio::VoiceSnapshot", + "@listener", + "Events::EventBus", + "Audio::PlaybackEnded", + "@init", + "Runtime::RuntimeServices", + "Runtime::ServiceAttachment", + "@cleanup", + "Events::EventBus::Publisher" + ], + "operations": { + "Audio::Miniaudio::System": [ + { + "name": "CE::Audio::Miniaudio::System::open_device", + "label": "open_device(Audio::Format output, Audio::PlaybackNotificationOptions notifications): Audio::Miniaudio::System [unique]", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 43, + "brief": "Open native output at a loading boundary; preparation may block. Playback then advances on the device clock.", + "role": "call" + }, + { + "name": "CE::Audio::Miniaudio::System::publish_events_to", + "label": "publish_events_to(Events::EventBus::Publisher publisher)", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 56, + "brief": "Select a weak event publication route on the designated notification owner before pumping.", + "role": "call" + }, + { + "name": "CE::Audio::Miniaudio::System::pump_with", + "label": "pump_with(const Runtime::RuntimeServices& services): Runtime::ServiceAttachment", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 59, + "brief": "Optionally attach notification publication to platform maintenance on its initialized owner. Retain the attachment; it neither maintains voices nor advances audio time.", + "role": "call" + }, + { + "name": "CE::Audio::Miniaudio::System::publish_notifications", + "label": "publish_notifications()", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 57, + "brief": "Publish queued outcomes on the designated notification owner, manually or for final outcomes after close(); this does not reclaim voices or advance playback.", + "role": "call" + } + ], + "Audio::Clip": [ + { + "name": "CE::Audio::Clip::from_samples", + "label": "from_samples(Audio::Format format, samples): Audio::Clip", + "file": "projects/engine/include/cheryl/audio/clip.h", + "line": 38, + "brief": "Create immutable owned interleaved float PCM without opening a device; decoding an audio file uses Miniaudio::decode_file() instead.", + "role": "call" + } + ], + "Audio::System": [ + { + "name": "CE::Audio::System::play", + "label": "play(const Audio::Clip& clip, Audio::PlaybackOptions options): Audio::Voice [shared]", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 75, + "brief": "Admit an independent voice retaining the prepared clip; dropping the voice handle does not stop playback.", + "role": "call" + }, + { + "name": "CE::Audio::System::stream", + "label": "stream(file, Audio::PlaybackOptions options): Audio::Voice [shared]", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 77, + "brief": "Open an independent streamed voice; keep its source file available through playback and restart.", + "role": "call" + }, + { + "name": "CE::Audio::System::maintain", + "label": "maintain()", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 82, + "brief": "Regularly reclaim unreferenced inactive voices; this does not advance playback or publish notifications.", + "role": "call" + }, + { + "name": "CE::Audio::System::close", + "label": "close()", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 83, + "brief": "Close output after producers settle. Pump any final notifications before removing subscriptions and releasing the system.", + "role": "call" + } + ], + "Audio::Voice": [ + { + "name": "CE::Audio::Voice::snapshot", + "label": "snapshot(): Audio::VoiceSnapshot", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 52, + "brief": "Copy synchronized voice state and current-generation outcome; surviving handles report Closed after output closure.", + "role": "call" + }, + { + "name": "CE::Audio::Voice::pause", + "label": "pause()", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 53, + "brief": "Pause a playing voice while preserving its playback position.", + "role": "call" + }, + { + "name": "CE::Audio::Voice::resume", + "label": "resume()", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 54, + "brief": "Resume a paused voice; stopped or finished voices need restart() instead.", + "role": "call" + }, + { + "name": "CE::Audio::Voice::stop", + "label": "stop()", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 55, + "brief": "Stop terminally until restart(); dropping the voice handle alone does not stop active playback.", + "role": "call" + } + ], + "Events::EventBus": [ + { + "name": "CE::Events::EventBus::register_listener", + "label": "register_listener(const Events::EventChannel& event, Events::EventChannel::Callback callback, Events::EventBus::Delivery delivery, Events::EventBus::ErrorHandler errors): Events::EventBus::Registration", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 206, + "brief": "Register a persistent typed PlaybackEnded listener. Immediate delivery runs on the notification-pump owner; use an explicit delivery adapter for simulation-owned state.", + "role": "call" + }, + { + "name": "CE::Events::EventBus::publisher", + "label": "publisher(): Events::EventBus::Publisher", + "role": "call", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 184, + "brief": "Acquire the weak notification endpoint to pass to publish_events_to(); it does not retain the bus." + } + ], + "Runtime::ServiceAttachment": [ + { + "name": "CE::Runtime::ServiceAttachment::reset", + "label": "reset()", + "file": "projects/engine/include/cheryl/core/engine/runtime-services.h", + "line": 60, + "brief": "Detach optional notification pumping after final outcomes are handled; keep the attachment alive while pumping is wanted.", + "role": "call" + } + ] + }, + "operation_labels": { + "Audio::Miniaudio::decode_file()": "Audio::Miniaudio::decode_file(file, Audio::Miniaudio::DecodeOptions options): Audio::Clip" + }, + "namespace": "Audio" + }, + { + "id": "events-application", + "system": "events", + "view": "application", + "title": "Events \u00b7 application registration and delivery", + "question": "What must producers, listeners and cleanup retain or choose?", + "edges": [ + { + "source": "@producer", + "target": "Events::EventChannel", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/events.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@producer", + "target": "Events::EventBus::Publisher", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/events.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@listener", + "target": "Events::EventBus::Registration", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/events.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Events::EventBus", + "target": "Events::EventChannel", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 206, + "member": "CE::Events::EventBus::register_listener", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 227, + "member": "CE::Events::EventBus::dispatch", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 233, + "member": "CE::Events::EventBus::notify", + "access": "public", + "via": "member declaration" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 207, + "member": "CE::Events::EventBus::register_listener", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 208, + "member": "CE::Events::EventBus::register_listener", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 227, + "member": "CE::Events::EventBus::dispatch", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 234, + "member": "CE::Events::EventBus::notify", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 207, + "member": "CE::Events::EventBus::register_listener", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 8 + }, + { + "source": "Events::EventBus", + "target": "Events::EventBus::Registration", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 194, + "member": "CE::Events::EventBus::register_listener", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 206, + "member": "CE::Events::EventBus::register_listener", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 246, + "member": "CE::Events::EventBus::unregister_listener", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 248, + "member": "CE::Events::EventBus::wait_for_listener", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 249, + "member": "CE::Events::EventBus::unregister_and_wait", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 260, + "member": "CE::Events::EventBus::register_channel", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 194, + "member": "CE::Events::EventBus::register_listener", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 194, + "member": "CE::Events::EventBus::register_listener", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 34 + }, + { + "source": "Events::EventBus", + "target": "@callback", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/events.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@listener", + "target": "Events::simulation_delivery()", + "optional": true, + "evidence": [ + { + "file": "docs/site/engine-systems/events.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Events::simulation_delivery()", + "target": "Runtime::SimulationDispatcher::Submission", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 16, + "member": "Events::simulation_delivery()", + "via": "operation signature" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 16, + "member": "CE::Events::simulation_delivery", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 2 + }, + { + "source": "Events::simulation_delivery()", + "target": "Events::EventBus::Delivery", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 16, + "member": "Events::simulation_delivery()", + "via": "operation signature" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 16, + "member": "CE::Events::simulation_delivery", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 2 + }, + { + "source": "Events::EventBus", + "target": "Events::EventBus::Delivery", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 194, + "member": "Events::EventBus::register_listener", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 206, + "member": "Events::EventBus::register_listener", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 260, + "member": "Events::EventBus::register_channel", + "via": "API declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 209, + "member": "CE::Events::EventBus::register_listener", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 4 + }, + { + "source": "@listener", + "target": "Events::EventBus::ErrorHandler", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/events.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@cleanup", + "target": "Events::EventBus::Registration", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/events.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@listener", + "target": "Runtime::Context", + "optional": false, + "evidence": [ + { + "file": "docs/site/runtime/event-delivery.md", + "line": 137, + "via": "documented application listener setup through engine.events().register_listener(...)" + } + ], + "evidence_count": 1 + }, + { + "source": "Runtime::Context", + "target": "Events::EventBus", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 116, + "member": "CE::Runtime::Context::events", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Events::EventBus", + "target": "Events::EventBus::Publisher", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 184, + "member": "CE::Events::EventBus::publisher", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "@cleanup", + "target": "Events::EventBus", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 249, + "member": "CE::Events::EventBus::unregister_and_wait", + "access": "public", + "via": "explicit application cleanup operation; waits for already-entered callbacks" + } + ], + "evidence_count": 1 + }, + { + "source": "Events::EventBus::Publisher", + "target": "Events::EventChannel", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 168, + "member": "CE::Events::EventBus::Publisher::notify", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Events::EventBus::Publisher", + "target": "Events::EventBus::ErrorHandler", + "optional": true, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 170, + "member": "CE::Events::EventBus::Publisher::notify", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Events::EventBus", + "target": "Events::EventBus::ErrorHandler", + "optional": true, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 210, + "member": "CE::Events::EventBus::register_listener", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "Immediate callbacks run on the producer. Explicit delivery selects a different owner and requires an independent error sink.", + "Dropping Registration does not unregister. Invalidate and settle entered callbacks before destroying borrowed targets.", + "Context::events() is the normal route for runtime-connected engine observations. An application-owned bus is also valid; the selected producer determines which bus to subscribe to. Registration::disconnect() invalidates entry without waiting; unregister_and_wait() additionally settles entered callbacks, except self-wait rejects." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/events.md" + ], + "nodes": [ + "@producer", + "Events::EventChannel", + "Events::EventBus::Publisher", + "@listener", + "Runtime::Context", + "Events::EventBus", + "Events::EventBus::Registration", + "@callback", + "Events::simulation_delivery()", + "Runtime::SimulationDispatcher::Submission", + "Events::EventBus::Delivery", + "Events::EventBus::ErrorHandler", + "@cleanup" + ], + "operations": { + "Runtime::Context": [ + { + "name": "CE::Runtime::Context::events", + "label": "events(): Events::EventBus&", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 116, + "brief": "Borrow this context's bus for its runtime-connected producer observations; raw GLFW resize uses a separate default bus.", + "role": "call" + } + ], + "Events::EventBus::Publisher": [ + { + "name": "CE::Events::EventBus::Publisher::notify", + "label": "notify(const Events::EventChannel& event, Value&& payload, const Events::EventBus::ErrorHandler& errors)", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 167, + "brief": "Publish an exact-type observation without propagating notification failures; this weak endpoint retains no bus or Context.", + "role": "call" + } + ], + "Events::EventBus": [ + { + "name": "CE::Events::EventBus::register_listener", + "label": "register_listener(const Events::EventChannel& event, Events::EventChannel::Callback callback, Events::EventBus::Delivery delivery, Events::EventBus::ErrorHandler errors): Events::EventBus::Registration", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 206, + "brief": "Register an exact channel and retain the persistent registration; optional queued delivery requires an independent, nonthrowing error sink.", + "role": "call" + }, + { + "name": "CE::Events::EventBus::publisher", + "label": "publisher(): Events::EventBus::Publisher", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 184, + "brief": "Acquire a copyable weak route for producers without retaining the bus facade.", + "role": "call" + }, + { + "name": "CE::Events::EventBus::unregister_and_wait", + "label": "unregister_and_wait(const Events::EventBus::Registration& registration)", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 249, + "brief": "Invalidate and wait for entered callbacks before destroying borrowed targets; never wait from that listener's own callback.", + "role": "call" + } + ], + "Events::EventBus::Registration": [ + { + "name": "CE::Events::EventBus::Registration::disconnect", + "label": "disconnect()", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 143, + "brief": "Prevent fresh callback entry without borrowing the bus; already-entered callbacks may still finish.", + "role": "call" + } + ] + }, + "operation_labels": { + "Events::simulation_delivery()": "Events::simulation_delivery(Runtime::SimulationDispatcher::Submission endpoint): Events::EventBus::Delivery" + }, + "namespace": "Events" + }, + { + "id": "dispatch-application", + "system": "dispatch", + "view": "application", + "title": "Dispatch \u00b7 application owner requests", + "question": "Which endpoint should a producer save, and which owner executes its work?", + "edges": [ + { + "source": "@init", + "target": "Runtime::Context", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/dispatch.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Runtime::Context", + "target": "Runtime::PlatformDispatcher", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 73, + "member": "CE::Runtime::Context::platform_dispatcher_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 115, + "member": "CE::Runtime::Context::platform_dispatcher", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 73, + "member": "CE::Runtime::Context::platform_dispatcher_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 115, + "member": "CE::Runtime::Context::platform_dispatcher", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 115, + "member": "CE::Runtime::Context::platform_dispatcher", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 91, + "member": "CE::Runtime::Context::begin_session", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "Runtime::PlatformDispatcher", + "target": "Runtime::PlatformDispatcher::Submission", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 95, + "member": "CE::Runtime::PlatformDispatcher::submission", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 95, + "member": "CE::Runtime::PlatformDispatcher::submission", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 95, + "member": "CE::Runtime::PlatformDispatcher::submission", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 98, + "member": "CE::Runtime::PlatformDispatcher::submit", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 4 + }, + { + "source": "@producer", + "target": "Runtime::PlatformDispatcher::Submission", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/dispatch.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@native", + "target": "Runtime::Context", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/dispatch.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Runtime::Context", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 68, + "member": "CE::Runtime::Context::resources_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 93, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 112, + "member": "CE::Runtime::Context::resources", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 68, + "member": "CE::Runtime::Context::resources_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 89, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 89, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 97, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 21 + }, + { + "source": "@host", + "target": "Runtime::GameRuntime", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/dispatch.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Runtime::GameRuntime", + "target": "Runtime::SimulationDispatcher", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 84, + "member": "CE::Runtime::GameRuntime::simulation_dispatcher_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 119, + "member": "CE::Runtime::GameRuntime::simulation_dispatcher", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 84, + "member": "CE::Runtime::GameRuntime::simulation_dispatcher_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 119, + "member": "CE::Runtime::GameRuntime::simulation_dispatcher", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 119, + "member": "CE::Runtime::GameRuntime::simulation_dispatcher", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 468, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 472, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 552, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 15 + }, + { + "source": "Runtime::SimulationDispatcher", + "target": "Runtime::SimulationDispatcher::Submission", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 95, + "member": "CE::Runtime::SimulationDispatcher::submission", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 95, + "member": "CE::Runtime::SimulationDispatcher::submission", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 95, + "member": "CE::Runtime::SimulationDispatcher::submission", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 97, + "member": "CE::Runtime::SimulationDispatcher::submit", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 4 + }, + { + "source": "@producer", + "target": "Runtime::SimulationDispatcher::Submission", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/dispatch.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@sim", + "target": "Game::TickContext", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/dispatch.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "Save copyable endpoints during initialization. Platform work receives Context; simulation requests drain at update boundaries.", + "Read retained futures only when ready. Never wait on the owner required to finish the request; endpoint lifetime does not extend borrowed captures." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/dispatch.md" + ], + "nodes": [ + "@init", + "Runtime::Context", + "Runtime::PlatformDispatcher", + "Runtime::PlatformDispatcher::Submission", + "@producer", + "@native", + "Rendering::Resources::Provider", + "@host", + "Runtime::GameRuntime", + "Runtime::SimulationDispatcher", + "Runtime::SimulationDispatcher::Submission", + "@sim", + "Game::TickContext" + ], + "operations": { + "Runtime::Context": [ + { + "name": "CE::Runtime::Context::platform_dispatcher", + "label": "platform_dispatcher(): Runtime::PlatformDispatcher&", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 115, + "brief": "Acquire the runtime-controlled platform queue facade while Context lives.", + "role": "call" + }, + { + "name": "CE::Runtime::Context::resources", + "label": "resources(): Rendering::Resources::Provider&", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 112, + "brief": "Borrow compatible resource upload services inside platform-owned work with the graphics context current.", + "role": "call" + } + ], + "Runtime::PlatformDispatcher": [ + { + "name": "CE::Runtime::PlatformDispatcher::submission", + "label": "submission(): Runtime::PlatformDispatcher::Submission", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 95, + "brief": "Retain a copyable endpoint while the dispatcher lives; acquiring it does not open queue acceptance.", + "role": "call" + } + ], + "Runtime::PlatformDispatcher::Submission": [ + { + "name": "CE::Runtime::PlatformDispatcher::Submission::submit", + "label": "submit(Work&& work): Result [future]", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 63, + "brief": "Queue an owned callable work(Runtime::Context&) for later platform execution. Result is the callable return type, delivered through its future; retain captures and never wait from the owner needed to execute the work.", + "role": "call" + } + ], + "Rendering::Resources::Provider": [ + { + "name": "CE::Rendering::Resources::Provider::create_image", + "label": "create_image(const Assets::Images::Decoded& image): Rendering::Resources::Image [shared]", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 66, + "brief": "Upload owned decoded pixels on the backend's graphics/loading owner; the returned immutable image retains no input buffer.", + "role": "call" + }, + { + "name": "CE::Rendering::Resources::Provider::upload_geometry", + "label": "upload_geometry(const Rendering::Geometry::Vertex2D[] vertices [borrowed], Rendering::Geometry::Topology topology): Rendering::Geometry::Buffer2D [shared]", + "file": "projects/engine/include/cheryl/assets/resources/resource-provider.h", + "line": 77, + "brief": "Upload transient vertices on the backend owner and retain the returned geometry handle. The API also accepts a Vertex2DColor collection or a shared Vertex2D buffer plus vertex_count. Diagram collection/result notation omits standard-library ownership wrappers.", + "role": "call" + } + ], + "Runtime::GameRuntime": [ + { + "name": "CE::Runtime::GameRuntime::simulation_dispatcher", + "label": "simulation_dispatcher(): Runtime::SimulationDispatcher&", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 119, + "brief": "Acquire the runtime-controlled simulation queue before producers need to post owner work.", + "role": "call" + } + ], + "Runtime::SimulationDispatcher": [ + { + "name": "CE::Runtime::SimulationDispatcher::submission", + "label": "submission(): Runtime::SimulationDispatcher::Submission", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 95, + "brief": "Retain a producer endpoint independent of dispatcher lifetime; runtime opens/closes the session queue.", + "role": "call" + } + ], + "Runtime::SimulationDispatcher::Submission": [ + { + "name": "CE::Runtime::SimulationDispatcher::Submission::submit", + "label": "submit(Work&& work): Result [future]", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 63, + "brief": "Queue an owned callable work() for a simulation update boundary. Result is the callable return type, delivered through its future; never wait from simulation.", + "role": "call" + } + ] + }, + "namespace": "Runtime" + }, + { + "id": "workers-application", + "system": "workers", + "view": "application", + "title": "Workers \u00b7 application CPU preparation", + "question": "What capacity does the host choose, and what does a preparation producer retain?", + "edges": [ + { + "source": "@host", + "target": "Runtime::ExecutionOptions", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/workers.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Runtime::ExecutionOptions", + "target": "Workers::Pool", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 49, + "member": "CE::Runtime::ExecutionOptions::shared_pool", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 49, + "member": "CE::Runtime::ExecutionOptions::shared_pool", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "@host", + "target": "Runtime::Context", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/workers.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Runtime::Context", + "target": "Workers::Group", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 78, + "member": "CE::Runtime::Context::worker_groups_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 78, + "member": "CE::Runtime::Context::worker_groups_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 115, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 115, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 118, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 23 + }, + { + "source": "Runtime::Context", + "target": "Workers::Pool", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 76, + "member": "CE::Runtime::Context::owned_worker_factory_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 77, + "member": "CE::Runtime::Context::owned_workers_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 76, + "member": "CE::Runtime::Context::owned_worker_factory_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 77, + "member": "CE::Runtime::Context::owned_workers_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 116, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 122, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 125, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 126, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 16 + }, + { + "source": "@cpu", + "target": "Workers::Group", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/workers.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Workers::Group", + "target": "Workers::GroupOptions", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 260, + "member": "CE::Workers::Group::report_diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 1 + }, + { + "source": "Workers::GroupOptions", + "target": "Workers::CpuPolicy", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 83, + "member": "CE::Workers::GroupOptions::cpu", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 83, + "member": "CE::Workers::GroupOptions::cpu", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "@cpu", + "target": "Assets::Loading::Loader", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/workers.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@cpu", + "target": "Runtime::PlatformDispatcher::Submission", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/workers.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@cleanup", + "target": "Workers::Group", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/workers.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Workers::Pool", + "target": "Workers::Group", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 200, + "member": "CE::Workers::Pool::make_group", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Workers::Pool", + "target": "Workers::GroupOptions", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 200, + "member": "CE::Workers::Pool::make_group", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Runtime::Context", + "target": "Workers::GroupOptions", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "The preparation role supplies its own owned job inputs and may post a graphics follow-up. That dependency belongs to the job, not Pool.", + "A group cap does not create more physical workers. Context closes its tracked groups; an injected pool can continue serving unrelated groups." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/workers.md" + ], + "nodes": [ + "@host", + "Runtime::ExecutionOptions", + "Workers::Pool", + "Runtime::Context", + "Workers::Group", + "@cpu", + "Workers::GroupOptions", + "Workers::CpuPolicy", + "Assets::Loading::Loader", + "Runtime::PlatformDispatcher::Submission", + "@cleanup" + ], + "operations": { + "Workers::Pool": [ + { + "name": "CE::Workers::Pool::make_group", + "label": "make_group(Workers::GroupOptions options): Workers::Group", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 200, + "brief": "Create a standalone service workload on this shared physical pool; use Context::make_worker_group() for runtime-tracked work.", + "role": "call" + }, + { + "name": "CE::Workers::Pool::shutdown", + "label": "shutdown()", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 209, + "brief": "Close, drain and join application-owned physical capacity after dependent owners can progress; never call from a worker in this pool.", + "role": "call" + } + ], + "Runtime::Context": [ + { + "name": "CE::Runtime::Context::make_worker_group", + "label": "make_worker_group(Workers::GroupOptions options): Workers::Group", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "brief": "Create a runtime-tracked CPU group on lazy owned or configured shared capacity; Runtime settles these groups before game resources are released.", + "role": "call" + } + ], + "Workers::Group": [ + { + "name": "CE::Workers::Group::submit", + "label": "submit(Work&& work): Result [future]", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 140, + "brief": "Queue an owned CPU-only callable work(). Result is the callable return type, delivered through its future; retain captures and arrange owner-affine follow-up explicitly.", + "role": "call" + }, + { + "name": "CE::Workers::Group::close", + "label": "close()", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 165, + "brief": "Reject new submissions without cancelling accepted jobs; Runtime also closes its tracked groups during teardown.", + "role": "call" + }, + { + "name": "CE::Workers::Group::drain", + "label": "drain()", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 166, + "brief": "After close(), wait for accepted jobs and captures; never on the same pool or while blocking an owner needed by a job. Runtime settles tracked groups after quiesce().", + "role": "call" + } + ], + "Assets::Loading::Loader": [ + { + "name": "CE::Assets::Loading::Loader::prepare", + "label": "prepare(): Assets::Loading::Prepared", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 112, + "brief": "Prepare owned asset data on CPU without publishing caches; upload later on the serialized loading/graphics owner.", + "role": "call" + } + ], + "Runtime::PlatformDispatcher::Submission": [ + { + "name": "CE::Runtime::PlatformDispatcher::Submission::submit", + "label": "submit(Work&& work): Result [future]", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 63, + "brief": "Queue an owned callable work(Runtime::Context&) for later platform execution. Result is the callable return type, delivered through its future; retain captures and never wait from the owner needed to execute the work.", + "role": "call" + } + ] + }, + "namespace": "Workers" + }, + { + "id": "memory-application", + "system": "memory", + "view": "application", + "title": "Memory \u00b7 application object storage", + "question": "What are the ordinary pooled path and the explicit-reservation alternative?", + "edges": [ + { + "source": "@storage", + "target": "Memory::Objects::Pool", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/memory.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@storage", + "target": "Memory::Objects::Reservation", + "optional": true, + "evidence": [ + { + "file": "docs/site/engine-systems/memory.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Memory::Objects::Reservation", + "target": "@objects", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/memory.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@world", + "target": "@objects", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/memory.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@cleanup", + "target": "Memory::Objects::Pool", + "optional": true, + "evidence": [ + { + "file": "docs/site/engine-systems/memory.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Memory::Objects::Pool", + "target": "@objects", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/resources/objects/pool.h", + "via": "pooled construction of caller-selected T", + "line": 51, + "member": "CE::Memory::Objects::Pool::retrieve_objects" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "Start with pooled retained handles. Reservations are for explicitly selected construction with an allocator that satisfies the release contract.", + "Final handles retain release state; this neither synchronizes object contents nor authorizes use of a destroyed pool facade." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/memory.md" + ], + "nodes": [ + "@storage", + "Memory::Objects::Pool", + "Memory::Objects::Reservation", + "@objects", + "@world", + "@cleanup" + ], + "operations": { + "Memory::Objects::Pool": [ + { + "name": "CE::State::Singleton::get", + "label": "get(): Memory::Objects::Pool&", + "file": "projects/engine/include/cheryl/templates/singleton.h", + "line": 64, + "brief": "Borrow the default per-T pool singleton. T is the application-selected object type; the inherited accessor returns this Pool specialization.", + "role": "call" + }, + { + "name": "CE::Memory::Objects::Pool::retrieve_objects", + "label": "retrieve_objects(N, args...): T[] [shared elements]", + "file": "projects/engine/include/cheryl/core/resources/objects/pool.h", + "line": 51, + "brief": "Construct N objects and return independent shared owners; T[] abridges a collection of shared-owned T objects, not a contiguous returned array.", + "role": "call" + }, + { + "name": "CE::Memory::Objects::Pool::cull", + "label": "cull(age)", + "file": "projects/engine/include/cheryl/core/resources/objects/pool.h", + "line": 57, + "brief": "Queue complete free owners old enough for release; age is a duration in minutes and does not forcibly free checked-out ranges.", + "role": "call" + }, + { + "name": "CE::Memory::Objects::Pool::release_culled", + "label": "release_culled()", + "file": "projects/engine/include/cheryl/core/resources/objects/pool.h", + "line": 58, + "brief": "Finish optional culling by releasing still-complete queued backing owners; live handles remain valid.", + "role": "call" + } + ], + "Memory::Objects::Reservation": [ + { + "name": "CE::Memory::Objects::Reservation::Reservation", + "label": "Reservation(count, Allocator allocator)", + "file": "projects/engine/include/cheryl/core/resources/objects/object-reservation.hpp", + "line": 77, + "brief": "Reserve unconstructed slots with an allocator satisfying ReservationAllocator; the default allocator is Memory::Allocators::ObjectPoolAllocator.", + "role": "call" + }, + { + "name": "CE::Memory::Objects::Reservation::emplace", + "label": "emplace(index, args...): T [shared]", + "file": "projects/engine/include/cheryl/core/resources/objects/object-reservation.hpp", + "line": 102, + "brief": "Construct one unclaimed slot and return a shared owner of T. The result can outlive the reservation/pool facade; this abridged signature hides the shared-ownership wrapper.", + "role": "call" + }, + { + "name": "CE::Memory::Objects::Reservation::remaining_ranges", + "label": "remaining_ranges(): const Memory::Block[]& [borrowed]", + "file": "projects/engine/include/cheryl/core/resources/objects/object-reservation.hpp", + "line": 99, + "brief": "Borrow the remaining unclaimed block collection. Emplace, move or destruction can change/invalidate it; [] abridges the container, not a built-in array type.", + "role": "call" + } + ] + }, + "namespace": "Memory" + }, + { + "id": "state-application", + "system": "state", + "view": "application", + "title": "State \u00b7 application state choices", + "question": "Which value/observation contract matches an application component?", + "edges": [ + { + "source": "@model", + "target": "State::StateTracker", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/state.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@model", + "target": "State::StateMachine", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/state.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@writer", + "target": "State::VersionedVariable", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/state.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@writer", + "target": "State::ObservedVariable", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/state.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "State::VersionedVariable", + "target": "State::VersionedSnapshot", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/templates/versioned-variable.h", + "line": 51, + "member": "CE::State::VersionedVariable::snapshot", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/templates/versioned-variable.h", + "line": 66, + "member": "CE::State::VersionedVariable::wait_for_change", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 2 + }, + { + "source": "@reader", + "target": "State::VersionedSnapshot", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/state.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "State::ObservedVariable", + "target": "@hooks", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/state.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "State::StateMachine", + "target": "@hooks", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/state.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@reader", + "target": "State::VersionedVariable", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/templates/versioned-variable.h", + "via": "public application entry point", + "line": 51 + } + ], + "evidence_count": 1 + }, + { + "source": "@reader", + "target": "State::ObservedVariable", + "optional": true, + "evidence": [ + { + "file": "projects/engine/include/cheryl/templates/observed-variables.h", + "via": "public application entry point", + "line": 50 + } + ], + "evidence_count": 1 + }, + { + "source": "State::ObservedVariable", + "target": "State::VersionedSnapshot", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/templates/observed-variables.h", + "line": 50, + "member": "CE::State::ObservedVariable::snapshot", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "These are alternatives, not mandatory layers. Trackers/machines need caller synchronization; a versioned snapshot copies value and revision together.", + "Observers run on setter threads. Publication coalesces latest values and is not an event history or owner assignment." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/state.md" + ], + "nodes": [ + "@model", + "State::StateTracker", + "State::StateMachine", + "@writer", + "State::VersionedVariable", + "State::ObservedVariable", + "State::VersionedSnapshot", + "@reader", + "@hooks" + ], + "operations": { + "State::StateTracker": [ + { + "name": "CE::State::StateTracker::update", + "label": "update(T next)", + "file": "projects/engine/include/cheryl/templates/state-tracker.h", + "line": 20, + "brief": "Shift the current sample to previous and assign next. T is the caller-selected template sample type.", + "role": "call" + }, + { + "name": "CE::State::StateTracker::changed", + "label": "changed()", + "file": "projects/engine/include/cheryl/templates/state-tracker.h", + "line": 27, + "brief": "Compare current and previous samples; this is not a latched unread-change flag.", + "role": "call" + }, + { + "name": "CE::State::StateTracker::delta", + "label": "delta(): decltype(current() - previous())", + "file": "projects/engine/include/cheryl/templates/state-tracker.h", + "line": 33, + "brief": "Return current minus previous. The subtraction expression determines the result type; it is not necessarily T, and its units/ownership follow that expression.", + "role": "call" + } + ], + "State::StateMachine": [ + { + "name": "CE::State::StateMachine::add_transition", + "label": "add_transition(State from, Trigger trigger, State to, State::StateMachine::Guard guard, State::StateMachine::Action action)", + "file": "projects/engine/include/cheryl/templates/state-machine.h", + "line": 38, + "brief": "Append a rule; State and Trigger are caller-selected template types, while Guard and Action are this machine specialization's public callback aliases. Empty guard/action are permitted.", + "role": "call" + }, + { + "name": "CE::State::StateMachine::trigger", + "label": "trigger(const Trigger& event)", + "file": "projects/engine/include/cheryl/templates/state-machine.h", + "line": 47, + "brief": "Execute the first eligible transition and report whether one ran. Trigger is the caller-selected template event type.", + "role": "call" + }, + { + "name": "CE::State::StateMachine::state", + "label": "state(): const State&", + "file": "projects/engine/include/cheryl/templates/state-machine.h", + "line": 45, + "brief": "Borrow the current caller-selected State value; State here is the template argument, not the Engine State namespace. Later transitions assign the same storage.", + "role": "call" + } + ], + "State::VersionedVariable": [ + { + "name": "CE::State::VersionedVariable::set", + "label": "set(T next)", + "file": "projects/engine/include/cheryl/templates/versioned-variable.h", + "line": 40, + "brief": "Publish a changed value and advance its revision; equal values leave the revision unchanged.", + "role": "call" + }, + { + "name": "CE::State::VersionedVariable::snapshot", + "label": "snapshot(): State::VersionedSnapshot", + "file": "projects/engine/include/cheryl/templates/versioned-variable.h", + "line": 51, + "brief": "Copy the value and revision together under one storage lock.", + "role": "call" + }, + { + "name": "CE::State::VersionedVariable::wait_for_change", + "label": "wait_for_change(since): State::VersionedSnapshot", + "file": "projects/engine/include/cheryl/templates/versioned-variable.h", + "line": 66, + "brief": "Wait for a revision different from a retained one; updates coalesce and there is no timeout/cancellation.", + "role": "call" + } + ], + "State::ObservedVariable": [ + { + "name": "CE::State::ObservedVariable::ObservedVariable", + "label": "ObservedVariable(T value, State::ObservedVariable::Callback[] observers)", + "file": "projects/engine/include/cheryl/templates/observed-variables.h", + "line": 32, + "brief": "Store the caller-selected T value and a fixed array of Observers owned callback objects. Each Callback receives const T& synchronously; construction invokes none.", + "role": "call" + }, + { + "name": "CE::State::ObservedVariable::set", + "label": "set(T value)", + "file": "projects/engine/include/cheryl/templates/observed-variables.h", + "line": 40, + "brief": "Publish then invoke observers on the setter's thread, even for equal values; callbacks protect their own targets.", + "role": "call" + }, + { + "name": "CE::State::ObservedVariable::snapshot", + "label": "snapshot(): State::VersionedSnapshot", + "file": "projects/engine/include/cheryl/templates/observed-variables.h", + "line": 50, + "brief": "Copy a consistent stored value/revision pair without invoking observers.", + "role": "call" + } + ] + }, + "namespace": "State" + }, + { + "id": "logging-application", + "system": "logging", + "view": "application", + "title": "Logging \u00b7 application configuration and writers", + "question": "What should happen before producers start and after they finish?", + "edges": [ + { + "source": "@host", + "target": "Logging::ProcessLifetime", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/logging.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@host", + "target": "Logging::Config", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/logging.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@host", + "target": "Logging::Log", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/logging.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Logging::Config", + "target": "Logging::Profile", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 48, + "member": "CE::Logging::Config::for_logger", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 50, + "member": "CE::Logging::Config::for_logger", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 50, + "member": "CE::Logging::Config::for_logger", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 54, + "member": "CE::Logging::Config::for_logger", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 58, + "member": "CE::Logging::Config::for_logger", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 62, + "member": "CE::Logging::Config::for_logger", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 71, + "member": "CE::Logging::Config::for_logger", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 7 + }, + { + "source": "Logging::Config", + "target": "Logging::Level", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 43, + "member": "CE::Logging::Config::logger_level", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 44, + "member": "CE::Logging::Config::file_level", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 45, + "member": "CE::Logging::Config::console_level", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 43, + "member": "CE::Logging::Config::logger_level", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 44, + "member": "CE::Logging::Config::file_level", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 45, + "member": "CE::Logging::Config::console_level", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 55, + "member": "CE::Logging::Config::for_logger", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 56, + "member": "CE::Logging::Config::for_logger", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 14 + }, + { + "source": "Logging::Config", + "target": "Logging::OverflowPolicy", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 46, + "member": "CE::Logging::Config::overflow_policy", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 46, + "member": "CE::Logging::Config::overflow_policy", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Logging::Log", + "target": "Logging::FileEventHandlers", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 119, + "member": "CE::Logging::Log::Log", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 119, + "member": "CE::Logging::Log::Log", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 119, + "member": "CE::Logging::Log::Log", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "@producer", + "target": "Logging::Logger", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/logging.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Logging::Logger", + "target": "Logging::Log", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 19, + "member": "CE::Logging::Logger::published_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 35, + "member": "CE::Logging::Logger::get", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 43, + "member": "CE::Logging::Logger::get", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 51, + "member": "CE::Logging::Logger::initialize", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 19, + "member": "CE::Logging::Logger::published_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 21, + "member": "CE::Logging::Logger::Facade::value", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 35, + "member": "CE::Logging::Logger::get", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 35, + "member": "CE::Logging::Logger::get", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 12 + }, + { + "source": "@report", + "target": "Logging::LogHandle", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/logging.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Logging::LogHandle", + "target": "Logging::QueueStats", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 67, + "member": "CE::Logging::LogHandle::shared_queue_stats", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 67, + "member": "CE::Logging::LogHandle::shared_queue_stats", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 67, + "member": "CE::Logging::LogHandle::shared_queue_stats", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/logging/log.cpp", + "line": 157, + "member": "CE::Logging::LogHandle::shared_queue_stats", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/logging/log.cpp", + "line": 157, + "member": "CE::Logging::LogHandle::shared_queue_stats", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/logging/log.cpp", + "line": 158, + "member": "CE::Logging::LogHandle::shared_queue_stats", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "@cleanup", + "target": "Logging::Log", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/logging.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@host", + "target": "Logging::Logger", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "via": "public application entry point", + "line": 51 + } + ], + "evidence_count": 1 + }, + { + "source": "Logging::Log", + "target": "Logging::Config", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 119, + "member": "CE::Logging::Log::Log", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Logging::Logger", + "target": "Logging::FileEventHandlers", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 51, + "member": "CE::Logging::Logger::initialize", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Logging::Logger", + "target": "Logging::Config", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 51, + "member": "CE::Logging::Logger::initialize", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + }, + { + "source": "Logging::LogHandle", + "target": "Logging::BackendStats", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 68, + "member": "CE::Logging::LogHandle::backend_stats", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "Configure used categories before writers; stop/settle producers before flushing and closing. Keep file callback targets alive through final closure.", + "Runtime thresholds cannot restore stripped calls. Logging failure handling and required operation failures remain separate." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/logging.md" + ], + "nodes": [ + "@host", + "Logging::ProcessLifetime", + "Logging::Config", + "Logging::Log", + "Logging::Profile", + "Logging::Level", + "Logging::OverflowPolicy", + "Logging::FileEventHandlers", + "@producer", + "Logging::Logger", + "@report", + "Logging::LogHandle", + "Logging::QueueStats", + "@cleanup", + "Logging::BackendStats" + ], + "operations": { + "Logging::ProcessLifetime": [ + { + "name": "CE::Logging::ProcessLifetime::ProcessLifetime", + "label": "ProcessLifetime()", + "file": "projects/engine/include/cheryl/core/logging/process-lifetime.h", + "line": 18, + "brief": "Keep an outer guard alive until all logging callers and diagnostic readers finish; final shutdown is terminal.", + "role": "call" + } + ], + "Logging::Config": [ + { + "name": "CE::Logging::Config::for_logger", + "label": "for_logger(name, Logging::Profile profile): Logging::Config", + "file": "projects/engine/include/cheryl/core/logging/config.h", + "line": 48, + "brief": "Choose the category/profile runtime threshold defaults before its first explicit initialization.", + "role": "call" + } + ], + "Logging::Log": [ + { + "name": "CE::Logging::Log::Log", + "label": "Log(Logging::FileEventHandlers handlers, Logging::Config configuration)", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 119, + "brief": "Construct an independently owned named logger with handlers and configuration; requires a live process guard.", + "role": "call" + }, + { + "name": "CE::Logging::LogHandle::info", + "label": "info(format, arguments...)", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 92, + "brief": "Inherited writer: format and submit an informational record only when compiled and admitted by runtime filtering.", + "role": "call" + }, + { + "name": "CE::Logging::LogHandle::close", + "label": "close(timeout)", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 73, + "brief": "Drain accepted records and close on the application owner; timeout is a milliseconds duration, and zero waits without a timeout. Keep callers stopped through close.", + "role": "call" + } + ], + "Logging::Logger": [ + { + "name": "CE::Logging::Logger::initialize", + "label": "initialize(Logging::FileEventHandlers handlers, Logging::Config config): Logging::Log&", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 51, + "brief": "Explicitly configure the shared named category before lazy access or writers; repeated explicit initialization rejects.", + "role": "call" + }, + { + "name": "CE::Logging::Logger::info", + "label": "info(fmt, args...)", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 121, + "brief": "Emit an informational record through the shared named category.", + "role": "call" + }, + { + "name": "CE::Logging::Logger::warn", + "label": "warn(fmt, args...)", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 129, + "brief": "Emit a warning through the shared named category; keep the original operation error authoritative.", + "role": "call" + } + ], + "Logging::LogHandle": [ + { + "name": "CE::Logging::LogHandle::shared_queue_stats", + "label": "shared_queue_stats(): Logging::QueueStats", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 67, + "brief": "Copy observations of the shared Cheryl queue; counts include flush requests and are not a completion barrier.", + "role": "call" + }, + { + "name": "CE::Logging::LogHandle::backend_stats", + "label": "backend_stats(): Logging::BackendStats", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 68, + "brief": "Observe backend health and destination activity for this logger.", + "role": "call" + }, + { + "name": "CE::Logging::LogHandle::report_diagnostics", + "label": "report_diagnostics()", + "file": "projects/engine/include/cheryl/core/logging/log.h", + "line": 69, + "brief": "Emit an explicit logging summary outside containing subsystem locks and callbacks.", + "role": "call" + } + ] + }, + "namespace": "Logging" + }, + { + "id": "diagnostics-application", + "system": "diagnostics", + "view": "application", + "title": "Diagnostics \u00b7 application observations", + "question": "What can a live observer inspect, and what requires runtime completion?", + "edges": [ + { + "source": "@report", + "target": "Runtime::GameRuntime", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/diagnostics.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Runtime::GameRuntime", + "target": "Runtime::RuntimeStats", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 66, + "member": "CE::Runtime::GameRuntime::diagnostics_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 111, + "member": "CE::Runtime::GameRuntime::diagnostics", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 66, + "member": "CE::Runtime::GameRuntime::diagnostics_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 111, + "member": "CE::Runtime::GameRuntime::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 111, + "member": "CE::Runtime::GameRuntime::diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 112, + "member": "CE::Runtime::GameRuntime::run", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 143, + "member": "CE::Runtime::GameRuntime::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 143, + "member": "CE::Runtime::GameRuntime::diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 49 + }, + { + "source": "@report", + "target": "Runtime::RuntimeStats", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/diagnostics.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@live", + "target": "Workers::Group", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/diagnostics.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Workers::Group", + "target": "Workers::GroupStatus", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 168, + "member": "CE::Workers::Group::status", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 168, + "member": "CE::Workers::Group::status", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 168, + "member": "CE::Workers::Group::status", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 190, + "member": "CE::Workers::Group::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 194, + "member": "CE::Workers::Group::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 195, + "member": "CE::Workers::Group::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 196, + "member": "CE::Workers::Group::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 207, + "member": "CE::Workers::Group::close", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 23 + }, + { + "source": "@live", + "target": "Runtime::PlatformDispatcher", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/diagnostics.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Runtime::PlatformDispatcher", + "target": "Diagnostics::DispatchStats", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 103, + "member": "CE::Runtime::PlatformDispatcher::diagnostics", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 41, + "member": "CE::Runtime::PlatformDispatcher::State::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 103, + "member": "CE::Runtime::PlatformDispatcher::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 103, + "member": "CE::Runtime::PlatformDispatcher::diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/platform-dispatcher.cpp", + "line": 22, + "member": "CE::Runtime::PlatformDispatcher::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/platform-dispatcher.cpp", + "line": 23, + "member": "CE::Runtime::PlatformDispatcher::enqueue", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/engine/platform-dispatcher.cpp", + "line": 36, + "member": "CE::Runtime::PlatformDispatcher::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/platform-dispatcher.cpp", + "line": 36, + "member": "CE::Runtime::PlatformDispatcher::diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 21 + }, + { + "source": "@live", + "target": "Events::EventBus", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/diagnostics.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Events::EventBus", + "target": "Events::EventStats", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 253, + "member": "CE::Events::EventBus::diagnostics", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 253, + "member": "CE::Events::EventBus::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 253, + "member": "CE::Events::EventBus::diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/subsystems/event-bus.cpp", + "line": 319, + "member": "CE::Events::EventBus::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/subsystems/event-bus.cpp", + "line": 319, + "member": "CE::Events::EventBus::diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/subsystems/event-bus.cpp", + "line": 329, + "member": "CE::Events::EventBus::report_diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/subsystems/event-bus.cpp", + "line": 330, + "member": "CE::Events::EventBus::report_diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 7 + }, + { + "source": "@report", + "target": "Logging::Logger", + "optional": true, + "evidence": [ + { + "file": "docs/site/engine-systems/diagnostics.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Workers::Group", + "target": "Workers::GroupPolicy", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 169, + "member": "CE::Workers::Group::policy", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "Query GameRuntime::diagnostics() before run or after synchronizing with its return/throw, never as a live runtime sampler.", + "Other snapshots have owner-specific rules. Empty queues/counters do not prove completion, budgets or hardware behavior." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/diagnostics.md" + ], + "nodes": [ + "@report", + "Runtime::GameRuntime", + "Runtime::RuntimeStats", + "@live", + "Workers::Group", + "Workers::GroupStatus", + "Runtime::PlatformDispatcher", + "Diagnostics::DispatchStats", + "Events::EventBus", + "Events::EventStats", + "Logging::Logger", + "Workers::GroupPolicy" + ], + "operations": { + "Runtime::GameRuntime": [ + { + "name": "CE::Runtime::GameRuntime::diagnostics", + "label": "diagnostics(): Runtime::RuntimeStats", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 111, + "brief": "Copy unstarted or completed-session observations after synchronization with run(); live sampling rejects.", + "role": "call" + } + ], + "Workers::Group": [ + { + "name": "CE::Workers::Group::status", + "label": "status(): Workers::GroupStatus", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 168, + "brief": "Copy workload activity and failure counters; a snapshot is not a completion barrier.", + "role": "call" + }, + { + "name": "CE::Workers::Group::policy", + "label": "policy(): Workers::GroupPolicy", + "file": "projects/engine/include/cheryl/core/engine/worker-pool.h", + "line": 169, + "brief": "Copy the requested/effective policy and preferred fallback recorded at creation.", + "role": "call" + } + ], + "Runtime::PlatformDispatcher": [ + { + "name": "CE::Runtime::PlatformDispatcher::diagnostics", + "label": "diagnostics(): Diagnostics::DispatchStats", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 103, + "brief": "Observe pending, running and cumulative activity; pending alone excludes detached work.", + "role": "call" + } + ], + "Events::EventBus": [ + { + "name": "CE::Events::EventBus::diagnostics", + "label": "diagnostics(): Events::EventStats", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 253, + "brief": "Copy registration and delivery counters; closure and counters do not drain callbacks.", + "role": "call" + } + ], + "Logging::Logger": [ + { + "name": "CE::Logging::Logger::info", + "label": "info(fmt, args...)", + "file": "projects/engine/include/cheryl/core/logging/logger.h", + "line": 121, + "brief": "Format the application's copied observations outside owner locks/native callbacks; filtering may suppress the report.", + "role": "call" + } + ] + } + }, + { + "id": "utilities-application", + "system": "utilities", + "view": "application", + "title": "Utilities \u00b7 application-local helpers", + "question": "Which local values and functions can a tool or game use without a runtime service?", + "edges": [ + { + "source": "@tools", + "target": "Files::FileIndex", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/utilities.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@tools", + "target": "Time::DeltaTime", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/utilities.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@bits", + "target": "Math::BitArray", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/utilities.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@storage", + "target": "Memory::adjust_length()", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/utilities.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Memory::adjust_length()", + "target": "Memory::FitPolicy", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/math/fit.h", + "line": 19, + "member": "Memory::adjust_length()", + "via": "operation signature" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/math/fit.h", + "line": 19, + "member": "CE::Memory::adjust_length", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 2 + }, + { + "source": "@placement", + "target": "Rendering::Geometry::Anchor", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/utilities.md", + "via": "illustrative application geometry preparation" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Geometry::Anchor", + "target": "Rendering::Geometry::Pivot", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/math/anchor.h", + "via": "public geometry operation argument", + "line": 47, + "member": "CE::Rendering::Geometry::Anchor::MakeQuad" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/math/anchor.h", + "line": 46, + "member": "CE::Rendering::Geometry::Anchor::MakeQuad", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/math/anchor.h", + "line": 55, + "member": "CE::Rendering::Geometry::Anchor::MakeQuadStrip", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 3 + }, + { + "source": "@config", + "target": "Text::Numbers::parse_integers()", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/math/string-numbers.h", + "via": "explicit application parser choice", + "line": 51, + "member": "CE::Text::Numbers::parse_integers" + } + ], + "evidence_count": 1 + }, + { + "source": "@config", + "target": "Text::Numbers::parse_floats()", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/math/string-numbers.h", + "via": "explicit application parser choice", + "line": 24, + "member": "CE::Text::Numbers::parse_floats" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "File discovery can block and its results do not refresh automatically. Bit proxies and borrowed index views retain their own lifetime/synchronization rules.", + "Use tick.delta_seconds for game simulation; DeltaTime measures an independent local elapsed interval." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/utilities.md" + ], + "nodes": [ + "@tools", + "Files::FileIndex", + "Time::DeltaTime", + "@bits", + "Math::BitArray", + "@placement", + "Rendering::Geometry::Anchor", + "Rendering::Geometry::Pivot", + "@storage", + "Memory::adjust_length()", + "Memory::FitPolicy", + "@config", + "Text::Numbers::parse_integers()", + "Text::Numbers::parse_floats()" + ], + "operations": { + "Files::FileIndex": [ + { + "name": "CE::Files::FileIndex::FileIndex", + "label": "FileIndex(const Files::Path& root_path)", + "file": "projects/engine/include/cheryl/core/resources/fileio/file-mgr.h", + "line": 23, + "brief": "Scan an explicit root. Files::Path is the public alias of the filesystem path type spelled in the constructor declaration; construct a new index to rescan visited roots.", + "role": "call" + }, + { + "name": "CE::Files::FileIndex::get_files_of_type", + "label": "get_files_of_type(Files::Extension extension): const Files::Path[]& [borrowed]", + "file": "projects/engine/include/cheryl/core/resources/fileio/file-mgr.h", + "line": 27, + "brief": "Borrow an extension bucket using a leading dot; a missing extension inserts an empty bucket.", + "role": "call" + }, + { + "name": "CE::Files::FileIndex::search_directory", + "label": "search_directory(const Files::Path& directory)", + "file": "projects/engine/include/cheryl/core/resources/fileio/file-mgr.h", + "line": 25, + "brief": "Add an unvisited tree; Files::Path is the public alias of the filesystem path type spelled in the declaration. Scanning can block and fail after partial indexing.", + "role": "call" + } + ], + "Time::DeltaTime": [ + { + "name": "CE::Time::DeltaTime::operator()", + "label": "operator()()", + "file": "projects/engine/include/cheryl/templates/delta.h", + "line": 34, + "brief": "Return elapsed seconds and start the next interval at the same clock sample; use runtime tick delta for gameplay.", + "role": "call" + }, + { + "name": "CE::Time::DeltaTime::elapsed", + "label": "elapsed(): Time::DeltaTime::Duration", + "file": "projects/engine/include/cheryl/templates/delta.h", + "line": 41, + "brief": "Observe the selected clock's native Duration through the public alias; this call does not begin a new interval.", + "role": "call" + } + ], + "Math::BitArray": [ + { + "name": "CE::Math::BitArray::operator[]", + "label": "operator[](index): Math::BitArray::Reference", + "file": "projects/engine/include/cheryl/math/binary.h", + "line": 79, + "brief": "The shown mutable overload returns the public write-through Reference alias, which borrows this array; the const overload returns a scalar bit value. Use a valid index and caller synchronization.", + "role": "call" + }, + { + "name": "CE::Math::BitArray::words", + "label": "words(): Math::BitArray::word_type[] [borrowed]", + "file": "projects/engine/include/cheryl/math/binary.h", + "line": 82, + "brief": "The shown mutable overload returns a borrowed view of packed word_type elements; the const overload borrows const words. The public alias hides the implementation-selected native integer type, and [] abridges the span wrapper.", + "role": "call" + } + ], + "Rendering::Geometry::Anchor": [ + { + "name": "CE::Rendering::Geometry::Anchor::MakeQuad", + "label": "MakeQuad(Rendering::Geometry::Pivot pivot, texture_width, texture_height, width, height, x0, y0): Rendering::Geometry::Quad", + "file": "projects/engine/include/cheryl/math/anchor.h", + "line": 46, + "brief": "Create six CPU vertices from a normalized pivot and pixel rectangle; require finite valid pivot and nonzero texture size.", + "role": "call" + }, + { + "name": "CE::Rendering::Geometry::Anchor::MakeQuadStrip", + "label": "MakeQuadStrip(Rendering::Geometry::Pivot pivot, texture_width, texture_height, width, height, x0, y0): Rendering::Geometry::QuadTriangleStrip", + "file": "projects/engine/include/cheryl/math/anchor.h", + "line": 55, + "brief": "Create a four-vertex CPU quad strip using the same coordinate and dimension requirements.", + "role": "call" + } + ] + }, + "operation_labels": { + "Memory::adjust_length()": "Memory::adjust_length(length, Memory::FitPolicy fit, gb, gf)", + "Text::Numbers::parse_integers()": "Text::Numbers::parse_integers(str): Text::Numbers::NumberVariant", + "Text::Numbers::parse_floats()": "Text::Numbers::parse_floats(str)" + } + }, + { + "id": "runtime-focus-assembly", + "system": "runtime", + "view": "focus-assembly", + "title": "Runtime \u00b7 simple adapter assembly", + "question": "What does Context assemble before any game session starts?", + "edges": [ + { + "source": "Runtime::GameRuntime", + "target": "Runtime::Context", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 61, + "member": "CE::Runtime::GameRuntime::engine_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 94, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 61, + "member": "CE::Runtime::GameRuntime::engine_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 95, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 95, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 70, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 70, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 107, + "member": "CE::Runtime::GameRuntime::run", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 79 + }, + { + "source": "Runtime::GameRuntime", + "target": "Game::AbstractGame", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 62, + "member": "CE::Runtime::GameRuntime::game_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 94, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 62, + "member": "CE::Runtime::GameRuntime::game_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 96, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 96, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 71, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 71, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 486, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 21 + }, + { + "source": "Runtime::Context", + "target": "Platform::DisplaySystem", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 65, + "member": "CE::Runtime::Context::display_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 93, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 108, + "member": "CE::Runtime::Context::display", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 65, + "member": "CE::Runtime::Context::display_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 86, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 86, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 94, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 21 + }, + { + "source": "Runtime::Context", + "target": "Input::System", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 69, + "member": "CE::Runtime::Context::owned_input_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 70, + "member": "CE::Runtime::Context::input_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 93, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 113, + "member": "CE::Runtime::Context::input", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 69, + "member": "CE::Runtime::Context::owned_input_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 70, + "member": "CE::Runtime::Context::input_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 90, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 24 + }, + { + "source": "Runtime::Context", + "target": "Rendering::PresentationSurface", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 66, + "member": "CE::Runtime::Context::surface_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 93, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 110, + "member": "CE::Runtime::Context::surface", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 66, + "member": "CE::Runtime::Context::surface_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 87, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 87, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 95, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 20 + }, + { + "source": "Runtime::Context", + "target": "Rendering::Renderer", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 67, + "member": "CE::Runtime::Context::renderer_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 93, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 111, + "member": "CE::Runtime::Context::renderer", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 67, + "member": "CE::Runtime::Context::renderer_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 88, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 88, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 96, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 21 + }, + { + "source": "Runtime::Context", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 68, + "member": "CE::Runtime::Context::resources_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 93, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 112, + "member": "CE::Runtime::Context::resources", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 68, + "member": "CE::Runtime::Context::resources_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 89, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 89, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 97, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 21 + } + ], + "notes": [ + "The supplied native/OpenGL factory creates a compatible assembly. These are contracts, not a requirement for five separate libraries.", + "Input may be owned or explicitly borrowed. Context and game must survive GameRuntime use and cleanup." + ], + "level": "simple", + "refs": [ + "docs/site/engine-systems/runtime.md" + ], + "nodes": [ + "Runtime::GameRuntime", + "Runtime::Context", + "Game::AbstractGame", + "Platform::DisplaySystem", + "Input::System", + "Rendering::PresentationSurface", + "Rendering::Renderer", + "Rendering::Resources::Provider" + ], + "namespace": "Runtime" + }, + { + "id": "runtime-focus-frame", + "system": "runtime", + "view": "focus-frame", + "title": "Runtime \u00b7 practical tick and frame boundary", + "question": "Which values cross simulation and rendering without exposing mutable game state?", + "edges": [ + { + "source": "Runtime::GameRuntime", + "target": "Game::AbstractGame", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 62, + "member": "CE::Runtime::GameRuntime::game_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 94, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 62, + "member": "CE::Runtime::GameRuntime::game_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 96, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 96, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 71, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 71, + "member": "CE::Runtime::GameRuntime::GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 486, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 21 + }, + { + "source": "Game::AbstractGame", + "target": "Game::TickContext", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 33, + "member": "CE::Game::AbstractGame::update", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 33, + "member": "CE::Game::AbstractGame::update", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 33, + "member": "CE::Game::AbstractGame::update", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Game::TickContext", + "target": "Input::Tick", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 23, + "member": "CE::Game::TickContext::input", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 23, + "member": "CE::Game::TickContext::input", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 31, + "member": "CE::Game::TickContext::observed_seconds", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/tick-context.cpp", + "line": 19, + "member": "CE::Game::TickContext::button_simulation_seconds", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 4 + }, + { + "source": "Game::AbstractGame", + "target": "Rendering::FrameWriter", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 34, + "member": "CE::Game::AbstractGame::prepare_render_frame", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 34, + "member": "CE::Game::AbstractGame::prepare_render_frame", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 34, + "member": "CE::Game::AbstractGame::prepare_render_frame", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Rendering::FrameWriter", + "target": "Rendering::Frame", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 121, + "member": "CE::Rendering::FrameWriter::frame_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 124, + "member": "CE::Rendering::FrameWriter::FrameWriter", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 121, + "member": "CE::Rendering::FrameWriter::frame_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 124, + "member": "CE::Rendering::FrameWriter::FrameWriter", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 124, + "member": "CE::Rendering::FrameWriter::FrameWriter", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 126, + "member": "CE::Rendering::FrameWriter::FrameWriter", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 130, + "member": "CE::Rendering::FrameWriter::reserve_passes", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 138, + "member": "CE::Rendering::FrameWriter::begin_pass", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 13 + }, + { + "source": "Rendering::FrameWriter", + "target": "Rendering::PassWriter", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 132, + "member": "CE::Rendering::FrameWriter::begin_pass", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 132, + "member": "CE::Rendering::FrameWriter::begin_pass", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 132, + "member": "CE::Rendering::FrameWriter::begin_pass", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 147, + "member": "CE::Rendering::FrameWriter::begin_pass", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 4 + }, + { + "source": "Rendering::Frame", + "target": "Rendering::Pass", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 40, + "member": "CE::Rendering::Frame::passes_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 52, + "member": "CE::Rendering::Frame::passes", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 40, + "member": "CE::Rendering::Frame::passes_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 52, + "member": "CE::Rendering::Frame::passes", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 52, + "member": "CE::Rendering::Frame::passes", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 58, + "member": "CE::Rendering::Frame::recycle", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 59, + "member": "CE::Rendering::Frame::recycle", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 7 + }, + { + "source": "Rendering::Pass", + "target": "Rendering::DrawPacket2D", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 24, + "member": "CE::Rendering::Pass::draws", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 24, + "member": "CE::Rendering::Pass::draws", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Runtime::GameRuntime", + "target": "Rendering::Renderer", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 401, + "member": "CE::Runtime::GameRuntime::bind_renderer_maintenance", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 445, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 460, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 500, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 594, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 595, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 640, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 647, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 16 + }, + { + "source": "Rendering::Renderer", + "target": "Rendering::Frame", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/renderer.h", + "line": 46, + "member": "CE::Rendering::Renderer::render", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/renderer.h", + "line": 46, + "member": "CE::Rendering::Renderer::render", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/renderer.h", + "line": 46, + "member": "CE::Rendering::Renderer::render", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + } + ], + "notes": [ + "The game reads a copied tick and prepares a borrowed frame slot; the renderer consumes a completed immutable frame.", + "Sequential and concurrent execution use the same API boundary. Frame preparation may be skipped without skipping authoritative updates." + ], + "level": "practical", + "refs": [ + "docs/site/engine-systems/runtime.md" + ], + "nodes": [ + "Runtime::GameRuntime", + "Game::AbstractGame", + "Game::TickContext", + "Input::Tick", + "Rendering::FrameWriter", + "Rendering::Frame", + "Rendering::PassWriter", + "Rendering::Pass", + "Rendering::DrawPacket2D", + "Rendering::Renderer" + ], + "namespace": "Runtime" + }, + { + "id": "runtime-focus-shutdown", + "system": "runtime", + "view": "focus-shutdown", + "title": "Runtime \u00b7 practical shutdown and observation", + "question": "How do ordinary requests, host decisions and retained outcomes fit around runtime?", + "edges": [ + { + "source": "@producer", + "target": "Runtime::ShutdownEndpoint", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/runtime.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Runtime::ShutdownEndpoint", + "target": "Runtime::ShutdownTicket", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/shutdown.h", + "line": 76, + "member": "CE::Runtime::ShutdownEndpoint::request", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/shutdown.h", + "line": 76, + "member": "CE::Runtime::ShutdownEndpoint::request", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/shutdown.h", + "line": 76, + "member": "CE::Runtime::ShutdownEndpoint::request", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/game-framework/shutdown.cpp", + "line": 481, + "member": "CE::Runtime::ShutdownEndpoint::request", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/shutdown.cpp", + "line": 481, + "member": "CE::Runtime::ShutdownEndpoint::request", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/shutdown.cpp", + "line": 484, + "member": "CE::Runtime::ShutdownEndpoint::request", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "@host", + "target": "Runtime::ShutdownPolicy", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/runtime.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@host", + "target": "Runtime::ShutdownDecision", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/runtime.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@host", + "target": "Runtime::ShutdownRequest", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/runtime.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Runtime::GameRuntime", + "target": "Runtime::ShutdownEndpoint", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 113, + "member": "CE::Runtime::GameRuntime::shutdown", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 113, + "member": "CE::Runtime::GameRuntime::shutdown", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 113, + "member": "CE::Runtime::GameRuntime::shutdown", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 149, + "member": "CE::Runtime::GameRuntime::shutdown", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 149, + "member": "CE::Runtime::GameRuntime::shutdown", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 150, + "member": "CE::Runtime::GameRuntime::shutdown", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "Runtime::GameRuntime", + "target": "Runtime::ShutdownPolicy", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 117, + "member": "CE::Runtime::GameRuntime::set_shutdown_policy", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 117, + "member": "CE::Runtime::GameRuntime::set_shutdown_policy", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 117, + "member": "CE::Runtime::GameRuntime::set_shutdown_policy", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 153, + "member": "CE::Runtime::GameRuntime::set_shutdown_policy", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 153, + "member": "CE::Runtime::GameRuntime::set_shutdown_policy", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 164, + "member": "CE::Runtime::GameRuntime::set_shutdown_policy", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "Runtime::GameRuntime", + "target": "Runtime::RuntimeLifecycle", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 88, + "member": "CE::Runtime::GameRuntime::lifecycle_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 112, + "member": "CE::Runtime::GameRuntime::lifecycle", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 88, + "member": "CE::Runtime::GameRuntime::lifecycle_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 112, + "member": "CE::Runtime::GameRuntime::lifecycle", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 112, + "member": "CE::Runtime::GameRuntime::lifecycle", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 93, + "member": "CE::Runtime::GameRuntime::~GameRuntime", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 110, + "member": "CE::Runtime::GameRuntime::run", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 132, + "member": "CE::Runtime::GameRuntime::run", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 16 + }, + { + "source": "@report", + "target": "Runtime::RuntimeLifecycle", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/runtime.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Runtime::RuntimeLifecycle", + "target": "Runtime::RuntimeLifecycleSnapshot", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/runtime-lifecycle.h", + "line": 151, + "member": "CE::Runtime::RuntimeLifecycle::ready", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/runtime-lifecycle.h", + "line": 152, + "member": "CE::Runtime::RuntimeLifecycle::starting", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/runtime-lifecycle.h", + "line": 153, + "member": "CE::Runtime::RuntimeLifecycle::quiescing", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/runtime-lifecycle.h", + "line": 154, + "member": "CE::Runtime::RuntimeLifecycle::pending", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/runtime-lifecycle.h", + "line": 155, + "member": "CE::Runtime::RuntimeLifecycle::stopped", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/runtime-lifecycle.h", + "line": 163, + "member": "CE::Runtime::RuntimeLifecycle::snapshot", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/runtime-lifecycle.h", + "line": 151, + "member": "CE::Runtime::RuntimeLifecycle::ready", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/game-framework/runtime-lifecycle.h", + "line": 151, + "member": "CE::Runtime::RuntimeLifecycle::ready", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 73 + } + ], + "notes": [ + "An ordinary request can be deferred/cancelled by the one host policy. stop()/failure bypass negotiation.", + "Retained lifecycle snapshots are safe observations; they do not keep the game, Context or native adapters alive." + ], + "level": "practical", + "refs": [ + "docs/site/engine-systems/runtime.md" + ], + "nodes": [ + "@producer", + "Runtime::ShutdownEndpoint", + "Runtime::ShutdownTicket", + "@host", + "Runtime::ShutdownPolicy", + "Runtime::ShutdownDecision", + "Runtime::ShutdownRequest", + "Runtime::GameRuntime", + "Runtime::RuntimeLifecycle", + "@report", + "Runtime::RuntimeLifecycleSnapshot" + ], + "namespace": "Runtime" + }, + { + "id": "input-focus-actions", + "system": "input", + "view": "focus-actions", + "title": "Input \u00b7 simple gameplay actions", + "question": "What is the smallest useful binding-and-read map?", + "edges": [ + { + "source": "@init", + "target": "Input::System", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/input.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Input::System", + "target": "Input::Actions::Bindings", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 36, + "member": "CE::Input::System::bindings", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 36, + "member": "CE::Input::System::bindings", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 36, + "member": "CE::Input::System::bindings", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 38, + "member": "CE::Input::System::action_contexts", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 39, + "member": "CE::Input::System::action_snapshot", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 17, + "member": "CE::Input::System::begin_input_poll", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 18, + "member": "CE::Input::System::begin_input_poll", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 35, + "member": "CE::Input::System::publish_input", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 8 + }, + { + "source": "Input::Actions::Bindings", + "target": "Input::Control", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/controls/input-bindings.h", + "line": 67, + "member": "CE::Input::Actions::Bindings::held_buttons_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/controls/input-bindings.h", + "line": 69, + "member": "CE::Input::Actions::Bindings::physical_axes_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-bindings.h", + "line": 93, + "member": "CE::Input::Actions::Bindings::bind_button", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-bindings.h", + "line": 97, + "member": "CE::Input::Actions::Bindings::bind_axis", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-bindings.h", + "line": 98, + "member": "CE::Input::Actions::Bindings::bind_axis", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-bindings.h", + "line": 109, + "member": "CE::Input::Actions::Bindings::on_axis", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-bindings.h", + "line": 111, + "member": "CE::Input::Actions::Bindings::on_delta", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-bindings.h", + "line": 112, + "member": "CE::Input::Actions::Bindings::on_button", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 63 + }, + { + "source": "Input::Control", + "target": "Input::KeyboardKey", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/device-binding.h", + "line": 17, + "member": "CE::Input::Control::Control", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/device-binding.h", + "line": 17, + "member": "CE::Input::Control::Control", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/device-binding.h", + "line": 17, + "member": "CE::Input::Control::Control", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Input::System", + "target": "Input::PollSnapshot", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 82, + "member": "CE::Input::System::published_poll_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 51, + "member": "CE::Input::System::poll_snapshot", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 69, + "member": "CE::Input::System::publish_input", + "access": "protected", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 90, + "member": "CE::Input::System::commit_gamepad_availability", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 51, + "member": "CE::Input::System::poll_snapshot", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 51, + "member": "CE::Input::System::poll_snapshot", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 69, + "member": "CE::Input::System::publish_input", + "access": "protected", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 68, + "member": "CE::Input::System::publish_input", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 29 + }, + { + "source": "@update", + "target": "Input::Tick", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/input.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Input::Tick", + "target": "Input::Actions::ButtonTickState", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 71, + "member": "CE::Input::Tick::button", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 94, + "member": "CE::Input::Tick::button", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 71, + "member": "CE::Input::Tick::button", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 71, + "member": "CE::Input::Tick::button", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 94, + "member": "CE::Input::Tick::button", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/tick-input.h", + "line": 94, + "member": "CE::Input::Tick::button", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/controls/tick-input.cpp", + "line": 38, + "member": "CE::Input::Tick::button", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/tick-input.cpp", + "line": 38, + "member": "CE::Input::Tick::button", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 26 + } + ], + "notes": [ + "Bind physical controls once on platform; update reads semantic button state from the tick.", + "Held-state movement and observed down-time are application policies. Keyboard positions do not provide localized text." + ], + "level": "simple", + "refs": [ + "docs/site/engine-systems/input.md" + ], + "nodes": [ + "@init", + "Input::System", + "Input::Actions::Bindings", + "Input::Control", + "Input::KeyboardKey", + "Input::PollSnapshot", + "@update", + "Input::Tick", + "Input::Actions::ButtonTickState" + ], + "namespace": "Input" + }, + { + "id": "input-focus-capture", + "system": "input", + "view": "focus-capture", + "title": "Input \u00b7 practical UI capture and focus", + "question": "Which separate contracts request records and decide their keyboard recipient?", + "edges": [ + { + "source": "@ui", + "target": "Input::System", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/input.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Input::System", + "target": "Input::Capture::Lease", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 47, + "member": "CE::Input::System::capture", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 47, + "member": "CE::Input::System::capture", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 47, + "member": "CE::Input::System::capture", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 21, + "member": "CE::Input::System::capture", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 21, + "member": "CE::Input::System::capture", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 24, + "member": "CE::Input::System::capture", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "Input::System", + "target": "Input::Routing::Manager", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 81, + "member": "CE::Input::System::routing_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 48, + "member": "CE::Input::System::routing", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 48, + "member": "CE::Input::System::routing", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 48, + "member": "CE::Input::System::routing", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 81, + "member": "CE::Input::System::routing_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 15, + "member": "CE::Input::System::begin_input_poll", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 27, + "member": "CE::Input::System::routing", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 27, + "member": "CE::Input::System::routing", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 10 + }, + { + "source": "Input::Routing::Manager", + "target": "Input::Routing::Lease", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-routing.h", + "line": 64, + "member": "CE::Input::Routing::Manager::focus", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/controls/input-routing.h", + "line": 64, + "member": "CE::Input::Routing::Manager::focus", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-routing.h", + "line": 64, + "member": "CE::Input::Routing::Manager::focus", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/controls/input-routing.cpp", + "line": 42, + "member": "CE::Input::Routing::Manager::focus", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-routing.cpp", + "line": 42, + "member": "CE::Input::Routing::Manager::focus", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-routing.cpp", + "line": 47, + "member": "CE::Input::Routing::Manager::focus", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "Input::System", + "target": "Input::Routing::KeyboardPolicy", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 17, + "member": "CE::Input::System::begin_input_poll", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 1 + }, + { + "source": "@ui", + "target": "Input::Capture::Lease", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/input.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@ui", + "target": "Input::Routing::Lease", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/input.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@ui", + "target": "Input::Events::Record", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/input.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Input::Events::Record", + "target": "Input::Events::Payload", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/controls/input-record.h", + "line": 82, + "member": "CE::Input::Events::Record::data", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/controls/input-record.h", + "line": 82, + "member": "CE::Input::Events::Record::data", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-record.h", + "line": 87, + "member": "CE::Input::Events::Record::is_text", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + } + ], + "notes": [ + "Capture requests Events/Text; a focus lease separately selects keyboard routing. Neither operation retroactively changes a captured batch.", + "Feed the full ordered batch once. Pointer selection stays application-owned and independent of keyboard focus." + ], + "level": "practical", + "refs": [ + "docs/site/engine-systems/input.md" + ], + "nodes": [ + "@ui", + "Input::System", + "Input::Capture::Lease", + "Input::Routing::Manager", + "Input::Routing::Lease", + "Input::Routing::KeyboardPolicy", + "Input::Events::Record", + "Input::Events::Payload" + ], + "namespace": "Input" + }, + { + "id": "assets-focus-loading", + "system": "assets", + "view": "focus-loading", + "title": "Assets \u00b7 simple loading and lookup", + "question": "What does ordinary application loading need before drawing a sprite?", + "edges": [ + { + "source": "@init", + "target": "Assets::Loading::Loader", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/assets.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Assets::Loading::Loader", + "target": "Assets::Loading::Prepared", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 105, + "member": "CE::Assets::Loading::Loader::prepare_selected", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 112, + "member": "CE::Assets::Loading::Loader::prepare", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 113, + "member": "CE::Assets::Loading::Loader::prepare_graphics", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 114, + "member": "CE::Assets::Loading::Loader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 105, + "member": "CE::Assets::Loading::Loader::prepare_selected", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 105, + "member": "CE::Assets::Loading::Loader::prepare_selected", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 112, + "member": "CE::Assets::Loading::Loader::prepare", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 112, + "member": "CE::Assets::Loading::Loader::prepare", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 47 + }, + { + "source": "Assets::Loading::Loader", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 114, + "member": "CE::Assets::Loading::Loader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 115, + "member": "CE::Assets::Loading::Loader::load_assets", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 114, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 114, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 115, + "member": "CE::Assets::Loading::Loader::load_assets", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 115, + "member": "CE::Assets::Loading::Loader::load_assets", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 296, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/asset-loader.cpp", + "line": 296, + "member": "CE::Assets::Loading::Loader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 11 + }, + { + "source": "@world", + "target": "Assets::Sprites::Manager", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/assets.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Assets::Sprites::Manager", + "target": "Assets::Sprites::Asset", + "optional": false, + "evidence": [ + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/sprite-mgr.h", + "line": 25, + "via": "base declaration" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/sprite-mgr.cpp", + "line": 22, + "member": "CE::Assets::Sprites::Manager::get_assets", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/sprite-mgr.cpp", + "line": 23, + "member": "CE::Assets::Sprites::Manager::get_assets", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/sprite-mgr.cpp", + "line": 25, + "member": "CE::Assets::Sprites::Manager::get_assets", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/sprite-mgr.cpp", + "line": 37, + "member": "CE::Assets::Sprites::Manager::load", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/sprite-mgr.cpp", + "line": 75, + "member": "CE::Assets::Sprites::Manager::load", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "@world", + "target": "Assets::Sprites::Asset", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/assets.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "On the platform loading owner, load_assets() prepares/uploads synchronously. Look up and validate the retained sprite before use.", + "Default materials are supplied by ordinary loading; a custom shader is not a prerequisite for this path." + ], + "level": "simple", + "refs": [ + "docs/site/engine-systems/assets.md" + ], + "nodes": [ + "@init", + "Assets::Loading::Loader", + "Assets::Loading::Prepared", + "Rendering::Resources::Provider", + "@world", + "Assets::Sprites::Manager", + "Assets::Sprites::Asset" + ], + "namespace": "Assets" + }, + { + "id": "assets-focus-playback", + "system": "assets", + "view": "focus-playback", + "title": "Assets \u00b7 practical shared sprite, separate playback", + "question": "What can be shared while each application object advances its own cursor?", + "edges": [ + { + "source": "@world", + "target": "Assets::Sprites::Asset", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/assets.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@update", + "target": "Assets::Sprites::Animation", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/assets.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Assets::Sprites::Asset", + "target": "Assets::Sprites::Animation", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/sprite.h", + "line": 59, + "member": "CE::Assets::Sprites::Asset::operator[]", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/sprite.h", + "line": 62, + "member": "CE::Assets::Sprites::Asset::animation", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/sprite.h", + "line": 59, + "member": "CE::Assets::Sprites::Asset::operator[]", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/types/2d/sprite.h", + "line": 59, + "member": "CE::Assets::Sprites::Asset::operator[]", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/sprite.h", + "line": 62, + "member": "CE::Assets::Sprites::Asset::animation", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/types/2d/sprite.h", + "line": 62, + "member": "CE::Assets::Sprites::Asset::animation", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/assets/types/2d/sprite.cpp", + "line": 111, + "member": "CE::Assets::Sprites::Asset::animation", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/assets/types/2d/sprite.cpp", + "line": 111, + "member": "CE::Assets::Sprites::Asset::animation", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 13 + }, + { + "source": "Assets::Sprites::Animation", + "target": "Assets::Sprites::Clip", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/types/2d/sprite.h", + "line": 46, + "member": "CE::Assets::Sprites::Animation::definition_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/sprite.h", + "line": 38, + "member": "CE::Assets::Sprites::Animation::definition", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/sprite.h", + "line": 44, + "member": "CE::Assets::Sprites::Animation::Animation", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/sprite.h", + "line": 38, + "member": "CE::Assets::Sprites::Animation::definition", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/types/2d/sprite.h", + "line": 38, + "member": "CE::Assets::Sprites::Animation::definition", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/types/2d/sprite.h", + "line": 40, + "member": "CE::Assets::Sprites::Animation::loops", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/sprite.h", + "line": 44, + "member": "CE::Assets::Sprites::Animation::Animation", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/types/2d/sprite.h", + "line": 44, + "member": "CE::Assets::Sprites::Animation::Animation", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 18 + }, + { + "source": "@prepare", + "target": "Rendering::Submission::resolve_sprite()", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/assets.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Assets::Sprites::Asset", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Rendering::DrawStyle2D", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Rendering::DrawPacket2D", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "A cursor retains immutable CPU clip metadata, not the asset GPU handles. Each object owns its cursor, position and behavior.", + "Advance during update; preparation copies the chosen cell/style into a packet. Tilesets provide the parallel retained-asset path." + ], + "level": "practical", + "refs": [ + "docs/site/engine-systems/assets.md" + ], + "nodes": [ + "@world", + "Assets::Sprites::Asset", + "@update", + "Assets::Sprites::Animation", + "Assets::Sprites::Clip", + "@prepare", + "Rendering::Submission::resolve_sprite()", + "Rendering::DrawStyle2D", + "Rendering::DrawPacket2D" + ], + "namespace": "Assets" + }, + { + "id": "rendering-focus-playback", + "system": "rendering", + "view": "focus-playback", + "title": "Rendering \u00b7 simple prepared frame", + "question": "What is the small immutable structure playback actually sees?", + "edges": [ + { + "source": "Rendering::Renderer", + "target": "Rendering::Frame", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/renderer.h", + "line": 46, + "member": "CE::Rendering::Renderer::render", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/renderer.h", + "line": 46, + "member": "CE::Rendering::Renderer::render", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/renderer.h", + "line": 46, + "member": "CE::Rendering::Renderer::render", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Rendering::Frame", + "target": "Rendering::Pass", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 40, + "member": "CE::Rendering::Frame::passes_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 52, + "member": "CE::Rendering::Frame::passes", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 40, + "member": "CE::Rendering::Frame::passes_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 52, + "member": "CE::Rendering::Frame::passes", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 52, + "member": "CE::Rendering::Frame::passes", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 58, + "member": "CE::Rendering::Frame::recycle", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 59, + "member": "CE::Rendering::Frame::recycle", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 7 + }, + { + "source": "Rendering::Pass", + "target": "Rendering::DrawPacket2D", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 24, + "member": "CE::Rendering::Pass::draws", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 24, + "member": "CE::Rendering::Pass::draws", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Rendering::DrawPacket2D", + "target": "Rendering::Resources::Material", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 34, + "member": "CE::Rendering::DrawPacket2D::material", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 34, + "member": "CE::Rendering::DrawPacket2D::material", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Rendering::DrawPacket2D", + "target": "Rendering::Geometry::Buffer2D", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 33, + "member": "CE::Rendering::DrawPacket2D::geometry", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 33, + "member": "CE::Rendering::DrawPacket2D::geometry", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + } + ], + "notes": [ + "A pass owns resolved packets; each packet retains the exact resources required by later playback.", + "No live camera, animation cursor, application object or toolkit widget is needed to consume this frame." + ], + "level": "simple", + "refs": [ + "docs/site/engine-systems/rendering.md" + ], + "nodes": [ + "Rendering::Renderer", + "Rendering::Frame", + "Rendering::Pass", + "Rendering::DrawPacket2D", + "Rendering::Resources::Material", + "Rendering::Geometry::Buffer2D" + ], + "namespace": "Rendering" + }, + { + "id": "rendering-focus-sprite", + "system": "rendering", + "view": "focus-sprite", + "title": "Rendering \u00b7 practical one-sprite draw", + "question": "Which names appear in a normal prepare_render_frame() implementation?", + "edges": [ + { + "source": "@prepare", + "target": "Rendering::FrameWriter", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/rendering.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::FrameWriter", + "target": "Rendering::PassWriter", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 132, + "member": "CE::Rendering::FrameWriter::begin_pass", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 132, + "member": "CE::Rendering::FrameWriter::begin_pass", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 132, + "member": "CE::Rendering::FrameWriter::begin_pass", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 147, + "member": "CE::Rendering::FrameWriter::begin_pass", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 4 + }, + { + "source": "@prepare", + "target": "Rendering::Camera2D", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/rendering.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@prepare", + "target": "Rendering::Submission::resolve_sprite()", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/rendering.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Rendering::Submission::Context2D", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Assets::Sprites::Asset", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Rendering::DrawStyle2D", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Rendering::DrawPacket2D", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::PassWriter", + "target": "Rendering::DrawPacket2D", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 88, + "member": "CE::Rendering::PassWriter::add", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 95, + "member": "CE::Rendering::PassWriter::add", + "access": "public", + "via": "member declaration" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 76, + "member": "CE::Rendering::PassWriter::reserve_draws", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 88, + "member": "CE::Rendering::PassWriter::add", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 88, + "member": "CE::Rendering::PassWriter::add", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 91, + "member": "CE::Rendering::PassWriter::add", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 92, + "member": "CE::Rendering::PassWriter::add", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/render-frame.h", + "line": 95, + "member": "CE::Rendering::PassWriter::add", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 13 + } + ], + "notes": [ + "Copy camera matrices into a pass, resolve using pass semantics/parameters/constraints, then add the returned packet.", + "The loaded asset already retains its ordinary material. Writers and their borrowed views last for this hook only." + ], + "level": "practical", + "refs": [ + "docs/site/engine-systems/rendering.md" + ], + "nodes": [ + "@prepare", + "Rendering::FrameWriter", + "Rendering::PassWriter", + "Rendering::Camera2D", + "Rendering::Submission::resolve_sprite()", + "Rendering::Submission::Context2D", + "Assets::Sprites::Asset", + "Rendering::DrawStyle2D", + "Rendering::DrawPacket2D" + ], + "namespace": "Rendering" + }, + { + "id": "text-focus-static", + "system": "text", + "view": "focus-static", + "title": "Text \u00b7 simple static label", + "question": "What are the explicit stages for a label that rarely changes?", + "edges": [ + { + "source": "@init", + "target": "Text::FontService", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/text.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Text::FontService", + "target": "Text::Font", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 54, + "member": "CE::Text::FontService::default_font", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 55, + "member": "CE::Text::FontService::get", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 56, + "member": "CE::Text::FontService::all", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 57, + "member": "CE::Text::FontService::fallbacks", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 58, + "member": "CE::Text::FontService::choose_random", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 59, + "member": "CE::Text::FontService::choose_random", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 60, + "member": "CE::Text::FontService::restore_or_choose", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/font-service.h", + "line": 62, + "member": "CE::Text::FontService::restore_or_choose", + "access": "public", + "via": "member declaration" + } + ], + "evidence_count": 93 + }, + { + "source": "Text::layout_text()", + "target": "Text::Font", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/text/layout.h", + "line": 84, + "member": "Text::layout_text()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Text::layout_text()", + "target": "Text::ShapedText", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/text/layout.h", + "line": 83, + "member": "Text::layout_text()", + "via": "operation signature" + }, + { + "file": "projects/engine/include/cheryl/text/layout.h", + "line": 84, + "member": "Text::layout_text()", + "via": "operation signature" + } + ], + "evidence_count": 2 + }, + { + "source": "Text::Rendering::prepare()", + "target": "Text::ShapedText", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 115, + "member": "Text::Rendering::prepare()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Text::Rendering::prepare()", + "target": "Text::Rendering::Prepared", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 115, + "member": "Text::Rendering::prepare()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Text::Rendering::upload()", + "target": "Text::Rendering::Prepared", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 116, + "member": "Text::Rendering::upload()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Text::Rendering::upload()", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 116, + "member": "Text::Rendering::upload()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Text::Rendering::upload()", + "target": "Text::Rendering::Generation", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/types/2d/unicode-text.h", + "line": 116, + "member": "Text::Rendering::upload()", + "via": "operation signature" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "The dependency arrows are not a timeline. Invoke layout, prepare, then upload; retain the Generation for frame preparation.", + "The embedded default font needs no application font catalogue plumbing. Upload still requires initialized graphics." + ], + "level": "simple", + "refs": [ + "docs/site/engine-systems/text.md" + ], + "nodes": [ + "@init", + "Text::FontService", + "Text::Font", + "Text::layout_text()", + "Text::ShapedText", + "Text::Rendering::prepare()", + "Text::Rendering::Prepared", + "Text::Rendering::upload()", + "Rendering::Resources::Provider", + "Text::Rendering::Generation" + ], + "namespace": "Text" + }, + { + "id": "text-focus-updater", + "system": "text", + "view": "focus-updater", + "title": "Text \u00b7 practical changing label", + "question": "Which boundaries does Updater manage instead of application-owned futures?", + "edges": [ + { + "source": "@update", + "target": "Text::Rendering::Updater", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/text.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@update", + "target": "Text::Rendering::Request", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/text.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Text::Rendering::Updater", + "target": "Text::Rendering::Request", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 48, + "member": "CE::Text::Rendering::Updater::Updater", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 53, + "member": "CE::Text::Rendering::Updater::request", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 48, + "member": "CE::Text::Rendering::Updater::Updater", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 48, + "member": "CE::Text::Rendering::Updater::Updater", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 53, + "member": "CE::Text::Rendering::Updater::request", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 53, + "member": "CE::Text::Rendering::Updater::request", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 39, + "member": "CE::Text::Rendering::Updater::State::desired", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 43, + "member": "CE::Text::Rendering::Updater::State::active", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 30 + }, + { + "source": "Text::Rendering::Updater", + "target": "Workers::Group", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 36, + "member": "CE::Text::Rendering::Updater::State::workers", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 52, + "member": "CE::Text::Rendering::Updater::State", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 146, + "member": "CE::Text::Rendering::Updater::update", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Text::Rendering::Updater", + "target": "Runtime::PlatformDispatcher::Submission", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 37, + "member": "CE::Text::Rendering::Updater::State::platform", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 53, + "member": "CE::Text::Rendering::Updater::State", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 133, + "member": "CE::Text::Rendering::Updater::update", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 3 + }, + { + "source": "Text::Rendering::Updater", + "target": "Text::Rendering::Generation", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 57, + "member": "CE::Text::Rendering::Updater::current", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 57, + "member": "CE::Text::Rendering::Updater::current", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/text/text-updater.h", + "line": 57, + "member": "CE::Text::Rendering::Updater::current", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 46, + "member": "CE::Text::Rendering::Updater::State::uploading", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 47, + "member": "CE::Text::Rendering::Updater::State::current", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 66, + "member": "CE::Text::Rendering::Updater::fail", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 72, + "member": "CE::Text::Rendering::Updater::pending", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 83, + "member": "CE::Text::Rendering::Updater::Updater", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 18 + }, + { + "source": "@prepare", + "target": "Rendering::Submission::resolve_text()", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/text.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Submission::resolve_text()", + "target": "Text::Rendering::Generation", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 70, + "member": "Rendering::Submission::resolve_text()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Rendering::Submission::resolve_text()", + "target": "Rendering::DrawPacket2D", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 62, + "member": "Rendering::Submission::resolve_text()", + "via": "operation signature" + }, + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 70, + "member": "Rendering::Submission::resolve_text()", + "via": "operation signature" + } + ], + "evidence_count": 2 + } + ], + "notes": [ + "Updater prepares, uploads and adopts replacements while the application continues using the last successful Generation.", + "Report/retry failures according to game policy. Published frames can retain older matching text geometry and atlas generations." + ], + "level": "practical", + "refs": [ + "docs/site/engine-systems/text.md" + ], + "nodes": [ + "@update", + "Text::Rendering::Updater", + "Text::Rendering::Request", + "Workers::Group", + "Runtime::PlatformDispatcher::Submission", + "Text::Rendering::Generation", + "@prepare", + "Rendering::Submission::resolve_text()", + "Rendering::DrawPacket2D" + ], + "namespace": "Text" + }, + { + "id": "gui-focus-tgui", + "system": "gui", + "view": "focus-tgui", + "title": "GUI \u00b7 TGUI publication", + "question": "What is the retained UI path for TGUI alone?", + "edges": [ + { + "source": "@ui", + "target": "UI::TGUI::Session", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/gui.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "UI::TGUI::Session", + "target": "Input::System", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 58, + "member": "CE::UI::TGUI::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 59, + "member": "CE::UI::TGUI::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 58, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 58, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 60, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 60, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 175, + "member": "CE::UI::TGUI::Session::State::input", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 195, + "member": "CE::UI::TGUI::Session::State", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 18 + }, + { + "source": "UI::TGUI::Session", + "target": "Text::FontService", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 59, + "member": "CE::UI::TGUI::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 61, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 61, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 228, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 228, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 233, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 235, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 245, + "member": "CE::UI::TGUI::Session::Session", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 8 + }, + { + "source": "UI::TGUI::Session", + "target": "UI::TGUI::RecordedScene", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 104, + "member": "CE::UI::TGUI::Session::record", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 104, + "member": "CE::UI::TGUI::Session::record", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/session.h", + "line": 104, + "member": "CE::UI::TGUI::Session::record", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 456, + "member": "CE::UI::TGUI::Session::record", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 456, + "member": "CE::UI::TGUI::Session::record", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/session.cpp", + "line": 463, + "member": "CE::UI::TGUI::Session::record", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "UI::TGUI::SceneUploader", + "target": "UI::TGUI::RecordedScene", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 61, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 65, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 61, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 61, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 26 + }, + { + "source": "UI::TGUI::SceneUploader", + "target": "Runtime::PlatformDispatcher::Submission", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 65, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 65, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 65, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/src/scene.cpp", + "line": 169, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/scene.cpp", + "line": 169, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 12 + }, + { + "source": "UI::TGUI::SceneUploader", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 56, + "member": "CE::UI::TGUI::SceneUploader::SceneUploader", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 61, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 56, + "member": "CE::UI::TGUI::SceneUploader::SceneUploader", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 56, + "member": "CE::UI::TGUI::SceneUploader::SceneUploader", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 61, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 26 + }, + { + "source": "UI::TGUI::SceneUploader", + "target": "UI::TGUI::Scene", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 61, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 65, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 59, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 61, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 60, + "member": "CE::UI::TGUI::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 36 + }, + { + "source": "@prepare", + "target": "UI::TGUI::Scene", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/gui.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "UI::TGUI::Scene", + "target": "Rendering::PassWriter", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/scene.cpp", + "line": 186, + "member": "CE::UI::TGUI::Scene::write", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/scene.cpp", + "line": 187, + "member": "CE::UI::TGUI::Scene::write", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 2 + } + ], + "notes": [ + "Author native widgets/documents, feed input and record on the UI/simulation owner. Upload owned recordings on graphics.", + "Context-aware sessions inherit Engine fonts. Select pointer delivery/focus explicitly; stop toolkit calls before closing the session." + ], + "level": "practical", + "refs": [ + "docs/site/engine-systems/gui.md" + ], + "nodes": [ + "@ui", + "UI::TGUI::Session", + "Input::System", + "Text::FontService", + "UI::TGUI::RecordedScene", + "UI::TGUI::SceneUploader", + "Runtime::PlatformDispatcher::Submission", + "Rendering::Resources::Provider", + "UI::TGUI::Scene", + "@prepare", + "Rendering::PassWriter" + ], + "namespace": "UI" + }, + { + "id": "gui-focus-rmlui", + "system": "gui", + "view": "focus-rmlui", + "title": "GUI \u00b7 RmlUi publication", + "question": "What is the retained UI path for RmlUi alone?", + "edges": [ + { + "source": "@ui", + "target": "UI::RmlUi::Session", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/gui.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "UI::RmlUi::Session", + "target": "Input::System", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 60, + "member": "CE::UI::RmlUi::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 61, + "member": "CE::UI::RmlUi::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 60, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 60, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 62, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 62, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 162, + "member": "CE::UI::RmlUi::Session::State::input", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 186, + "member": "CE::UI::RmlUi::Session::State", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 18 + }, + { + "source": "UI::RmlUi::Session", + "target": "Text::FontService", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 61, + "member": "CE::UI::RmlUi::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 63, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 63, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 276, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 276, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 281, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 284, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 285, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 9 + }, + { + "source": "UI::RmlUi::Session", + "target": "UI::RmlUi::RecordedScene", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 102, + "member": "CE::UI::RmlUi::Session::record", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 102, + "member": "CE::UI::RmlUi::Session::record", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 102, + "member": "CE::UI::RmlUi::Session::record", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 513, + "member": "CE::UI::RmlUi::Session::record", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 513, + "member": "CE::UI::RmlUi::Session::record", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 521, + "member": "CE::UI::RmlUi::Session::record", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 6 + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "UI::RmlUi::RecordedScene", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 56, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 60, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 56, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 56, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 26 + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "Runtime::PlatformDispatcher::Submission", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 60, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 60, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 60, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 152, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 152, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 12 + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "Rendering::Resources::Provider", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 51, + "member": "CE::UI::RmlUi::SceneUploader::SceneUploader", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 56, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 51, + "member": "CE::UI::RmlUi::SceneUploader::SceneUploader", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 51, + "member": "CE::UI::RmlUi::SceneUploader::SceneUploader", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 56, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 25 + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "UI::RmlUi::Scene", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 56, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 60, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 54, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 56, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 55, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 36 + }, + { + "source": "@prepare", + "target": "UI::RmlUi::Scene", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/gui.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "UI::RmlUi::Scene", + "target": "Rendering::PassWriter", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 169, + "member": "CE::UI::RmlUi::Scene::write", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 170, + "member": "CE::UI::RmlUi::Scene::write", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 2 + } + ], + "notes": [ + "Author native widgets/documents, feed input and record on the UI/simulation owner. Upload owned recordings on graphics.", + "Context-aware sessions inherit Engine fonts. Select pointer delivery/focus explicitly; stop toolkit calls before closing the session." + ], + "level": "practical", + "refs": [ + "docs/site/engine-systems/gui.md" + ], + "nodes": [ + "@ui", + "UI::RmlUi::Session", + "Input::System", + "Text::FontService", + "UI::RmlUi::RecordedScene", + "UI::RmlUi::SceneUploader", + "Runtime::PlatformDispatcher::Submission", + "Rendering::Resources::Provider", + "UI::RmlUi::Scene", + "@prepare", + "Rendering::PassWriter" + ], + "namespace": "UI" + }, + { + "id": "events-focus-immediate", + "system": "events", + "view": "focus-immediate", + "title": "Events \u00b7 simple immediate notification", + "question": "What must a synchronous producer/listener pair actually retain?", + "edges": [ + { + "source": "@producer", + "target": "Events::EventBus", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/events.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@producer", + "target": "Events::EventChannel", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/events.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@listener", + "target": "Events::EventBus", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/events.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@listener", + "target": "Events::EventBus::Registration", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/events.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Events::EventBus", + "target": "Events::EventChannel", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 206, + "member": "CE::Events::EventBus::register_listener", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 227, + "member": "CE::Events::EventBus::dispatch", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 233, + "member": "CE::Events::EventBus::notify", + "access": "public", + "via": "member declaration" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 207, + "member": "CE::Events::EventBus::register_listener", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 208, + "member": "CE::Events::EventBus::register_listener", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 227, + "member": "CE::Events::EventBus::dispatch", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 234, + "member": "CE::Events::EventBus::notify", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 7 + }, + { + "source": "Events::EventBus", + "target": "Events::EventBus::Registration", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 194, + "member": "CE::Events::EventBus::register_listener", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 206, + "member": "CE::Events::EventBus::register_listener", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 246, + "member": "CE::Events::EventBus::unregister_listener", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 248, + "member": "CE::Events::EventBus::wait_for_listener", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 249, + "member": "CE::Events::EventBus::unregister_and_wait", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 260, + "member": "CE::Events::EventBus::register_channel", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 194, + "member": "CE::Events::EventBus::register_listener", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 194, + "member": "CE::Events::EventBus::register_listener", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 34 + }, + { + "source": "Events::EventBus", + "target": "@callback", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/events.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@cleanup", + "target": "Events::EventBus::Registration", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/events.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "The callback runs synchronously on the producer. Concurrent producers can overlap callbacks.", + "Explicitly unregister; dropping the registration identity does not remove it. Settle entered callbacks before releasing borrowed targets." + ], + "level": "simple", + "refs": [ + "docs/site/engine-systems/events.md" + ], + "nodes": [ + "@producer", + "Events::EventBus", + "Events::EventChannel", + "@listener", + "Events::EventBus::Registration", + "@callback", + "@cleanup" + ], + "namespace": "Events" + }, + { + "id": "events-focus-delivery", + "system": "events", + "view": "focus-delivery", + "title": "Events \u00b7 practical delivery choices", + "question": "What selects platform, simulation or a serial worker listener stream?", + "edges": [ + { + "source": "@listener", + "target": "Events::EventBus", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/events.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Events::EventBus", + "target": "Events::EventBus::Delivery", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 194, + "member": "Events::EventBus::register_listener", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 206, + "member": "Events::EventBus::register_listener", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/subsystems/event-bus.h", + "line": 260, + "member": "Events::EventBus::register_channel", + "via": "API declaration" + } + ], + "evidence_count": 3 + }, + { + "source": "@listener", + "target": "Events::platform_delivery()", + "optional": true, + "evidence": [ + { + "file": "docs/site/engine-systems/events.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@listener", + "target": "Events::simulation_delivery()", + "optional": true, + "evidence": [ + { + "file": "docs/site/engine-systems/events.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@listener", + "target": "Events::worker_delivery()", + "optional": true, + "evidence": [ + { + "file": "docs/site/engine-systems/events.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Events::platform_delivery()", + "target": "Runtime::PlatformDispatcher::Submission", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 15, + "member": "Events::platform_delivery()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Events::simulation_delivery()", + "target": "Runtime::SimulationDispatcher::Submission", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 16, + "member": "Events::simulation_delivery()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Events::worker_delivery()", + "target": "Workers::Group", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 19, + "member": "Events::worker_delivery()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Events::platform_delivery()", + "target": "Events::EventBus::Delivery", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 15, + "member": "Events::platform_delivery()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Events::simulation_delivery()", + "target": "Events::EventBus::Delivery", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 16, + "member": "Events::simulation_delivery()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "Events::worker_delivery()", + "target": "Events::EventBus::Delivery", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 19, + "member": "Events::worker_delivery()", + "via": "operation signature" + } + ], + "evidence_count": 1 + }, + { + "source": "@listener", + "target": "Events::EventBus::ErrorHandler", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/events.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "Choose one optional transport per registration. The bus does not find a thread automatically.", + "Copies of one worker transport share a serial FIFO stream; different transports can run concurrently. Keep an independent error sink alive." + ], + "level": "practical", + "refs": [ + "docs/site/engine-systems/events.md" + ], + "nodes": [ + "@listener", + "Events::EventBus", + "Events::EventBus::Delivery", + "Events::platform_delivery()", + "Events::simulation_delivery()", + "Events::worker_delivery()", + "Runtime::PlatformDispatcher::Submission", + "Runtime::SimulationDispatcher::Submission", + "Workers::Group", + "Events::EventBus::ErrorHandler" + ], + "namespace": "Events" + }, + { + "id": "workers-focus-groups", + "system": "workers", + "view": "focus-groups", + "title": "Workers \u00b7 simple Context-owned groups", + "question": "How does a preparation producer get shared CPU capacity through Context?", + "edges": [ + { + "source": "@host", + "target": "Runtime::ExecutionOptions", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/workers.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Runtime::Context", + "target": "Runtime::ExecutionOptions", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 74, + "member": "CE::Runtime::Context::execution_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 93, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 74, + "member": "CE::Runtime::Context::execution_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 91, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 91, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 99, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 99, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 18 + }, + { + "source": "Runtime::Context", + "target": "Workers::Pool", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 76, + "member": "CE::Runtime::Context::owned_worker_factory_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 77, + "member": "CE::Runtime::Context::owned_workers_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 76, + "member": "CE::Runtime::Context::owned_worker_factory_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 77, + "member": "CE::Runtime::Context::owned_workers_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 116, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 122, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 125, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 126, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 16 + }, + { + "source": "Runtime::Context", + "target": "Workers::Group", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 78, + "member": "CE::Runtime::Context::worker_groups_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 78, + "member": "CE::Runtime::Context::worker_groups_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 115, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 115, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 118, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 23 + }, + { + "source": "@cpu", + "target": "Workers::Group", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/workers.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Workers::Group", + "target": "Workers::GroupOptions", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 260, + "member": "CE::Workers::Group::report_diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 1 + } + ], + "notes": [ + "Context creates tracked groups over its lazy owned pool or a supplied shared pool. One group is not one physical pool.", + "Physical capacity must permit the requested concurrency. Context shutdown settles its tracked work while platform dependencies can progress." + ], + "level": "simple", + "refs": [ + "docs/site/engine-systems/workers.md" + ], + "nodes": [ + "@host", + "Runtime::ExecutionOptions", + "Runtime::Context", + "Workers::Pool", + "Workers::Group", + "@cpu", + "Workers::GroupOptions" + ], + "namespace": "Workers" + }, + { + "id": "workers-focus-shared", + "system": "workers", + "view": "focus-shared", + "title": "Workers \u00b7 practical pool shared by contexts", + "question": "What stays shared, and what closes independently?", + "edges": [ + { + "source": "@host", + "target": "Workers::Pool", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/workers.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "@host", + "target": "Runtime::ExecutionOptions", + "optional": false, + "evidence": [ + { + "file": "docs/site/engine-systems/workers.md", + "via": "illustrative application composition" + } + ], + "evidence_count": 1 + }, + { + "source": "Runtime::ExecutionOptions", + "target": "Workers::Pool", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 49, + "member": "CE::Runtime::ExecutionOptions::shared_pool", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 49, + "member": "CE::Runtime::ExecutionOptions::shared_pool", + "access": "public", + "via": "libclang AST" + } + ], + "evidence_count": 2 + }, + { + "source": "Runtime::Context#context A", + "target": "Runtime::ExecutionOptions", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 74, + "member": "CE::Runtime::Context::execution_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 93, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 74, + "member": "CE::Runtime::Context::execution_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 91, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 91, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 99, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 99, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 18 + }, + { + "source": "Runtime::Context#context B", + "target": "Runtime::ExecutionOptions", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 74, + "member": "CE::Runtime::Context::execution_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 93, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 74, + "member": "CE::Runtime::Context::execution_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 91, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 91, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 99, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 99, + "member": "CE::Runtime::Context::Context", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 18 + }, + { + "source": "Runtime::Context#context A", + "target": "Workers::Pool", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 76, + "member": "CE::Runtime::Context::owned_worker_factory_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 77, + "member": "CE::Runtime::Context::owned_workers_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 76, + "member": "CE::Runtime::Context::owned_worker_factory_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 77, + "member": "CE::Runtime::Context::owned_workers_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 116, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 122, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 125, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 126, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 16 + }, + { + "source": "Runtime::Context#context B", + "target": "Workers::Pool", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 76, + "member": "CE::Runtime::Context::owned_worker_factory_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 77, + "member": "CE::Runtime::Context::owned_workers_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 76, + "member": "CE::Runtime::Context::owned_worker_factory_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 77, + "member": "CE::Runtime::Context::owned_workers_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 116, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 122, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 125, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 126, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 16 + }, + { + "source": "Runtime::Context#context A", + "target": "Workers::Group#group A", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 78, + "member": "CE::Runtime::Context::worker_groups_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 78, + "member": "CE::Runtime::Context::worker_groups_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 115, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 115, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 118, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 23 + }, + { + "source": "Runtime::Context#context B", + "target": "Workers::Group#group B", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 78, + "member": "CE::Runtime::Context::worker_groups_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 78, + "member": "CE::Runtime::Context::worker_groups_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 115, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 115, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 118, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + } + ], + "evidence_count": 23 + } + ], + "notes": [ + "The repeated Context/Group boxes represent different instances of the same Engine types. Both contexts retain shared physical capacity.", + "Stopping context A closes/drains group A. It does not close unrelated group B or force the supplied pool to shut down." + ], + "level": "practical", + "refs": [ + "docs/site/engine-systems/workers.md" + ], + "nodes": [ + "@host", + "Workers::Pool", + "Runtime::ExecutionOptions", + "Runtime::Context#context A", + "Runtime::Context#context B", + "Workers::Group#group A", + "Workers::Group#group B" + ], + "namespace": "Workers" + }, + { + "id": "overview-systems", + "system": "overview", + "view": "systems", + "title": "Subsystems \u00b7 interactions", + "question": "How do the 17 documented responsibilities relate inside Cheryl Engine?", + "edges": [ + { + "source": "%runtime", + "target": "%display", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/runtime/startup/include/cheryl/application/startup.h", + "line": 39, + "member": "CE::Application::ApplicationOptions::mode", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/runtime/startup/include/cheryl/application/startup.h", + "line": 39, + "member": "CE::Application::ApplicationOptions::mode", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-backend.h", + "line": 20, + "member": "CE::Application::GLFWOpenGL::Options::mode", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-backend.h", + "line": 20, + "member": "CE::Application::GLFWOpenGL::Options::mode", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 27, + "member": "CE::Game::TickContext::logical_size", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 27, + "member": "CE::Game::TickContext::logical_size", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 24, + "member": "CE::Game::TickContext::framebuffer_size", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 24, + "member": "CE::Game::TickContext::framebuffer_size", + "access": "public", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Application::ApplicationOptions", + "target": "Platform::WindowMode", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/runtime/startup/include/cheryl/application/startup.h", + "line": 39, + "member": "CE::Application::ApplicationOptions::mode", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/runtime/startup/include/cheryl/application/startup.h", + "line": 39, + "member": "CE::Application::ApplicationOptions::mode", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Application::GLFWOpenGL::Options", + "target": "Platform::WindowMode", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-backend.h", + "line": 20, + "member": "CE::Application::GLFWOpenGL::Options::mode", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-backend.h", + "line": 20, + "member": "CE::Application::GLFWOpenGL::Options::mode", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Game::TickContext", + "target": "Platform::Dimensions", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 27, + "member": "CE::Game::TickContext::logical_size", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 27, + "member": "CE::Game::TickContext::logical_size", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Game::TickContext", + "target": "Platform::FramebufferSize", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 24, + "member": "CE::Game::TickContext::framebuffer_size", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 24, + "member": "CE::Game::TickContext::framebuffer_size", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Platform::DisplaySystem", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 65, + "member": "CE::Runtime::Context::display_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Platform::Window", + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 109, + "member": "CE::Runtime::Context::window", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 109, + "member": "CE::Runtime::Context::window", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Detail::ShutdownController", + "target": "Platform::WindowCloseRequested", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/src/core/game-framework/shutdown-internal.h", + "line": 22, + "member": "CE::Runtime::Detail::ShutdownController::window_close", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/shutdown-internal.h", + "line": 22, + "member": "CE::Runtime::Detail::ShutdownController::window_close", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::GameRuntime", + "target": "Platform::Dimensions", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 414, + "member": "CE::Runtime::GameRuntime::select_window", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 425, + "member": "CE::Runtime::GameRuntime::select_simulation_view", + "access": "invalid", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 8 + }, + { + "source": "%runtime", + "target": "%input", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/runtime/startup/include/cheryl/application/startup.h", + "line": 20, + "member": "CE::Application::RuntimeOptions::polling", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/runtime/startup/include/cheryl/application/startup.h", + "line": 20, + "member": "CE::Application::RuntimeOptions::polling", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 132, + "member": "CE::Application::Startup::validate_configuration", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 94, + "member": "CE::Application::Startup::add_engine_options", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 103, + "member": "CE::Application::Startup::add_engine_options", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 94, + "member": "CE::Application::Startup::add_engine_options", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 103, + "member": "CE::Application::Startup::add_engine_options", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Application::RuntimeOptions", + "target": "Input::PollingOptions", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/runtime/startup/include/cheryl/application/startup.h", + "line": 20, + "member": "CE::Application::RuntimeOptions::polling", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/runtime/startup/include/cheryl/application/startup.h", + "line": 20, + "member": "CE::Application::RuntimeOptions::polling", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Application::Startup", + "target": "Input::PollingBacklog", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 132, + "member": "CE::Application::Startup::validate_configuration", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Application::Startup", + "target": "Input::PollingOptions", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 94, + "member": "CE::Application::Startup::add_engine_options", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 103, + "member": "CE::Application::Startup::add_engine_options", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Application::Startup", + "target": "Input::PollingPolicy", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 94, + "member": "CE::Application::Startup::add_engine_options", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 103, + "member": "CE::Application::Startup::add_engine_options", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Application::StartupResult", + "target": "Input::PollingOptions", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 14, + "member": "CE::Application::StartupResult::make_runtime", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Game::TickContext", + "target": "Input::Actions::ButtonTickState", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/tick-context.cpp", + "line": 19, + "member": "CE::Game::TickContext::button_simulation_seconds", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/tick-context.cpp", + "line": 21, + "member": "CE::Game::TickContext::button_simulation_seconds", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Game::TickContext", + "target": "Input::Actions::Id", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 34, + "member": "CE::Game::TickContext::button_simulation_seconds", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 34, + "member": "CE::Game::TickContext::button_simulation_seconds", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Game::TickContext", + "target": "Input::Tick", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 23, + "member": "CE::Game::TickContext::input", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 23, + "member": "CE::Game::TickContext::input", + "access": "public", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 7 + }, + { + "source": "%runtime", + "target": "%rendering", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 34, + "member": "CE::Game::AbstractGame::prepare_render_frame", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 34, + "member": "CE::Game::AbstractGame::prepare_render_frame", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 66, + "member": "CE::Runtime::Context::surface_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 67, + "member": "CE::Runtime::Context::renderer_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 447, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 586, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Game::AbstractGame", + "target": "Rendering::FrameWriter", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 34, + "member": "CE::Game::AbstractGame::prepare_render_frame", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 34, + "member": "CE::Game::AbstractGame::prepare_render_frame", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Rendering::PresentationSurface", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 66, + "member": "CE::Runtime::Context::surface_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Rendering::Renderer", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 67, + "member": "CE::Runtime::Context::renderer_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "Runtime::GameRuntime", + "target": "Rendering::Frame", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 447, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 586, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::GameRuntime", + "target": "Rendering::FrameWriter", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 587, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 880, + "member": "CE::Runtime::GameRuntime::run_concurrent", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::GameRuntime", + "target": "Rendering::PresentationSurface", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 596, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 1054, + "member": "CE::Runtime::GameRuntime::run_concurrent", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::GameRuntime", + "target": "Rendering::Renderer", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 401, + "member": "CE::Runtime::GameRuntime::bind_renderer_maintenance", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 445, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Game::AbstractGame", + "target": "Rendering::FrameWriter", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 34, + "member": "Game::AbstractGame::prepare_render_frame", + "via": "API declaration" + } + ] + } + ], + "overview": true, + "evidence_count": 8 + }, + { + "source": "%runtime", + "target": "%resources", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 87, + "member": "CE::Runtime::Context::begin_session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 68, + "member": "CE::Runtime::Context::resources_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 68, + "member": "Runtime::Context::resources_", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "Runtime::Context::Context", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "Runtime::Context::Context()", + "via": "operation signature" + }, + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 93, + "member": "Runtime::Context::Context()", + "via": "operation signature" + } + ], + "examples": [ + { + "source": "Runtime::Context", + "target": "Rendering::GraphicsDomainStateChanged", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 87, + "member": "CE::Runtime::Context::begin_session", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Rendering::Resources::Provider", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 68, + "member": "CE::Runtime::Context::resources_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Rendering::Resources::Provider", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 68, + "member": "Runtime::Context::resources_", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "Runtime::Context::Context", + "via": "API declaration" + } + ] + }, + { + "source": "Runtime::Context::Context()", + "target": "Rendering::Resources::Provider", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "Runtime::Context::Context()", + "via": "operation signature" + }, + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 93, + "member": "Runtime::Context::Context()", + "via": "operation signature" + } + ] + }, + { + "source": "Runtime::Context::resources()", + "target": "Rendering::Resources::Provider", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 112, + "member": "Runtime::Context::resources()", + "via": "operation signature" + } + ] + } + ], + "overview": true, + "evidence_count": 7 + }, + { + "source": "%runtime", + "target": "%text", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 82, + "member": "CE::Runtime::Context::fonts_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 114, + "member": "CE::Runtime::Context::fonts", + "access": "public", + "via": "member declaration" + }, + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 82, + "member": "Runtime::Context::fonts_", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 114, + "member": "Runtime::Context::fonts", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 114, + "member": "Runtime::Context::fonts()", + "via": "operation signature" + } + ], + "examples": [ + { + "source": "Runtime::Context", + "target": "Text::FontService", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 82, + "member": "CE::Runtime::Context::fonts_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 114, + "member": "CE::Runtime::Context::fonts", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Text::FontService", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 82, + "member": "Runtime::Context::fonts_", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 114, + "member": "Runtime::Context::fonts", + "via": "API declaration" + } + ] + }, + { + "source": "Runtime::Context::fonts()", + "target": "Text::FontService", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 114, + "member": "Runtime::Context::fonts()", + "via": "operation signature" + } + ] + } + ], + "overview": true, + "evidence_count": 5 + }, + { + "source": "%runtime", + "target": "%events", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 61, + "member": "CE::Runtime::Context::events_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 116, + "member": "CE::Runtime::Context::events", + "access": "public", + "via": "member declaration" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 84, + "member": "CE::Runtime::Context::begin_session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 85, + "member": "CE::Runtime::Context::begin_session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 86, + "member": "CE::Runtime::Context::begin_session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime-internal.h", + "line": 25, + "member": "CE::Runtime::Detail::GameRuntimeAccess::watch_pressure", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 49, + "member": "CE::Runtime::Detail::GameRuntimeAccess::watch_pressure", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Runtime::Context", + "target": "Events::EventBus", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 61, + "member": "CE::Runtime::Context::events_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 116, + "member": "CE::Runtime::Context::events", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Events::EventBus::Publisher", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 84, + "member": "CE::Runtime::Context::begin_session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 85, + "member": "CE::Runtime::Context::begin_session", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Events::EventBus::Registration", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 86, + "member": "CE::Runtime::Context::begin_session", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Detail::GameRuntimeAccess", + "target": "Events::EventChannel", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime-internal.h", + "line": 25, + "member": "CE::Runtime::Detail::GameRuntimeAccess::watch_pressure", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 49, + "member": "CE::Runtime::Detail::GameRuntimeAccess::watch_pressure", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Detail::RequiredBindings", + "target": "Events::EventBus", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/src/core/engine/required-bindings.h", + "line": 122, + "member": "CE::Runtime::Detail::RequiredBindings::attach", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/required-bindings.h", + "line": 122, + "member": "CE::Runtime::Detail::RequiredBindings::attach", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Detail::RequiredBindings", + "target": "Events::EventBus::Publisher", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/src/core/engine/required-bindings.h", + "line": 160, + "member": "CE::Runtime::Detail::RequiredBindings::publish", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/required-bindings.h", + "line": 160, + "member": "CE::Runtime::Detail::RequiredBindings::publish", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Detail::RequiredBindings", + "target": "Events::EventChannel", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/src/core/engine/required-bindings.h", + "line": 123, + "member": "CE::Runtime::Detail::RequiredBindings::attach", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/required-bindings.h", + "line": 123, + "member": "CE::Runtime::Detail::RequiredBindings::attach", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Detail::RuntimePressureWatch", + "target": "Events::EventBus::Registration", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/src/core/game-framework/game-runtime-internal.h", + "line": 16, + "member": "CE::Runtime::Detail::RuntimePressureWatch::registration", + "access": "public", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 7 + }, + { + "source": "%runtime", + "target": "%dispatch", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 73, + "member": "CE::Runtime::Context::platform_dispatcher_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 115, + "member": "CE::Runtime::Context::platform_dispatcher", + "access": "public", + "via": "member declaration" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 91, + "member": "CE::Runtime::Context::begin_session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 464, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 489, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 84, + "member": "CE::Runtime::GameRuntime::simulation_dispatcher_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 119, + "member": "CE::Runtime::GameRuntime::simulation_dispatcher", + "access": "public", + "via": "member declaration" + } + ], + "examples": [ + { + "source": "Runtime::Context", + "target": "Runtime::PlatformDispatcher", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 73, + "member": "CE::Runtime::Context::platform_dispatcher_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 115, + "member": "CE::Runtime::Context::platform_dispatcher", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Runtime::PlatformDispatcher::Submission", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 91, + "member": "CE::Runtime::Context::begin_session", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::GameRuntime", + "target": "Runtime::PlatformDispatcher", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 464, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 489, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::GameRuntime", + "target": "Runtime::SimulationDispatcher", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 84, + "member": "CE::Runtime::GameRuntime::simulation_dispatcher_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 119, + "member": "CE::Runtime::GameRuntime::simulation_dispatcher", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Runtime::PlatformDispatcher", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 73, + "member": "Runtime::Context::platform_dispatcher_", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 115, + "member": "Runtime::Context::platform_dispatcher", + "via": "API declaration" + } + ] + }, + { + "source": "Runtime::Context::platform_dispatcher()", + "target": "Runtime::PlatformDispatcher", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 115, + "member": "Runtime::Context::platform_dispatcher()", + "via": "operation signature" + } + ] + }, + { + "source": "Runtime::GameRuntime", + "target": "Runtime::SimulationDispatcher", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 84, + "member": "Runtime::GameRuntime::simulation_dispatcher_", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 119, + "member": "Runtime::GameRuntime::simulation_dispatcher", + "via": "API declaration" + } + ] + }, + { + "source": "Runtime::GameRuntime::simulation_dispatcher()", + "target": "Runtime::SimulationDispatcher", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 119, + "member": "Runtime::GameRuntime::simulation_dispatcher()", + "via": "operation signature" + } + ] + } + ], + "overview": true, + "evidence_count": 7 + }, + { + "source": "%runtime", + "target": "%workers", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 78, + "member": "CE::Runtime::Context::worker_groups_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 76, + "member": "CE::Runtime::Context::owned_worker_factory_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 77, + "member": "CE::Runtime::Context::owned_workers_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 28, + "member": "CE::Runtime::Detail::ContextAccess::set_owned_worker_factory", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Runtime::Context", + "target": "Workers::Group", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 78, + "member": "CE::Runtime::Context::worker_groups_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Workers::GroupOptions", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Workers::Pool", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 76, + "member": "CE::Runtime::Context::owned_worker_factory_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 77, + "member": "CE::Runtime::Context::owned_workers_", + "access": "private", + "via": "member declaration" + } + ] + }, + { + "source": "Runtime::Detail::ContextAccess", + "target": "Workers::Group", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 28, + "member": "CE::Runtime::Detail::ContextAccess::set_owned_worker_factory", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Detail::ContextAccess", + "target": "Workers::Pool", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/src/core/engine/engine-context-internal.h", + "line": 16, + "member": "CE::Runtime::Detail::ContextAccess::set_owned_worker_factory", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context-internal.h", + "line": 16, + "member": "CE::Runtime::Detail::ContextAccess::set_owned_worker_factory", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::ExecutionOptions", + "target": "Workers::Pool", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 49, + "member": "CE::Runtime::ExecutionOptions::shared_pool", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 49, + "member": "CE::Runtime::ExecutionOptions::shared_pool", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Workers::Pool", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 76, + "member": "Runtime::Context::owned_worker_factory_", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 77, + "member": "Runtime::Context::owned_workers_", + "via": "API declaration" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Workers::Group", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 78, + "member": "Runtime::Context::worker_groups_", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "Runtime::Context::make_worker_group", + "via": "API declaration" + } + ] + } + ], + "overview": true, + "evidence_count": 7 + }, + { + "source": "%input", + "target": "%display", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 32, + "member": "CE::Input::System::initialize", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 32, + "member": "CE::Input::System::initialize", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 352, + "member": "CE::Platform::GLFW::InputSystem::initialize", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 353, + "member": "CE::Platform::GLFW::InputSystem::initialize", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 207, + "member": "CE::Platform::GLFW::InputSystem::State::gainput_lifetime_", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 311, + "member": "CE::Platform::GLFW::InputSystem::initialize", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 209, + "member": "CE::Platform::GLFW::InputSystem::State::bindings_", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 246, + "member": "CE::Platform::GLFW::InputSystem::bindings", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Input::System", + "target": "Platform::Window", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 32, + "member": "CE::Input::System::initialize", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 32, + "member": "CE::Input::System::initialize", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Platform::GLFW::InputSystem", + "target": "Platform::Dimensions", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 352, + "member": "CE::Platform::GLFW::InputSystem::initialize", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 353, + "member": "CE::Platform::GLFW::InputSystem::initialize", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Platform::GLFW::InputSystem", + "target": "Platform::GLFW::Detail::GainputLifetime", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 207, + "member": "CE::Platform::GLFW::InputSystem::State::gainput_lifetime_", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 311, + "member": "CE::Platform::GLFW::InputSystem::initialize", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Platform::GLFW::InputSystem", + "target": "Platform::GLFW::Detail::GainputMapper", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 209, + "member": "CE::Platform::GLFW::InputSystem::State::bindings_", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 246, + "member": "CE::Platform::GLFW::InputSystem::bindings", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Platform::GLFW::InputSystem", + "target": "Platform::GLFW::Detail::GlfwKeyboardDevice", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 326, + "member": "CE::Platform::GLFW::InputSystem::initialize", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Platform::GLFW::InputSystem", + "target": "Platform::GLFW::Detail::GlfwMouseDevice", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 330, + "member": "CE::Platform::GLFW::InputSystem::initialize", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Platform::GLFW::InputSystem", + "target": "Platform::GLFW::Detail::InputDevice", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 212, + "member": "CE::Platform::GLFW::InputSystem::State::keyboard_", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 213, + "member": "CE::Platform::GLFW::InputSystem::State::mouse_", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Platform::GLFW::InputSystem", + "target": "Platform::GLFW::DeviceSnapshot", + "evidence": [ + { + "kind": "returns", + "file": "projects/modules/platform/native-glfw/include/cheryl/core/controls/input-system.h", + "line": 85, + "member": "CE::Platform::GLFW::InputSystem::device", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/platform/native-glfw/include/cheryl/core/controls/input-system.h", + "line": 85, + "member": "CE::Platform::GLFW::InputSystem::device", + "access": "public", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 8 + }, + { + "source": "%input", + "target": "%events", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 57, + "member": "CE::Input::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 61, + "member": "CE::Input::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 87, + "member": "CE::Input::System::gamepad_publisher_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 57, + "member": "CE::Input::System::publish_events_to", + "access": "public", + "via": "member declaration" + }, + { + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 87, + "member": "Input::System::gamepad_publisher_", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 57, + "member": "Input::System::publish_events_to", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 57, + "member": "Input::System::publish_events_to()", + "via": "operation signature" + } + ], + "examples": [ + { + "source": "Input::System", + "target": "Events::EventBus", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 57, + "member": "CE::Input::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/controls/input-interface.cpp", + "line": 61, + "member": "CE::Input::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Input::System", + "target": "Events::EventBus::Publisher", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 87, + "member": "CE::Input::System::gamepad_publisher_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 57, + "member": "CE::Input::System::publish_events_to", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "Input::System", + "target": "Events::EventBus::Publisher", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 87, + "member": "Input::System::gamepad_publisher_", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 57, + "member": "Input::System::publish_events_to", + "via": "API declaration" + } + ] + }, + { + "source": "Input::System::publish_events_to()", + "target": "Events::EventBus::Publisher", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 57, + "member": "Input::System::publish_events_to()", + "via": "operation signature" + } + ] + } + ], + "overview": true, + "evidence_count": 7 + }, + { + "source": "%display", + "target": "%events", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/display/window.cpp", + "line": 173, + "member": "CE::Platform::GLFW::Window::update_framebuffer_size", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/display/window.cpp", + "line": 174, + "member": "CE::Platform::GLFW::Window::update_framebuffer_size", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Platform::GLFW::Window", + "target": "Events::EventSystem", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/display/window.cpp", + "line": 173, + "member": "CE::Platform::GLFW::Window::update_framebuffer_size", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/display/window.cpp", + "line": 174, + "member": "CE::Platform::GLFW::Window::update_framebuffer_size", + "access": "invalid", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 2 + }, + { + "source": "%rendering", + "target": "%resources", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/rendering/renderer.cpp", + "line": 30, + "member": "CE::Rendering::@anon(projects/engine/src/core/rendering/renderer.cpp)::TerminalObservation::~TerminalObservation", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/src/core/rendering/renderer.cpp", + "line": 25, + "member": "CE::Rendering::@anon(projects/engine/src/core/rendering/renderer.cpp)::TerminalObservation::change", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/rendering/renderer.cpp", + "line": 30, + "member": "CE::Rendering::@anon(projects/engine/src/core/rendering/renderer.cpp)::TerminalObservation::~TerminalObservation", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/src/core/rendering/graphics-domain.cpp", + "line": 13, + "member": "CE::Rendering::Detail::GraphicsDomainState::reason", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 33, + "member": "CE::Rendering::DrawPacket2D::geometry", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 33, + "member": "CE::Rendering::DrawPacket2D::geometry", + "access": "public", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Rendering::(anonymous)::TerminalObservation", + "target": "Rendering::GraphicsDomainSnapshot", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/rendering/renderer.cpp", + "line": 30, + "member": "CE::Rendering::@anon(projects/engine/src/core/rendering/renderer.cpp)::TerminalObservation::~TerminalObservation", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Rendering::(anonymous)::TerminalObservation", + "target": "Rendering::GraphicsDomainStateChanged", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/src/core/rendering/renderer.cpp", + "line": 25, + "member": "CE::Rendering::@anon(projects/engine/src/core/rendering/renderer.cpp)::TerminalObservation::change", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/rendering/renderer.cpp", + "line": 30, + "member": "CE::Rendering::@anon(projects/engine/src/core/rendering/renderer.cpp)::TerminalObservation::~TerminalObservation", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Rendering::Detail::GraphicsDomainState", + "target": "Rendering::GraphicsDomainReason", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/src/core/rendering/graphics-domain.cpp", + "line": 13, + "member": "CE::Rendering::Detail::GraphicsDomainState::reason", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Rendering::DrawPacket2D", + "target": "Rendering::Geometry::Buffer2D", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 33, + "member": "CE::Rendering::DrawPacket2D::geometry", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 33, + "member": "CE::Rendering::DrawPacket2D::geometry", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Rendering::DrawPacket2D", + "target": "Rendering::Resources::Material", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 34, + "member": "CE::Rendering::DrawPacket2D::material", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 34, + "member": "CE::Rendering::DrawPacket2D::material", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Rendering::DrawStyle2D", + "target": "Rendering::Resources::Material", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 19, + "member": "CE::Rendering::DrawStyle2D::material", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 19, + "member": "CE::Rendering::DrawStyle2D::material", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Rendering::Materials::Definition", + "target": "Rendering::Resources::Pipeline", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/definitions/pipeline.h", + "line": 90, + "member": "CE::Rendering::Materials::Definition::pipeline", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/definitions/pipeline.h", + "line": 90, + "member": "CE::Rendering::Materials::Definition::pipeline", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Rendering::Materials::Definition", + "target": "Rendering::Sampling::Sampler", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/definitions/pipeline.h", + "line": 92, + "member": "CE::Rendering::Materials::Definition::sampling", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/definitions/pipeline.h", + "line": 92, + "member": "CE::Rendering::Materials::Definition::sampling", + "access": "public", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 6 + }, + { + "source": "%rendering", + "target": "%assets", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + }, + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + }, + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 48, + "member": "Rendering::Submission::resolve_tile()", + "via": "operation signature" + }, + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 48, + "member": "Rendering::Submission::resolve_tile()", + "via": "operation signature" + } + ], + "examples": [ + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Assets::Sprites::Asset", + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + } + ] + }, + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Assets::Grids::CellIndex", + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + } + ] + }, + { + "source": "Rendering::Submission::resolve_tile()", + "target": "Assets::Tilesets::Asset", + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 48, + "member": "Rendering::Submission::resolve_tile()", + "via": "operation signature" + } + ] + }, + { + "source": "Rendering::Submission::resolve_tile()", + "target": "Assets::Grids::CellIndex", + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 48, + "member": "Rendering::Submission::resolve_tile()", + "via": "operation signature" + } + ] + }, + { + "source": "Rendering::Submission::resolve_tile()", + "target": "Assets::Tilesets::Tile", + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 55, + "member": "Rendering::Submission::resolve_tile()", + "via": "operation signature" + } + ] + }, + { + "source": "Rendering::Submission::resolve_tile()", + "target": "Assets::Tilesets::Animation", + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 57, + "member": "Rendering::Submission::resolve_tile()", + "via": "operation signature" + } + ] + }, + { + "source": "Rendering::Submission::resolve_graphic()", + "target": "Assets::Graphics::Asset", + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 59, + "member": "Rendering::Submission::resolve_graphic()", + "via": "operation signature" + } + ] + } + ], + "overview": true, + "evidence_count": 4 + }, + { + "source": "%text", + "target": "%resources", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 61, + "member": "CE::Text::Legacy::AsciiFont::glyph_geometry", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 61, + "member": "CE::Text::Legacy::AsciiFont::glyph_geometry", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 62, + "member": "CE::Text::Legacy::AsciiFont::glyph_atlas", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 62, + "member": "CE::Text::Legacy::AsciiFont::glyph_atlas", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 54, + "member": "CE::Text::Legacy::AsciiFont::load_font", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 58, + "member": "CE::Text::Legacy::AsciiFont::load_font", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 54, + "member": "CE::Text::Legacy::AsciiFont::load_font", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 57, + "member": "CE::Text::Legacy::AsciiFont::load_font", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Text::Legacy::AsciiFont", + "target": "Rendering::Geometry::Buffer2D", + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 61, + "member": "CE::Text::Legacy::AsciiFont::glyph_geometry", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 61, + "member": "CE::Text::Legacy::AsciiFont::glyph_geometry", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Text::Legacy::AsciiFont", + "target": "Rendering::Resources::Image", + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 62, + "member": "CE::Text::Legacy::AsciiFont::glyph_atlas", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 62, + "member": "CE::Text::Legacy::AsciiFont::glyph_atlas", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Text::Legacy::AsciiFont", + "target": "Rendering::Resources::Material", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 54, + "member": "CE::Text::Legacy::AsciiFont::load_font", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 58, + "member": "CE::Text::Legacy::AsciiFont::load_font", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Text::Legacy::AsciiFont", + "target": "Rendering::Resources::Provider", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 54, + "member": "CE::Text::Legacy::AsciiFont::load_font", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 57, + "member": "CE::Text::Legacy::AsciiFont::load_font", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Text::Legacy::AsciiFontData", + "target": "Rendering::Geometry::Buffer2D", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 29, + "member": "CE::Text::Legacy::AsciiFontData::geometry", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 29, + "member": "CE::Text::Legacy::AsciiFontData::geometry", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Text::Legacy::AsciiFontData", + "target": "Rendering::Resources::Image", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 30, + "member": "CE::Text::Legacy::AsciiFontData::texture", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 30, + "member": "CE::Text::Legacy::AsciiFontData::texture", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Text::Legacy::AsciiFontData", + "target": "Rendering::Resources::Material", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 33, + "member": "CE::Text::Legacy::AsciiFontData::material", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 33, + "member": "CE::Text::Legacy::AsciiFontData::material", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Text::Legacy::BitmapFont", + "target": "Rendering::Geometry::Buffer2D", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/types/2d/ffont.h", + "line": 43, + "member": "CE::Text::Legacy::BitmapFont::BitmapFont", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/assets/types/2d/ffont.cpp", + "line": 74, + "member": "CE::Text::Legacy::BitmapFont::load_ffont", + "access": "invalid", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 8 + }, + { + "source": "%text", + "target": "%workers", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 36, + "member": "CE::Text::Rendering::Updater::State::workers", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 52, + "member": "CE::Text::Rendering::Updater::State", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Text::Rendering::Updater", + "target": "Workers::Group", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 36, + "member": "CE::Text::Rendering::Updater::State::workers", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 52, + "member": "CE::Text::Rendering::Updater::State", + "access": "invalid", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 2 + }, + { + "source": "%text", + "target": "%dispatch", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 53, + "member": "CE::Text::Rendering::Updater::State", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 37, + "member": "CE::Text::Rendering::Updater::State::platform", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 53, + "member": "CE::Text::Rendering::Updater::State", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Text::Rendering::Updater", + "target": "Runtime::PlatformDispatcher", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 53, + "member": "CE::Text::Rendering::Updater::State", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Text::Rendering::Updater", + "target": "Runtime::PlatformDispatcher::Submission", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 37, + "member": "CE::Text::Rendering::Updater::State::platform", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/text/text-updater.cpp", + "line": 53, + "member": "CE::Text::Rendering::Updater::State", + "access": "invalid", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 3 + }, + { + "source": "%gui", + "target": "%input", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 189, + "member": "CE::UI::RmlUi::Session::State", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 190, + "member": "CE::UI::RmlUi::Session::State", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 166, + "member": "CE::UI::RmlUi::Session::State::events", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 167, + "member": "CE::UI::RmlUi::Session::State::text", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 460, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 462, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 459, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 461, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "UI::RmlUi::Session", + "target": "Input::Capture::Channel", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 189, + "member": "CE::UI::RmlUi::Session::State", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 190, + "member": "CE::UI::RmlUi::Session::State", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::Session", + "target": "Input::Capture::Lease", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 166, + "member": "CE::UI::RmlUi::Session::State::events", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 167, + "member": "CE::UI::RmlUi::Session::State::text", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::Session", + "target": "Input::DeviceKind", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 460, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 462, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::Session", + "target": "Input::Events::Button", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 459, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 461, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::Session", + "target": "Input::Events::ButtonPhase", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 474, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 486, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::Session", + "target": "Input::Events::Payload", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 459, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 466, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::Session", + "target": "Input::Events::Pointer", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 481, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 490, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::Session", + "target": "Input::Events::Record", + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 93, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 97, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "public", + "via": "member declaration" + } + ] + } + ], + "overview": true, + "evidence_count": 8 + }, + { + "source": "%gui", + "target": "%text", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/src/font-service.cpp", + "line": 43, + "member": "CE::UI::RmlUi::Detail::@anon(projects/modules/ui/rmlui/src/font-service.cpp)::Face::rasterizer", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 79, + "member": "CE::UI::RmlUi::Session::font_family", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 80, + "member": "CE::UI::RmlUi::Session::set_font", + "access": "public", + "via": "member declaration" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 208, + "member": "CE::UI::RmlUi::Session::reserve_font", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 220, + "member": "CE::UI::RmlUi::Session::font_family", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 61, + "member": "CE::UI::RmlUi::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 63, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "UI::RmlUi::Detail::(anonymous)::Face", + "target": "Text::FontRasterizer", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/src/font-service.cpp", + "line": 43, + "member": "CE::UI::RmlUi::Detail::@anon(projects/modules/ui/rmlui/src/font-service.cpp)::Face::rasterizer", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::Session", + "target": "Text::Font", + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 79, + "member": "CE::UI::RmlUi::Session::font_family", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 80, + "member": "CE::UI::RmlUi::Session::set_font", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "UI::RmlUi::Session", + "target": "Text::FontFace", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 208, + "member": "CE::UI::RmlUi::Session::reserve_font", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 220, + "member": "CE::UI::RmlUi::Session::font_family", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::Session", + "target": "Text::FontService", + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 61, + "member": "CE::UI::RmlUi::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 63, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::TGUI::Detail::FontBackend", + "target": "Text::Font", + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/src/font.h", + "line": 49, + "member": "CE::UI::TGUI::Detail::FontBackend::load", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/font.h", + "line": 49, + "member": "CE::UI::TGUI::Detail::FontBackend::load", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::TGUI::Detail::FontBackend", + "target": "Text::FontFace", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/font.cpp", + "line": 87, + "member": "CE::UI::TGUI::Detail::FontBackend::load", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/font.cpp", + "line": 90, + "member": "CE::UI::TGUI::Detail::FontBackend::load", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::TGUI::Detail::FontBackend", + "target": "Text::FontRasterGlyph", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/font.cpp", + "line": 147, + "member": "CE::UI::TGUI::Detail::FontBackend::glyph", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/font.cpp", + "line": 148, + "member": "CE::UI::TGUI::Detail::FontBackend::glyph", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::TGUI::Detail::FontBackend", + "target": "Text::FontRasterMetrics", + "evidence": [ + { + "kind": "returns", + "file": "projects/modules/ui/tgui/src/font.h", + "line": 43, + "member": "CE::UI::TGUI::Detail::FontBackend::metrics", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/font.h", + "line": 43, + "member": "CE::UI::TGUI::Detail::FontBackend::metrics", + "access": "invalid", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 7 + }, + { + "source": "%gui", + "target": "%resources", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 23, + "member": "CE::UI::RmlUi::Materials::solid", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 24, + "member": "CE::UI::RmlUi::Materials::textured", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 21, + "member": "CE::UI::RmlUi::RecordedDraw::texture", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 21, + "member": "CE::UI::RmlUi::RecordedDraw::texture", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 49, + "member": "CE::UI::RmlUi::RenderTarget::textures_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 92, + "member": "CE::UI::RmlUi::RenderTarget::store_texture", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 74, + "member": "CE::UI::RmlUi::SceneUploader::State::images", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 122, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "UI::RmlUi::Materials", + "target": "Rendering::Resources::Material", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 23, + "member": "CE::UI::RmlUi::Materials::solid", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 24, + "member": "CE::UI::RmlUi::Materials::textured", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "UI::RmlUi::RecordedDraw", + "target": "Assets::Images::Decoded", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 21, + "member": "CE::UI::RmlUi::RecordedDraw::texture", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 21, + "member": "CE::UI::RmlUi::RecordedDraw::texture", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::RenderTarget", + "target": "Assets::Images::Decoded", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 49, + "member": "CE::UI::RmlUi::RenderTarget::textures_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 92, + "member": "CE::UI::RmlUi::RenderTarget::store_texture", + "access": "private", + "via": "member declaration" + } + ] + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "Assets::Images::Decoded", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 74, + "member": "CE::UI::RmlUi::SceneUploader::State::images", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 122, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "Assets::Shaders::Builder", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 89, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 93, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "Rendering::Geometry::Buffer2D", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 142, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 144, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "Rendering::Resources::Image", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 74, + "member": "CE::UI::RmlUi::SceneUploader::State::images", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 122, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "Rendering::Resources::Material", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 93, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 96, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 8 + }, + { + "source": "%gui", + "target": "%dispatch", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 60, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 60, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 65, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 65, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + } + ], + "examples": [ + { + "source": "UI::RmlUi::SceneUploader", + "target": "Runtime::PlatformDispatcher", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 60, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "Runtime::PlatformDispatcher::Submission", + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 60, + "member": "CE::UI::RmlUi::SceneUploader::submit", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "UI::TGUI::SceneUploader", + "target": "Runtime::PlatformDispatcher", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 65, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::TGUI::SceneUploader", + "target": "Runtime::PlatformDispatcher::Submission", + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 65, + "member": "CE::UI::TGUI::SceneUploader::submit", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "Runtime::PlatformDispatcher::Submission", + "evidence": [ + { + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "member": "UI::RmlUi::SceneUploader::submit", + "via": "API declaration" + }, + { + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 60, + "member": "UI::RmlUi::SceneUploader::submit", + "via": "API declaration" + } + ] + }, + { + "source": "UI::RmlUi::SceneUploader::submit()", + "target": "Runtime::PlatformDispatcher::Submission", + "evidence": [ + { + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 58, + "member": "UI::RmlUi::SceneUploader::submit()", + "via": "operation signature" + }, + { + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 60, + "member": "UI::RmlUi::SceneUploader::submit()", + "via": "operation signature" + } + ] + }, + { + "source": "UI::TGUI::SceneUploader", + "target": "Runtime::PlatformDispatcher::Submission", + "evidence": [ + { + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "member": "UI::TGUI::SceneUploader::submit", + "via": "API declaration" + }, + { + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 65, + "member": "UI::TGUI::SceneUploader::submit", + "via": "API declaration" + } + ] + }, + { + "source": "UI::TGUI::SceneUploader::submit()", + "target": "Runtime::PlatformDispatcher::Submission", + "evidence": [ + { + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 63, + "member": "UI::TGUI::SceneUploader::submit()", + "via": "operation signature" + }, + { + "file": "projects/modules/ui/tgui/include/cheryl/ui/tgui/scene.h", + "line": 65, + "member": "UI::TGUI::SceneUploader::submit()", + "via": "operation signature" + } + ] + } + ], + "overview": true, + "evidence_count": 8 + }, + { + "source": "%gui", + "target": "%rendering", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 27, + "member": "CE::UI::RmlUi::Materials::pass_parameters", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 27, + "member": "CE::UI::RmlUi::Materials::pass_parameters", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 22, + "member": "CE::UI::RmlUi::RecordedDraw::clip", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 22, + "member": "CE::UI::RmlUi::RecordedDraw::clip", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 20, + "member": "CE::UI::RmlUi::RecordedDraw::vertices", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 20, + "member": "CE::UI::RmlUi::RecordedDraw::vertices", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 47, + "member": "CE::UI::RmlUi::RenderTarget::scissor_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 47, + "member": "CE::UI::RmlUi::RenderTarget::scissor_", + "access": "private", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "UI::RmlUi::Materials", + "target": "Rendering::Parameters::Set", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 27, + "member": "CE::UI::RmlUi::Materials::pass_parameters", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 27, + "member": "CE::UI::RmlUi::Materials::pass_parameters", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::RecordedDraw", + "target": "Rendering::ClipRegion2D", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 22, + "member": "CE::UI::RmlUi::RecordedDraw::clip", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 22, + "member": "CE::UI::RmlUi::RecordedDraw::clip", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::RecordedDraw", + "target": "Rendering::Geometry::Vertex2DColor", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 20, + "member": "CE::UI::RmlUi::RecordedDraw::vertices", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 20, + "member": "CE::UI::RmlUi::RecordedDraw::vertices", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::RenderTarget", + "target": "Rendering::ClipRect2D", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 47, + "member": "CE::UI::RmlUi::RenderTarget::scissor_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 47, + "member": "CE::UI::RmlUi::RenderTarget::scissor_", + "access": "private", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::RenderTarget", + "target": "Rendering::ClipRegion2D", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/rendering.cpp", + "line": 113, + "member": "CE::UI::RmlUi::RenderTarget::RenderGeometry", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/rendering.cpp", + "line": 117, + "member": "CE::UI::RmlUi::RenderTarget::RenderGeometry", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::RenderTarget", + "target": "Rendering::Geometry::PixelSize", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/rendering.cpp", + "line": 160, + "member": "CE::UI::RmlUi::RenderTarget::LoadTexture", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/rendering.cpp", + "line": 162, + "member": "CE::UI::RmlUi::RenderTarget::LoadTexture", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::RenderTarget", + "target": "Rendering::Geometry::Vertex2DColor", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 48, + "member": "CE::UI::RmlUi::RenderTarget::geometry_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 48, + "member": "CE::UI::RmlUi::RenderTarget::geometry_", + "access": "private", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::Scene", + "target": "Rendering::DrawPacket2D", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 35, + "member": "CE::UI::RmlUi::Scene::draws_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 38, + "member": "CE::UI::RmlUi::Scene::draws", + "access": "public", + "via": "member declaration" + } + ] + } + ], + "overview": true, + "evidence_count": 8 + }, + { + "source": "%assets", + "target": "%resources", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 106, + "member": "CE::Assets::Cache::AssetCatalogue::State::domain", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 133, + "member": "CE::Assets::Cache::AssetCatalogue::require_domain", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 133, + "member": "CE::Assets::Cache::AssetCatalogue::require_domain", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 134, + "member": "CE::Assets::Cache::AssetCatalogue::require_domain", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 134, + "member": "CE::Assets::Cache::AssetCatalogue::require_domain", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 32, + "member": "CE::Assets::Cache::CatalogueGeneration::graphics_domain", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 32, + "member": "CE::Assets::Cache::CatalogueGeneration::graphics_domain", + "access": "public", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Assets::Cache::AssetCatalogue", + "target": "Rendering::GraphicsDomain", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 106, + "member": "CE::Assets::Cache::AssetCatalogue::State::domain", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 133, + "member": "CE::Assets::Cache::AssetCatalogue::require_domain", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Cache::AssetCatalogue", + "target": "Rendering::GraphicsDomainSnapshot", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 133, + "member": "CE::Assets::Cache::AssetCatalogue::require_domain", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 134, + "member": "CE::Assets::Cache::AssetCatalogue::require_domain", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Cache::AssetCatalogue", + "target": "Rendering::GraphicsDomainStatus", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 134, + "member": "CE::Assets::Cache::AssetCatalogue::require_domain", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Cache::CatalogueGeneration", + "target": "Rendering::GraphicsDomain", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 32, + "member": "CE::Assets::Cache::CatalogueGeneration::graphics_domain", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 32, + "member": "CE::Assets::Cache::CatalogueGeneration::graphics_domain", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Cache::Context", + "target": "Rendering::GraphicsDomain", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 52, + "member": "CE::Assets::Cache::Context::bound_domain_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 52, + "member": "CE::Assets::Cache::Context::bound_domain_", + "access": "private", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Cache::Context", + "target": "Rendering::GraphicsDomainSnapshot", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 94, + "member": "CE::Assets::Cache::Context::verify_locked", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 94, + "member": "CE::Assets::Cache::Context::verify_locked", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Cache::Context", + "target": "Rendering::GraphicsDomainStateChanged", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 62, + "member": "CE::Assets::Cache::Context::evict_graphics_domain", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 62, + "member": "CE::Assets::Cache::Context::evict_graphics_domain", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Cache::Context", + "target": "Rendering::GraphicsDomainStatus", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/assets/resources/resource-provider.cpp", + "line": 104, + "member": "CE::Assets::Cache::Context::verify_locked", + "access": "invalid", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 7 + }, + { + "source": "%assets", + "target": "%memory", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 313, + "member": "CE::Assets::Cache::Store::reserve", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 323, + "member": "CE::Assets::Cache::Store::allocate", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 329, + "member": "CE::Assets::Cache::Store::allocate", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 313, + "member": "CE::Assets::Cache::Store::reserve", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/sprite-mgr.cpp", + "line": 37, + "member": "CE::Assets::Sprites::Manager::load", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Assets::Cache::Store", + "target": "Memory::Allocators::ObjectPoolAllocator", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 313, + "member": "CE::Assets::Cache::Store::reserve", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 323, + "member": "CE::Assets::Cache::Store::allocate", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Cache::Store", + "target": "Memory::Block", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 329, + "member": "CE::Assets::Cache::Store::allocate", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Cache::Store", + "target": "Memory::Objects::Reservation", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 313, + "member": "CE::Assets::Cache::Store::reserve", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Sprites::Manager", + "target": "Memory::Allocators::ObjectPoolAllocator", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/sprite-mgr.cpp", + "line": 37, + "member": "CE::Assets::Sprites::Manager::load", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Sprites::Manager", + "target": "Memory::Objects::Reservation", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/sprite-mgr.cpp", + "line": 37, + "member": "CE::Assets::Sprites::Manager::load", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/sprite-mgr.cpp", + "line": 72, + "member": "CE::Assets::Sprites::Manager::load", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Tilesets::Manager", + "target": "Memory::Allocators::ObjectPoolAllocator", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/tileset-mgr.cpp", + "line": 22, + "member": "CE::Assets::Tilesets::Manager::load", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Tilesets::Manager", + "target": "Memory::Objects::Reservation", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/tileset-mgr.cpp", + "line": 22, + "member": "CE::Assets::Tilesets::Manager::load", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/resources/asset-management/tileset-mgr.cpp", + "line": 57, + "member": "CE::Assets::Tilesets::Manager::load", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Tilesets::Animation", + "target": "Memory::Shared", + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/types/2d/tileset.h", + "line": 51, + "member": "Assets::Tilesets::Animation::Animation", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/assets/types/2d/tileset.h", + "line": 51, + "member": "Assets::Tilesets::Animation::Animation", + "via": "API declaration" + } + ] + } + ], + "overview": true, + "evidence_count": 5 + }, + { + "source": "%assets", + "target": "%utilities", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/types/primitives/frame.h", + "line": 45, + "member": "CE::Assets::Animation::Cursor::Cursor", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 282, + "member": "CE::Assets::Cache::AssetCatalogue::watch", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 303, + "member": "CE::Assets::Cache::AssetCatalogue::publish_events_to", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 122, + "member": "CE::Assets::Cache::AssetCatalogue::State", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 129, + "member": "CE::Assets::Cache::AssetCatalogue::next_revision", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 272, + "member": "CE::Assets::Cache::AssetCatalogue::watch", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Assets::Animation::Cursor", + "target": "Exceptions::bad_request", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/types/primitives/frame.h", + "line": 45, + "member": "CE::Assets::Animation::Cursor::Cursor", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Cache::AssetCatalogue", + "target": "Exceptions::bad_request", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 282, + "member": "CE::Assets::Cache::AssetCatalogue::watch", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 303, + "member": "CE::Assets::Cache::AssetCatalogue::publish_events_to", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Cache::AssetCatalogue", + "target": "Exceptions::failed_operation", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 122, + "member": "CE::Assets::Cache::AssetCatalogue::State", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 129, + "member": "CE::Assets::Cache::AssetCatalogue::next_revision", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Cache::AssetCatalogue", + "target": "Exceptions::invalid_args", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 272, + "member": "CE::Assets::Cache::AssetCatalogue::watch", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Cache::Context", + "target": "Exceptions::failed_operation", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/assets/resources/resource-provider.cpp", + "line": 101, + "member": "CE::Assets::Cache::Context::verify_locked", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/assets/resources/resource-provider.cpp", + "line": 103, + "member": "CE::Assets::Cache::Context::verify_locked", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Cache::Store", + "target": "Exceptions::failed_operation", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 244, + "member": "CE::Assets::Cache::Store::publish_asset", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 274, + "member": "CE::Assets::Cache::Store::replace_asset", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Graphics::Asset", + "target": "Exceptions::invalid_args", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/assets/types/2d/graphic.cpp", + "line": 18, + "member": "CE::Assets::Graphics::Asset::from_image", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/assets/types/2d/graphic.cpp", + "line": 21, + "member": "CE::Assets::Graphics::Asset::from_image", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Graphics::Asset", + "target": "Rendering::Geometry::Pivot", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/graphic.h", + "line": 30, + "member": "CE::Assets::Graphics::Asset::from_image", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/graphic.h", + "line": 37, + "member": "CE::Assets::Graphics::Asset::load", + "access": "public", + "via": "member declaration" + } + ] + } + ], + "overview": true, + "evidence_count": 6 + }, + { + "source": "%assets", + "target": "%state", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/assets/resources/resource-provider.cpp", + "line": 83, + "member": "CE::Assets::Cache::Context::clear_provider_assets", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/assets/resources/resource-provider.cpp", + "line": 84, + "member": "CE::Assets::Cache::Context::clear_provider_assets", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/texture-mgr.h", + "line": 22, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/texture-mgr.h", + "line": 23, + "access": "public", + "via": "libclang AST" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/file-registry.h", + "line": 24, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/file-registry.h", + "line": 24, + "access": "public", + "via": "libclang AST" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 88, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 88, + "access": "public", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Assets::Cache::Context", + "target": "State::Singleton", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/assets/resources/resource-provider.cpp", + "line": 83, + "member": "CE::Assets::Cache::Context::clear_provider_assets", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/assets/resources/resource-provider.cpp", + "line": 84, + "member": "CE::Assets::Cache::Context::clear_provider_assets", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Images::Manager", + "target": "State::Singleton", + "evidence": [ + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/texture-mgr.h", + "line": 22, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/texture-mgr.h", + "line": 23, + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Loading::FileRegistry", + "target": "State::Singleton", + "evidence": [ + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/file-registry.h", + "line": 24, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/file-registry.h", + "line": 24, + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Loading::Loader", + "target": "State::Singleton", + "evidence": [ + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 88, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-loader.h", + "line": 88, + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Materials::Manager", + "target": "State::Singleton", + "evidence": [ + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/material-mgr.h", + "line": 24, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/material-mgr.h", + "line": 25, + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Shaders::Manager", + "target": "State::Singleton", + "evidence": [ + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/shader-asset-mgr.h", + "line": 32, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/shader-asset-mgr.h", + "line": 33, + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Shaders::ProgramCache", + "target": "State::Singleton", + "evidence": [ + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/shader-mgr.h", + "line": 23, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/shader-mgr.h", + "line": 24, + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Sprites::Manager", + "target": "State::Singleton", + "evidence": [ + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/sprite-mgr.h", + "line": 25, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/asset-management/sprite-mgr.h", + "line": 26, + "access": "public", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 8 + }, + { + "source": "%audio", + "target": "%events", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 101, + "member": "CE::Audio::Miniaudio::Detail::SystemState::publisher", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 56, + "member": "CE::Audio::Miniaudio::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 600, + "member": "CE::Audio::Miniaudio::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 56, + "member": "CE::Audio::Miniaudio::System::publish_events_to", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 56, + "member": "CE::Audio::Miniaudio::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 85, + "member": "CE::Audio::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/audio/system.cpp", + "line": 14, + "member": "CE::Audio::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Audio::Miniaudio::Detail::SystemState", + "target": "Events::EventBus::Publisher", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 101, + "member": "CE::Audio::Miniaudio::Detail::SystemState::publisher", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Audio::Miniaudio::System", + "target": "Events::EventBus", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 56, + "member": "CE::Audio::Miniaudio::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/audio/miniaudio/src/system.cpp", + "line": 600, + "member": "CE::Audio::Miniaudio::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Audio::Miniaudio::System", + "target": "Events::EventBus::Publisher", + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 56, + "member": "CE::Audio::Miniaudio::System::publish_events_to", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 56, + "member": "CE::Audio::Miniaudio::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Audio::System", + "target": "Events::EventBus", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 85, + "member": "CE::Audio::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/audio/system.cpp", + "line": 14, + "member": "CE::Audio::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Audio::System", + "target": "Events::EventBus::Publisher", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 85, + "member": "CE::Audio::System::publish_events_to", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 85, + "member": "CE::Audio::System::publish_events_to", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Audio::Miniaudio::System", + "target": "Events::EventBus::Publisher", + "evidence": [ + { + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 56, + "member": "Audio::Miniaudio::System::publish_events_to", + "via": "API declaration" + } + ] + }, + { + "source": "Audio::Miniaudio::System::publish_events_to()", + "target": "Events::EventBus::Publisher", + "evidence": [ + { + "file": "projects/modules/audio/miniaudio/include/cheryl/backends/miniaudio.h", + "line": 56, + "member": "Audio::Miniaudio::System::publish_events_to()", + "via": "operation signature" + } + ] + }, + { + "source": "Audio::System", + "target": "Events::EventBus::Publisher", + "evidence": [ + { + "file": "projects/engine/include/cheryl/audio/system.h", + "line": 85, + "member": "Audio::System::publish_events_to", + "via": "API declaration" + } + ] + } + ], + "overview": true, + "evidence_count": 7 + }, + { + "source": "%events", + "target": "%dispatch", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 15, + "member": "Events::platform_delivery()", + "via": "operation signature" + }, + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 16, + "member": "Events::simulation_delivery()", + "via": "operation signature" + } + ], + "examples": [ + { + "source": "Events::platform_delivery()", + "target": "Runtime::PlatformDispatcher::Submission", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 15, + "member": "Events::platform_delivery()", + "via": "operation signature" + } + ] + }, + { + "source": "Events::simulation_delivery()", + "target": "Runtime::SimulationDispatcher::Submission", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 16, + "member": "Events::simulation_delivery()", + "via": "operation signature" + } + ] + } + ], + "overview": true, + "evidence_count": 2 + }, + { + "source": "%events", + "target": "%workers", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 19, + "member": "Events::worker_delivery()", + "via": "operation signature" + } + ], + "examples": [ + { + "source": "Events::worker_delivery()", + "target": "Workers::Group", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 19, + "member": "Events::worker_delivery()", + "via": "operation signature" + } + ] + } + ], + "overview": true, + "evidence_count": 1 + }, + { + "source": "%memory", + "target": "%state", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/allocators/default-allocator.hpp", + "line": 24, + "member": "CE::Memory::Allocators::DefaultAllocator::allocate", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/allocators/default-allocator.hpp", + "line": 27, + "member": "CE::Memory::Allocators::DefaultAllocator::deallocate", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 27, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 28, + "access": "public", + "via": "libclang AST" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/objects/pool.h", + "line": 42, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/objects/pool.h", + "line": 43, + "access": "public", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Memory::Allocators::DefaultAllocator", + "target": "State::Singleton", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/allocators/default-allocator.hpp", + "line": 24, + "member": "CE::Memory::Allocators::DefaultAllocator::allocate", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/allocators/default-allocator.hpp", + "line": 27, + "member": "CE::Memory::Allocators::DefaultAllocator::deallocate", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Memory::Manager", + "target": "State::Singleton", + "evidence": [ + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 27, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 28, + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Memory::Objects::Pool", + "target": "State::Singleton", + "evidence": [ + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/objects/pool.h", + "line": 42, + "via": "base declaration" + }, + { + "kind": "inherits", + "file": "projects/engine/include/cheryl/core/resources/objects/pool.h", + "line": 43, + "access": "public", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 6 + }, + { + "source": "%memory", + "target": "%utilities", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/allocators/default-allocator.hpp", + "line": 24, + "member": "CE::Memory::Allocators::DefaultAllocator::allocate", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/allocators/object-allocator.hpp", + "line": 23, + "member": "CE::Memory::Allocators::ObjectAllocator::allocate", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/release-context.h", + "line": 21, + "member": "CE::Memory::ByteReleaseContext::return_chunk", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/release-context.h", + "line": 32, + "member": "CE::Memory::ByteReleaseContext::release_owned", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 109, + "member": "CE::Memory::Manager::return_portion", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 135, + "member": "CE::Memory::Manager::checkout_chunk", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Memory::Allocators::DefaultAllocator", + "target": "Memory::FitPolicy", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/allocators/default-allocator.hpp", + "line": 24, + "member": "CE::Memory::Allocators::DefaultAllocator::allocate", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Memory::Allocators::ObjectAllocator", + "target": "Memory::FitPolicy", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/allocators/object-allocator.hpp", + "line": 23, + "member": "CE::Memory::Allocators::ObjectAllocator::allocate", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Memory::ByteReleaseContext", + "target": "Exceptions::failed_operation", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/release-context.h", + "line": 21, + "member": "CE::Memory::ByteReleaseContext::return_chunk", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/release-context.h", + "line": 32, + "member": "CE::Memory::ByteReleaseContext::release_owned", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Memory::Manager", + "target": "Exceptions::bad_request", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 109, + "member": "CE::Memory::Manager::return_portion", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.hpp", + "line": 135, + "member": "CE::Memory::Manager::checkout_chunk", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Memory::Manager", + "target": "Memory::FitPolicy", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 61, + "member": "CE::Memory::Manager::checkout_chunk", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/resources/memory/mem-mgr.h", + "line": 64, + "member": "CE::Memory::Manager::checkout_chunk", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Memory::Objects::Constructor::Context", + "target": "Exceptions::bad_request", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/objects/object-construction.hpp", + "line": 42, + "member": "CE::Memory::Objects::Constructor::Context::claim", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/objects/object-construction.hpp", + "line": 105, + "member": "CE::Memory::Objects::Constructor::Context::erase", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Memory::Objects::PoolState", + "target": "Exceptions::bad_request", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/objects/pool.hpp", + "line": 88, + "member": "CE::Memory::Objects::PoolState::retrieve_block", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/objects/pool.hpp", + "line": 103, + "member": "CE::Memory::Objects::PoolState::return_objects", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Memory::Objects::PoolState", + "target": "Exceptions::failed_operation", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/objects/pool.hpp", + "line": 114, + "member": "CE::Memory::Objects::PoolState::return_block", + "access": "invalid", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 6 + }, + { + "source": "%logging", + "target": "%state", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/logging/backend-log.h", + "line": 364, + "member": "CE::Logging::BackendLog::BackendLog", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/logging/backend-log.h", + "line": 365, + "member": "CE::Logging::BackendLog::BackendLog", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "inherits", + "file": "projects/engine/src/core/logging/backend-log.h", + "line": 234, + "access": "public", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Logging::BackendLog", + "target": "State::Singleton", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/logging/backend-log.h", + "line": 364, + "member": "CE::Logging::BackendLog::BackendLog", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/logging/backend-log.h", + "line": 365, + "member": "CE::Logging::BackendLog::BackendLog", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Logging::Detail::ThreadPoolInit", + "target": "State::Singleton", + "evidence": [ + { + "kind": "inherits", + "file": "projects/engine/src/core/logging/backend-log.h", + "line": 234, + "access": "public", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 3 + }, + { + "source": "%runtime", + "target": "%logging", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/runtime/startup/include/cheryl/application/startup.h", + "line": 81, + "member": "CE::Application::Startup::logging_lifetime_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/runtime/startup/include/cheryl/application/startup.h", + "line": 81, + "member": "CE::Application::Startup::logging_lifetime_", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 142, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 149, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 142, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 149, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/runtime-services.cpp", + "line": 66, + "member": "CE::Runtime::Detail::RuntimeServicesAccess::maintenance", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/runtime-services.cpp", + "line": 93, + "member": "CE::Runtime::Detail::RuntimeServicesAccess::step", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Application::Startup", + "target": "Logging::ProcessLifetime", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/runtime/startup/include/cheryl/application/startup.h", + "line": 81, + "member": "CE::Application::Startup::logging_lifetime_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/runtime/startup/include/cheryl/application/startup.h", + "line": 81, + "member": "CE::Application::Startup::logging_lifetime_", + "access": "private", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Logging::Compile::LogLevel", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 142, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 149, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Logging::Logger", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 142, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 149, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Detail::RuntimeServicesAccess", + "target": "Logging::Compile::LogLevel", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/runtime-services.cpp", + "line": 66, + "member": "CE::Runtime::Detail::RuntimeServicesAccess::maintenance", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/runtime-services.cpp", + "line": 93, + "member": "CE::Runtime::Detail::RuntimeServicesAccess::step", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Detail::RuntimeServicesAccess", + "target": "Logging::Logger", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/runtime-services.cpp", + "line": 66, + "member": "CE::Runtime::Detail::RuntimeServicesAccess::maintenance", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/runtime-services.cpp", + "line": 93, + "member": "CE::Runtime::Detail::RuntimeServicesAccess::step", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Detail::ShutdownController", + "target": "Logging::Compile::LogLevel", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/shutdown.cpp", + "line": 373, + "member": "CE::Runtime::Detail::ShutdownController::process", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/shutdown.cpp", + "line": 391, + "member": "CE::Runtime::Detail::ShutdownController::process", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Detail::ShutdownController", + "target": "Logging::Logger", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/shutdown.cpp", + "line": 373, + "member": "CE::Runtime::Detail::ShutdownController::process", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/shutdown.cpp", + "line": 391, + "member": "CE::Runtime::Detail::ShutdownController::process", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Detail::ViewSourceState", + "target": "Logging::Compile::LogLevel", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/view-state.cpp", + "line": 82, + "member": "CE::Runtime::Detail::ViewSourceState::select", + "access": "invalid", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 8 + }, + { + "source": "%workers", + "target": "%logging", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 245, + "member": "CE::Workers::Group::report_diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 252, + "member": "CE::Workers::Group::report_diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 245, + "member": "CE::Workers::Group::report_diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 252, + "member": "CE::Workers::Group::report_diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 355, + "member": "CE::Workers::Pool::report_diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 355, + "member": "CE::Workers::Pool::report_diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Workers::Group", + "target": "Logging::Compile::LogLevel", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 245, + "member": "CE::Workers::Group::report_diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 252, + "member": "CE::Workers::Group::report_diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Workers::Group", + "target": "Logging::Logger", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 245, + "member": "CE::Workers::Group::report_diagnostics", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 252, + "member": "CE::Workers::Group::report_diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Workers::Pool", + "target": "Logging::Compile::LogLevel", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 355, + "member": "CE::Workers::Pool::report_diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Workers::Pool", + "target": "Logging::Logger", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/worker-pool.cpp", + "line": 355, + "member": "CE::Workers::Pool::report_diagnostics", + "access": "invalid", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 6 + }, + { + "source": "%runtime", + "target": "%diagnostics", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 142, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 164, + "member": "CE::Runtime::Context::workers_idle", + "access": "invalid", + "via": "libclang AST" + }, + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 80, + "member": "Runtime::Context::domain_", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 120, + "member": "Runtime::Context::diagnostic_id", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 120, + "member": "Runtime::Context::diagnostic_id()", + "via": "operation signature" + }, + { + "file": "projects/engine/include/cheryl/core/game-framework/runtime-lifecycle.h", + "line": 24, + "member": "Runtime::RuntimeStats::domain", + "via": "API declaration" + } + ], + "examples": [ + { + "source": "Runtime::Context", + "target": "Workers::GroupStatus", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 142, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 164, + "member": "CE::Runtime::Context::workers_idle", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Diagnostics::DomainId", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 80, + "member": "Runtime::Context::domain_", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 120, + "member": "Runtime::Context::diagnostic_id", + "via": "API declaration" + } + ] + }, + { + "source": "Runtime::Context::diagnostic_id()", + "target": "Diagnostics::DomainId", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 120, + "member": "Runtime::Context::diagnostic_id()", + "via": "operation signature" + } + ] + }, + { + "source": "Runtime::RuntimeStats", + "target": "Diagnostics::DomainId", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/game-framework/runtime-lifecycle.h", + "line": 24, + "member": "Runtime::RuntimeStats::domain", + "via": "API declaration" + } + ] + } + ], + "overview": true, + "evidence_count": 6 + }, + { + "source": "%dispatch", + "target": "%diagnostics", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 103, + "member": "CE::Runtime::PlatformDispatcher::diagnostics", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 41, + "member": "CE::Runtime::PlatformDispatcher::State::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 102, + "member": "CE::Runtime::SimulationDispatcher::diagnostics", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 41, + "member": "CE::Runtime::SimulationDispatcher::State::diagnostics", + "access": "public", + "via": "libclang AST" + }, + { + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 103, + "member": "Runtime::PlatformDispatcher::diagnostics", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 103, + "member": "Runtime::PlatformDispatcher::diagnostics()", + "via": "operation signature" + } + ], + "examples": [ + { + "source": "Runtime::PlatformDispatcher", + "target": "Diagnostics::DispatchStats", + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 103, + "member": "CE::Runtime::PlatformDispatcher::diagnostics", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 41, + "member": "CE::Runtime::PlatformDispatcher::State::diagnostics", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::SimulationDispatcher", + "target": "Diagnostics::DispatchStats", + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 102, + "member": "CE::Runtime::SimulationDispatcher::diagnostics", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 41, + "member": "CE::Runtime::SimulationDispatcher::State::diagnostics", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::PlatformDispatcher", + "target": "Diagnostics::DispatchStats", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 103, + "member": "Runtime::PlatformDispatcher::diagnostics", + "via": "API declaration" + } + ] + }, + { + "source": "Runtime::PlatformDispatcher::diagnostics()", + "target": "Diagnostics::DispatchStats", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/platform-dispatcher.h", + "line": 103, + "member": "Runtime::PlatformDispatcher::diagnostics()", + "via": "operation signature" + } + ] + }, + { + "source": "Runtime::SimulationDispatcher", + "target": "Diagnostics::DispatchStats", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 102, + "member": "Runtime::SimulationDispatcher::diagnostics", + "via": "API declaration" + } + ] + }, + { + "source": "Runtime::SimulationDispatcher::diagnostics()", + "target": "Diagnostics::DispatchStats", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/simulation-dispatcher.h", + "line": 102, + "member": "Runtime::SimulationDispatcher::diagnostics()", + "via": "operation signature" + } + ] + } + ], + "overview": true, + "evidence_count": 6 + } + ], + "notes": [ + "Responsibilities are not CMake libraries or universal manager objects. Arrows select architectural dependencies; optional capabilities are explained by the detailed views.", + "Audio output remains separately owned. Asset worker scheduling belongs to application wiring; Events uses explicit delivery adapters." + ], + "level": "detailed", + "refs": [ + "docs/site/engine-systems/index.md", + "docs/site/architecture/interfaces-adapters.md" + ], + "nodes": [ + "%runtime", + "%display", + "%input", + "%rendering", + "%resources", + "%text", + "%events", + "%dispatch", + "%workers", + "%assets", + "%gui", + "%memory", + "%utilities", + "%state", + "%audio", + "%logging", + "%diagnostics" + ] + }, + { + "id": "overview-execution", + "system": "overview", + "view": "execution", + "title": "Subsystems \u00b7 simple execution boundary", + "question": "What does runtime coordinate, and where does optional queued work enter?", + "edges": [ + { + "source": "%runtime", + "target": "%display", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/runtime/startup/include/cheryl/application/startup.h", + "line": 39, + "member": "CE::Application::ApplicationOptions::mode", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/runtime/startup/include/cheryl/application/startup.h", + "line": 39, + "member": "CE::Application::ApplicationOptions::mode", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-backend.h", + "line": 20, + "member": "CE::Application::GLFWOpenGL::Options::mode", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-backend.h", + "line": 20, + "member": "CE::Application::GLFWOpenGL::Options::mode", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 27, + "member": "CE::Game::TickContext::logical_size", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 27, + "member": "CE::Game::TickContext::logical_size", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 24, + "member": "CE::Game::TickContext::framebuffer_size", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 24, + "member": "CE::Game::TickContext::framebuffer_size", + "access": "public", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Application::ApplicationOptions", + "target": "Platform::WindowMode", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/runtime/startup/include/cheryl/application/startup.h", + "line": 39, + "member": "CE::Application::ApplicationOptions::mode", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/runtime/startup/include/cheryl/application/startup.h", + "line": 39, + "member": "CE::Application::ApplicationOptions::mode", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Application::GLFWOpenGL::Options", + "target": "Platform::WindowMode", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-backend.h", + "line": 20, + "member": "CE::Application::GLFWOpenGL::Options::mode", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-backend.h", + "line": 20, + "member": "CE::Application::GLFWOpenGL::Options::mode", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Game::TickContext", + "target": "Platform::Dimensions", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 27, + "member": "CE::Game::TickContext::logical_size", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 27, + "member": "CE::Game::TickContext::logical_size", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Game::TickContext", + "target": "Platform::FramebufferSize", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 24, + "member": "CE::Game::TickContext::framebuffer_size", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 24, + "member": "CE::Game::TickContext::framebuffer_size", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Platform::DisplaySystem", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 65, + "member": "CE::Runtime::Context::display_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Platform::Window", + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 109, + "member": "CE::Runtime::Context::window", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 109, + "member": "CE::Runtime::Context::window", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Detail::ShutdownController", + "target": "Platform::WindowCloseRequested", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/src/core/game-framework/shutdown-internal.h", + "line": 22, + "member": "CE::Runtime::Detail::ShutdownController::window_close", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/shutdown-internal.h", + "line": 22, + "member": "CE::Runtime::Detail::ShutdownController::window_close", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::GameRuntime", + "target": "Platform::Dimensions", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 414, + "member": "CE::Runtime::GameRuntime::select_window", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 425, + "member": "CE::Runtime::GameRuntime::select_simulation_view", + "access": "invalid", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 8 + }, + { + "source": "%runtime", + "target": "%input", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/runtime/startup/include/cheryl/application/startup.h", + "line": 20, + "member": "CE::Application::RuntimeOptions::polling", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/runtime/startup/include/cheryl/application/startup.h", + "line": 20, + "member": "CE::Application::RuntimeOptions::polling", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 132, + "member": "CE::Application::Startup::validate_configuration", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 94, + "member": "CE::Application::Startup::add_engine_options", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 103, + "member": "CE::Application::Startup::add_engine_options", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 94, + "member": "CE::Application::Startup::add_engine_options", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 103, + "member": "CE::Application::Startup::add_engine_options", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Application::RuntimeOptions", + "target": "Input::PollingOptions", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/runtime/startup/include/cheryl/application/startup.h", + "line": 20, + "member": "CE::Application::RuntimeOptions::polling", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/runtime/startup/include/cheryl/application/startup.h", + "line": 20, + "member": "CE::Application::RuntimeOptions::polling", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Application::Startup", + "target": "Input::PollingBacklog", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 132, + "member": "CE::Application::Startup::validate_configuration", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Application::Startup", + "target": "Input::PollingOptions", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 94, + "member": "CE::Application::Startup::add_engine_options", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 103, + "member": "CE::Application::Startup::add_engine_options", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Application::Startup", + "target": "Input::PollingPolicy", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 94, + "member": "CE::Application::Startup::add_engine_options", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 103, + "member": "CE::Application::Startup::add_engine_options", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Application::StartupResult", + "target": "Input::PollingOptions", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/runtime/startup/src/startup.cpp", + "line": 14, + "member": "CE::Application::StartupResult::make_runtime", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Game::TickContext", + "target": "Input::Actions::ButtonTickState", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/tick-context.cpp", + "line": 19, + "member": "CE::Game::TickContext::button_simulation_seconds", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/tick-context.cpp", + "line": 21, + "member": "CE::Game::TickContext::button_simulation_seconds", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Game::TickContext", + "target": "Input::Actions::Id", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 34, + "member": "CE::Game::TickContext::button_simulation_seconds", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 34, + "member": "CE::Game::TickContext::button_simulation_seconds", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Game::TickContext", + "target": "Input::Tick", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 23, + "member": "CE::Game::TickContext::input", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/tick-context.h", + "line": 23, + "member": "CE::Game::TickContext::input", + "access": "public", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 7 + }, + { + "source": "%runtime", + "target": "%rendering", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 34, + "member": "CE::Game::AbstractGame::prepare_render_frame", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 34, + "member": "CE::Game::AbstractGame::prepare_render_frame", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 66, + "member": "CE::Runtime::Context::surface_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 67, + "member": "CE::Runtime::Context::renderer_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 447, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 586, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Game::AbstractGame", + "target": "Rendering::FrameWriter", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 34, + "member": "CE::Game::AbstractGame::prepare_render_frame", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 34, + "member": "CE::Game::AbstractGame::prepare_render_frame", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Rendering::PresentationSurface", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 66, + "member": "CE::Runtime::Context::surface_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Rendering::Renderer", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 67, + "member": "CE::Runtime::Context::renderer_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 85, + "member": "CE::Runtime::Context::Context", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "Runtime::GameRuntime", + "target": "Rendering::Frame", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 447, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 586, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::GameRuntime", + "target": "Rendering::FrameWriter", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 587, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 880, + "member": "CE::Runtime::GameRuntime::run_concurrent", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::GameRuntime", + "target": "Rendering::PresentationSurface", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 596, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 1054, + "member": "CE::Runtime::GameRuntime::run_concurrent", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::GameRuntime", + "target": "Rendering::Renderer", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 401, + "member": "CE::Runtime::GameRuntime::bind_renderer_maintenance", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 445, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Game::AbstractGame", + "target": "Rendering::FrameWriter", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/game-framework/abstract-game.h", + "line": 34, + "member": "Game::AbstractGame::prepare_render_frame", + "via": "API declaration" + } + ] + } + ], + "overview": true, + "evidence_count": 8 + }, + { + "source": "%runtime", + "target": "%dispatch", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 73, + "member": "CE::Runtime::Context::platform_dispatcher_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 115, + "member": "CE::Runtime::Context::platform_dispatcher", + "access": "public", + "via": "member declaration" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 91, + "member": "CE::Runtime::Context::begin_session", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 464, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 489, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 84, + "member": "CE::Runtime::GameRuntime::simulation_dispatcher_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 119, + "member": "CE::Runtime::GameRuntime::simulation_dispatcher", + "access": "public", + "via": "member declaration" + } + ], + "examples": [ + { + "source": "Runtime::Context", + "target": "Runtime::PlatformDispatcher", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 73, + "member": "CE::Runtime::Context::platform_dispatcher_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 115, + "member": "CE::Runtime::Context::platform_dispatcher", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Runtime::PlatformDispatcher::Submission", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 91, + "member": "CE::Runtime::Context::begin_session", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::GameRuntime", + "target": "Runtime::PlatformDispatcher", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 464, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/game-framework/game-runtime.cpp", + "line": 489, + "member": "CE::Runtime::GameRuntime::run_sequential", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::GameRuntime", + "target": "Runtime::SimulationDispatcher", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 84, + "member": "CE::Runtime::GameRuntime::simulation_dispatcher_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 119, + "member": "CE::Runtime::GameRuntime::simulation_dispatcher", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Runtime::PlatformDispatcher", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 73, + "member": "Runtime::Context::platform_dispatcher_", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 115, + "member": "Runtime::Context::platform_dispatcher", + "via": "API declaration" + } + ] + }, + { + "source": "Runtime::Context::platform_dispatcher()", + "target": "Runtime::PlatformDispatcher", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 115, + "member": "Runtime::Context::platform_dispatcher()", + "via": "operation signature" + } + ] + }, + { + "source": "Runtime::GameRuntime", + "target": "Runtime::SimulationDispatcher", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 84, + "member": "Runtime::GameRuntime::simulation_dispatcher_", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 119, + "member": "Runtime::GameRuntime::simulation_dispatcher", + "via": "API declaration" + } + ] + }, + { + "source": "Runtime::GameRuntime::simulation_dispatcher()", + "target": "Runtime::SimulationDispatcher", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/game-framework/game-runtime.h", + "line": 119, + "member": "Runtime::GameRuntime::simulation_dispatcher()", + "via": "operation signature" + } + ] + } + ], + "overview": true, + "evidence_count": 7 + }, + { + "source": "%runtime", + "target": "%workers", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 78, + "member": "CE::Runtime::Context::worker_groups_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 76, + "member": "CE::Runtime::Context::owned_worker_factory_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 77, + "member": "CE::Runtime::Context::owned_workers_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 28, + "member": "CE::Runtime::Detail::ContextAccess::set_owned_worker_factory", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Runtime::Context", + "target": "Workers::Group", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 78, + "member": "CE::Runtime::Context::worker_groups_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Workers::GroupOptions", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "CE::Runtime::Context::make_worker_group", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Workers::Pool", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 76, + "member": "CE::Runtime::Context::owned_worker_factory_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 77, + "member": "CE::Runtime::Context::owned_workers_", + "access": "private", + "via": "member declaration" + } + ] + }, + { + "source": "Runtime::Detail::ContextAccess", + "target": "Workers::Group", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context.cpp", + "line": 28, + "member": "CE::Runtime::Detail::ContextAccess::set_owned_worker_factory", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Detail::ContextAccess", + "target": "Workers::Pool", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/src/core/engine/engine-context-internal.h", + "line": 16, + "member": "CE::Runtime::Detail::ContextAccess::set_owned_worker_factory", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/engine/engine-context-internal.h", + "line": 16, + "member": "CE::Runtime::Detail::ContextAccess::set_owned_worker_factory", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::ExecutionOptions", + "target": "Workers::Pool", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 49, + "member": "CE::Runtime::ExecutionOptions::shared_pool", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 49, + "member": "CE::Runtime::ExecutionOptions::shared_pool", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Workers::Pool", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 76, + "member": "Runtime::Context::owned_worker_factory_", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 77, + "member": "Runtime::Context::owned_workers_", + "via": "API declaration" + } + ] + }, + { + "source": "Runtime::Context", + "target": "Workers::Group", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 78, + "member": "Runtime::Context::worker_groups_", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/engine/engine-context.h", + "line": 124, + "member": "Runtime::Context::make_worker_group", + "via": "API declaration" + } + ] + } + ], + "overview": true, + "evidence_count": 7 + }, + { + "source": "%input", + "target": "%display", + "optional": false, + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 32, + "member": "CE::Input::System::initialize", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 32, + "member": "CE::Input::System::initialize", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 352, + "member": "CE::Platform::GLFW::InputSystem::initialize", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 353, + "member": "CE::Platform::GLFW::InputSystem::initialize", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 207, + "member": "CE::Platform::GLFW::InputSystem::State::gainput_lifetime_", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 311, + "member": "CE::Platform::GLFW::InputSystem::initialize", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 209, + "member": "CE::Platform::GLFW::InputSystem::State::bindings_", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 246, + "member": "CE::Platform::GLFW::InputSystem::bindings", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Input::System", + "target": "Platform::Window", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 32, + "member": "CE::Input::System::initialize", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/controls/input-interface.h", + "line": 32, + "member": "CE::Input::System::initialize", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Platform::GLFW::InputSystem", + "target": "Platform::Dimensions", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 352, + "member": "CE::Platform::GLFW::InputSystem::initialize", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 353, + "member": "CE::Platform::GLFW::InputSystem::initialize", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Platform::GLFW::InputSystem", + "target": "Platform::GLFW::Detail::GainputLifetime", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 207, + "member": "CE::Platform::GLFW::InputSystem::State::gainput_lifetime_", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 311, + "member": "CE::Platform::GLFW::InputSystem::initialize", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Platform::GLFW::InputSystem", + "target": "Platform::GLFW::Detail::GainputMapper", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 209, + "member": "CE::Platform::GLFW::InputSystem::State::bindings_", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 246, + "member": "CE::Platform::GLFW::InputSystem::bindings", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Platform::GLFW::InputSystem", + "target": "Platform::GLFW::Detail::GlfwKeyboardDevice", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 326, + "member": "CE::Platform::GLFW::InputSystem::initialize", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Platform::GLFW::InputSystem", + "target": "Platform::GLFW::Detail::GlfwMouseDevice", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 330, + "member": "CE::Platform::GLFW::InputSystem::initialize", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Platform::GLFW::InputSystem", + "target": "Platform::GLFW::Detail::InputDevice", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 212, + "member": "CE::Platform::GLFW::InputSystem::State::keyboard_", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/platform/native-glfw/src/core/controls/input-system.cpp", + "line": 213, + "member": "CE::Platform::GLFW::InputSystem::State::mouse_", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Platform::GLFW::InputSystem", + "target": "Platform::GLFW::DeviceSnapshot", + "evidence": [ + { + "kind": "returns", + "file": "projects/modules/platform/native-glfw/include/cheryl/core/controls/input-system.h", + "line": 85, + "member": "CE::Platform::GLFW::InputSystem::device", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/platform/native-glfw/include/cheryl/core/controls/input-system.h", + "line": 85, + "member": "CE::Platform::GLFW::InputSystem::device", + "access": "public", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 8 + }, + { + "source": "%rendering", + "target": "%display", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/rendering/camera.cpp", + "line": 42, + "member": "CE::Rendering::Camera2D::recalculate_projection", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/rendering/camera.cpp", + "line": 43, + "member": "CE::Rendering::Camera2D::recalculate_projection", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/rendering/camera.cpp", + "line": 66, + "member": "CE::Rendering::Camera3D::recalculate_projection", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/rendering/camera.cpp", + "line": 67, + "member": "CE::Rendering::Camera3D::recalculate_projection", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/camera.h", + "line": 35, + "member": "CE::Rendering::CameraBase::framebuffer_size_", + "access": "protected", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/camera.h", + "line": 22, + "member": "CE::Rendering::CameraBase::set_framebuffer_size", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-context.h", + "line": 26, + "member": "CE::Rendering::OpenGL::GLFWContext::window_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-context.h", + "line": 17, + "member": "CE::Rendering::OpenGL::GLFWContext::GLFWContext", + "access": "public", + "via": "member declaration" + } + ], + "examples": [ + { + "source": "Rendering::Camera2D", + "target": "Platform::FramebufferSize", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/rendering/camera.cpp", + "line": 42, + "member": "CE::Rendering::Camera2D::recalculate_projection", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/rendering/camera.cpp", + "line": 43, + "member": "CE::Rendering::Camera2D::recalculate_projection", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Rendering::Camera3D", + "target": "Platform::FramebufferSize", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/rendering/camera.cpp", + "line": 66, + "member": "CE::Rendering::Camera3D::recalculate_projection", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/rendering/camera.cpp", + "line": 67, + "member": "CE::Rendering::Camera3D::recalculate_projection", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Rendering::CameraBase", + "target": "Platform::FramebufferSize", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/camera.h", + "line": 35, + "member": "CE::Rendering::CameraBase::framebuffer_size_", + "access": "protected", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/camera.h", + "line": 22, + "member": "CE::Rendering::CameraBase::set_framebuffer_size", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "Rendering::OpenGL::GLFWContext", + "target": "Platform::GLFW::Window", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-context.h", + "line": 26, + "member": "CE::Rendering::OpenGL::GLFWContext::window_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/glfw-context.h", + "line": 17, + "member": "CE::Rendering::OpenGL::GLFWContext::GLFWContext", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "Rendering::OpenGL::Renderer", + "target": "Platform::FramebufferSize", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/renderer.h", + "line": 30, + "member": "CE::Rendering::OpenGL::Renderer::viewport_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/graphics/opengl/include/cheryl/backends/opengl/renderer.h", + "line": 56, + "member": "CE::Rendering::OpenGL::Renderer::set_viewport", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "Rendering::Renderer", + "target": "Platform::FramebufferSize", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/renderer.h", + "line": 48, + "member": "CE::Rendering::Renderer::set_viewport", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/core/rendering/renderer.h", + "line": 48, + "member": "CE::Rendering::Renderer::set_viewport", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Rendering::CameraBase", + "target": "Platform::FramebufferSize", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/rendering/camera.h", + "line": 35, + "member": "Rendering::CameraBase::framebuffer_size_", + "via": "API declaration" + }, + { + "file": "projects/engine/include/cheryl/core/rendering/camera.h", + "line": 22, + "member": "Rendering::CameraBase::set_framebuffer_size", + "via": "API declaration" + } + ] + }, + { + "source": "Rendering::CameraBase::set_framebuffer_size()", + "target": "Platform::FramebufferSize", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/rendering/camera.h", + "line": 22, + "member": "Rendering::CameraBase::set_framebuffer_size()", + "via": "operation signature" + } + ] + } + ], + "overview": true, + "evidence_count": 8 + }, + { + "source": "%events", + "target": "%dispatch", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 15, + "member": "Events::platform_delivery()", + "via": "operation signature" + }, + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 16, + "member": "Events::simulation_delivery()", + "via": "operation signature" + } + ], + "examples": [ + { + "source": "Events::platform_delivery()", + "target": "Runtime::PlatformDispatcher::Submission", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 15, + "member": "Events::platform_delivery()", + "via": "operation signature" + } + ] + }, + { + "source": "Events::simulation_delivery()", + "target": "Runtime::SimulationDispatcher::Submission", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 16, + "member": "Events::simulation_delivery()", + "via": "operation signature" + } + ] + } + ], + "overview": true, + "evidence_count": 2 + }, + { + "source": "%events", + "target": "%workers", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 19, + "member": "Events::worker_delivery()", + "via": "operation signature" + } + ], + "examples": [ + { + "source": "Events::worker_delivery()", + "target": "Workers::Group", + "evidence": [ + { + "file": "projects/engine/include/cheryl/core/engine/event-delivery.h", + "line": 19, + "member": "Events::worker_delivery()", + "via": "operation signature" + } + ] + } + ], + "overview": true, + "evidence_count": 1 + } + ], + "notes": [ + "The dedicated simulation owner is separate from general worker capacity. Display/graphics remain on platform.", + "These are dependency arrows, not a schedule. The runtime/dispatch/worker pages define actual draining and shutdown order." + ], + "level": "simple", + "refs": [ + "docs/site/architecture/threads-data-flow.md" + ], + "nodes": [ + "%runtime", + "%display", + "%input", + "%rendering", + "%dispatch", + "%workers", + "%events" + ] + }, + { + "id": "overview-content", + "system": "overview", + "view": "content", + "title": "Subsystems \u00b7 simple content and presentation", + "question": "Which responsibilities meet when visual content becomes retained draws?", + "edges": [ + { + "source": "%assets", + "target": "%resources", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 106, + "member": "CE::Assets::Cache::AssetCatalogue::State::domain", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 133, + "member": "CE::Assets::Cache::AssetCatalogue::require_domain", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 133, + "member": "CE::Assets::Cache::AssetCatalogue::require_domain", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 134, + "member": "CE::Assets::Cache::AssetCatalogue::require_domain", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 134, + "member": "CE::Assets::Cache::AssetCatalogue::require_domain", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 32, + "member": "CE::Assets::Cache::CatalogueGeneration::graphics_domain", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 32, + "member": "CE::Assets::Cache::CatalogueGeneration::graphics_domain", + "access": "public", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Assets::Cache::AssetCatalogue", + "target": "Rendering::GraphicsDomain", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 106, + "member": "CE::Assets::Cache::AssetCatalogue::State::domain", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 133, + "member": "CE::Assets::Cache::AssetCatalogue::require_domain", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Cache::AssetCatalogue", + "target": "Rendering::GraphicsDomainSnapshot", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 133, + "member": "CE::Assets::Cache::AssetCatalogue::require_domain", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 134, + "member": "CE::Assets::Cache::AssetCatalogue::require_domain", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Cache::AssetCatalogue", + "target": "Rendering::GraphicsDomainStatus", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 134, + "member": "CE::Assets::Cache::AssetCatalogue::require_domain", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Cache::CatalogueGeneration", + "target": "Rendering::GraphicsDomain", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 32, + "member": "CE::Assets::Cache::CatalogueGeneration::graphics_domain", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/resources/asset-management/asset-catalogue.h", + "line": 32, + "member": "CE::Assets::Cache::CatalogueGeneration::graphics_domain", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Cache::Context", + "target": "Rendering::GraphicsDomain", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 52, + "member": "CE::Assets::Cache::Context::bound_domain_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 52, + "member": "CE::Assets::Cache::Context::bound_domain_", + "access": "private", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Cache::Context", + "target": "Rendering::GraphicsDomainSnapshot", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 94, + "member": "CE::Assets::Cache::Context::verify_locked", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 94, + "member": "CE::Assets::Cache::Context::verify_locked", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Cache::Context", + "target": "Rendering::GraphicsDomainStateChanged", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 62, + "member": "CE::Assets::Cache::Context::evict_graphics_domain", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/templates/asset-mgr.h", + "line": 62, + "member": "CE::Assets::Cache::Context::evict_graphics_domain", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Assets::Cache::Context", + "target": "Rendering::GraphicsDomainStatus", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/assets/resources/resource-provider.cpp", + "line": 104, + "member": "CE::Assets::Cache::Context::verify_locked", + "access": "invalid", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 7 + }, + { + "source": "%text", + "target": "%resources", + "optional": false, + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 61, + "member": "CE::Text::Legacy::AsciiFont::glyph_geometry", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 61, + "member": "CE::Text::Legacy::AsciiFont::glyph_geometry", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 62, + "member": "CE::Text::Legacy::AsciiFont::glyph_atlas", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 62, + "member": "CE::Text::Legacy::AsciiFont::glyph_atlas", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 54, + "member": "CE::Text::Legacy::AsciiFont::load_font", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 58, + "member": "CE::Text::Legacy::AsciiFont::load_font", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 54, + "member": "CE::Text::Legacy::AsciiFont::load_font", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 57, + "member": "CE::Text::Legacy::AsciiFont::load_font", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Text::Legacy::AsciiFont", + "target": "Rendering::Geometry::Buffer2D", + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 61, + "member": "CE::Text::Legacy::AsciiFont::glyph_geometry", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 61, + "member": "CE::Text::Legacy::AsciiFont::glyph_geometry", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Text::Legacy::AsciiFont", + "target": "Rendering::Resources::Image", + "evidence": [ + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 62, + "member": "CE::Text::Legacy::AsciiFont::glyph_atlas", + "access": "public", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 62, + "member": "CE::Text::Legacy::AsciiFont::glyph_atlas", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Text::Legacy::AsciiFont", + "target": "Rendering::Resources::Material", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 54, + "member": "CE::Text::Legacy::AsciiFont::load_font", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 58, + "member": "CE::Text::Legacy::AsciiFont::load_font", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Text::Legacy::AsciiFont", + "target": "Rendering::Resources::Provider", + "evidence": [ + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 54, + "member": "CE::Text::Legacy::AsciiFont::load_font", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 57, + "member": "CE::Text::Legacy::AsciiFont::load_font", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Text::Legacy::AsciiFontData", + "target": "Rendering::Geometry::Buffer2D", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 29, + "member": "CE::Text::Legacy::AsciiFontData::geometry", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 29, + "member": "CE::Text::Legacy::AsciiFontData::geometry", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Text::Legacy::AsciiFontData", + "target": "Rendering::Resources::Image", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 30, + "member": "CE::Text::Legacy::AsciiFontData::texture", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 30, + "member": "CE::Text::Legacy::AsciiFontData::texture", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Text::Legacy::AsciiFontData", + "target": "Rendering::Resources::Material", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 33, + "member": "CE::Text::Legacy::AsciiFontData::material", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/types/2d/stbfont.h", + "line": 33, + "member": "CE::Text::Legacy::AsciiFontData::material", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Text::Legacy::BitmapFont", + "target": "Rendering::Geometry::Buffer2D", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/include/cheryl/assets/types/2d/ffont.h", + "line": 43, + "member": "CE::Text::Legacy::BitmapFont::BitmapFont", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/assets/types/2d/ffont.cpp", + "line": 74, + "member": "CE::Text::Legacy::BitmapFont::load_ffont", + "access": "invalid", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 8 + }, + { + "source": "%gui", + "target": "%resources", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 23, + "member": "CE::UI::RmlUi::Materials::solid", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 24, + "member": "CE::UI::RmlUi::Materials::textured", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 21, + "member": "CE::UI::RmlUi::RecordedDraw::texture", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 21, + "member": "CE::UI::RmlUi::RecordedDraw::texture", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 49, + "member": "CE::UI::RmlUi::RenderTarget::textures_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 92, + "member": "CE::UI::RmlUi::RenderTarget::store_texture", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 74, + "member": "CE::UI::RmlUi::SceneUploader::State::images", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 122, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "UI::RmlUi::Materials", + "target": "Rendering::Resources::Material", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 23, + "member": "CE::UI::RmlUi::Materials::solid", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 24, + "member": "CE::UI::RmlUi::Materials::textured", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "UI::RmlUi::RecordedDraw", + "target": "Assets::Images::Decoded", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 21, + "member": "CE::UI::RmlUi::RecordedDraw::texture", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 21, + "member": "CE::UI::RmlUi::RecordedDraw::texture", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::RenderTarget", + "target": "Assets::Images::Decoded", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 49, + "member": "CE::UI::RmlUi::RenderTarget::textures_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 92, + "member": "CE::UI::RmlUi::RenderTarget::store_texture", + "access": "private", + "via": "member declaration" + } + ] + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "Assets::Images::Decoded", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 74, + "member": "CE::UI::RmlUi::SceneUploader::State::images", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 122, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "Assets::Shaders::Builder", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 89, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 93, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "Rendering::Geometry::Buffer2D", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 142, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 144, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "Rendering::Resources::Image", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 74, + "member": "CE::UI::RmlUi::SceneUploader::State::images", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 122, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::SceneUploader", + "target": "Rendering::Resources::Material", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 93, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/scene.cpp", + "line": 96, + "member": "CE::UI::RmlUi::SceneUploader::upload", + "access": "invalid", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 8 + }, + { + "source": "%gui", + "target": "%input", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 189, + "member": "CE::UI::RmlUi::Session::State", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 190, + "member": "CE::UI::RmlUi::Session::State", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 166, + "member": "CE::UI::RmlUi::Session::State::events", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 167, + "member": "CE::UI::RmlUi::Session::State::text", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 460, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 462, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 459, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 461, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "UI::RmlUi::Session", + "target": "Input::Capture::Channel", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 189, + "member": "CE::UI::RmlUi::Session::State", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 190, + "member": "CE::UI::RmlUi::Session::State", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::Session", + "target": "Input::Capture::Lease", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 166, + "member": "CE::UI::RmlUi::Session::State::events", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 167, + "member": "CE::UI::RmlUi::Session::State::text", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::Session", + "target": "Input::DeviceKind", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 460, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 462, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::Session", + "target": "Input::Events::Button", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 459, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 461, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::Session", + "target": "Input::Events::ButtonPhase", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 474, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 486, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::Session", + "target": "Input::Events::Payload", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 459, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 466, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::Session", + "target": "Input::Events::Pointer", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 481, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 490, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::Session", + "target": "Input::Events::Record", + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 93, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 97, + "member": "CE::UI::RmlUi::Session::handle_input", + "access": "public", + "via": "member declaration" + } + ] + } + ], + "overview": true, + "evidence_count": 8 + }, + { + "source": "%gui", + "target": "%text", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/src/font-service.cpp", + "line": 43, + "member": "CE::UI::RmlUi::Detail::@anon(projects/modules/ui/rmlui/src/font-service.cpp)::Face::rasterizer", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 79, + "member": "CE::UI::RmlUi::Session::font_family", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 80, + "member": "CE::UI::RmlUi::Session::set_font", + "access": "public", + "via": "member declaration" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 208, + "member": "CE::UI::RmlUi::Session::reserve_font", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 220, + "member": "CE::UI::RmlUi::Session::font_family", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 61, + "member": "CE::UI::RmlUi::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 63, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "UI::RmlUi::Detail::(anonymous)::Face", + "target": "Text::FontRasterizer", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/src/font-service.cpp", + "line": 43, + "member": "CE::UI::RmlUi::Detail::@anon(projects/modules/ui/rmlui/src/font-service.cpp)::Face::rasterizer", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::Session", + "target": "Text::Font", + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 79, + "member": "CE::UI::RmlUi::Session::font_family", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 80, + "member": "CE::UI::RmlUi::Session::set_font", + "access": "public", + "via": "member declaration" + } + ] + }, + { + "source": "UI::RmlUi::Session", + "target": "Text::FontFace", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 208, + "member": "CE::UI::RmlUi::Session::reserve_font", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/session.cpp", + "line": 220, + "member": "CE::UI::RmlUi::Session::font_family", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::Session", + "target": "Text::FontService", + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 61, + "member": "CE::UI::RmlUi::Session::Session", + "access": "public", + "via": "member declaration" + }, + { + "kind": "accepts", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/session.h", + "line": 63, + "member": "CE::UI::RmlUi::Session::Session", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::TGUI::Detail::FontBackend", + "target": "Text::Font", + "evidence": [ + { + "kind": "accepts", + "file": "projects/modules/ui/tgui/src/font.h", + "line": 49, + "member": "CE::UI::TGUI::Detail::FontBackend::load", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/font.h", + "line": 49, + "member": "CE::UI::TGUI::Detail::FontBackend::load", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::TGUI::Detail::FontBackend", + "target": "Text::FontFace", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/font.cpp", + "line": 87, + "member": "CE::UI::TGUI::Detail::FontBackend::load", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/font.cpp", + "line": 90, + "member": "CE::UI::TGUI::Detail::FontBackend::load", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::TGUI::Detail::FontBackend", + "target": "Text::FontRasterGlyph", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/font.cpp", + "line": 147, + "member": "CE::UI::TGUI::Detail::FontBackend::glyph", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/font.cpp", + "line": 148, + "member": "CE::UI::TGUI::Detail::FontBackend::glyph", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::TGUI::Detail::FontBackend", + "target": "Text::FontRasterMetrics", + "evidence": [ + { + "kind": "returns", + "file": "projects/modules/ui/tgui/src/font.h", + "line": 43, + "member": "CE::UI::TGUI::Detail::FontBackend::metrics", + "access": "private", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/tgui/src/font.h", + "line": 43, + "member": "CE::UI::TGUI::Detail::FontBackend::metrics", + "access": "invalid", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 7 + }, + { + "source": "%gui", + "target": "%rendering", + "optional": false, + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 27, + "member": "CE::UI::RmlUi::Materials::pass_parameters", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 27, + "member": "CE::UI::RmlUi::Materials::pass_parameters", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 22, + "member": "CE::UI::RmlUi::RecordedDraw::clip", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 22, + "member": "CE::UI::RmlUi::RecordedDraw::clip", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 20, + "member": "CE::UI::RmlUi::RecordedDraw::vertices", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 20, + "member": "CE::UI::RmlUi::RecordedDraw::vertices", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 47, + "member": "CE::UI::RmlUi::RenderTarget::scissor_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 47, + "member": "CE::UI::RmlUi::RenderTarget::scissor_", + "access": "private", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "UI::RmlUi::Materials", + "target": "Rendering::Parameters::Set", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 27, + "member": "CE::UI::RmlUi::Materials::pass_parameters", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 27, + "member": "CE::UI::RmlUi::Materials::pass_parameters", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::RecordedDraw", + "target": "Rendering::ClipRegion2D", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 22, + "member": "CE::UI::RmlUi::RecordedDraw::clip", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 22, + "member": "CE::UI::RmlUi::RecordedDraw::clip", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::RecordedDraw", + "target": "Rendering::Geometry::Vertex2DColor", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 20, + "member": "CE::UI::RmlUi::RecordedDraw::vertices", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 20, + "member": "CE::UI::RmlUi::RecordedDraw::vertices", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::RenderTarget", + "target": "Rendering::ClipRect2D", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 47, + "member": "CE::UI::RmlUi::RenderTarget::scissor_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 47, + "member": "CE::UI::RmlUi::RenderTarget::scissor_", + "access": "private", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::RenderTarget", + "target": "Rendering::ClipRegion2D", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/rendering.cpp", + "line": 113, + "member": "CE::UI::RmlUi::RenderTarget::RenderGeometry", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/rendering.cpp", + "line": 117, + "member": "CE::UI::RmlUi::RenderTarget::RenderGeometry", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::RenderTarget", + "target": "Rendering::Geometry::PixelSize", + "evidence": [ + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/rendering.cpp", + "line": 160, + "member": "CE::UI::RmlUi::RenderTarget::LoadTexture", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/modules/ui/rmlui/src/rendering.cpp", + "line": 162, + "member": "CE::UI::RmlUi::RenderTarget::LoadTexture", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::RenderTarget", + "target": "Rendering::Geometry::Vertex2DColor", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 48, + "member": "CE::UI::RmlUi::RenderTarget::geometry_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/rendering.h", + "line": 48, + "member": "CE::UI::RmlUi::RenderTarget::geometry_", + "access": "private", + "via": "libclang AST" + } + ] + }, + { + "source": "UI::RmlUi::Scene", + "target": "Rendering::DrawPacket2D", + "evidence": [ + { + "kind": "stores", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 35, + "member": "CE::UI::RmlUi::Scene::draws_", + "access": "private", + "via": "member declaration" + }, + { + "kind": "returns", + "file": "projects/modules/ui/rmlui/include/cheryl/ui/rmlui/scene.h", + "line": 38, + "member": "CE::UI::RmlUi::Scene::draws", + "access": "public", + "via": "member declaration" + } + ] + } + ], + "overview": true, + "evidence_count": 8 + }, + { + "source": "%rendering", + "target": "%assets", + "optional": false, + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + }, + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + }, + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 48, + "member": "Rendering::Submission::resolve_tile()", + "via": "operation signature" + }, + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 48, + "member": "Rendering::Submission::resolve_tile()", + "via": "operation signature" + } + ], + "examples": [ + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Assets::Sprites::Asset", + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + } + ] + }, + { + "source": "Rendering::Submission::resolve_sprite()", + "target": "Assets::Grids::CellIndex", + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 39, + "member": "Rendering::Submission::resolve_sprite()", + "via": "operation signature" + } + ] + }, + { + "source": "Rendering::Submission::resolve_tile()", + "target": "Assets::Tilesets::Asset", + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 48, + "member": "Rendering::Submission::resolve_tile()", + "via": "operation signature" + } + ] + }, + { + "source": "Rendering::Submission::resolve_tile()", + "target": "Assets::Grids::CellIndex", + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 48, + "member": "Rendering::Submission::resolve_tile()", + "via": "operation signature" + } + ] + }, + { + "source": "Rendering::Submission::resolve_tile()", + "target": "Assets::Tilesets::Tile", + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 55, + "member": "Rendering::Submission::resolve_tile()", + "via": "operation signature" + } + ] + }, + { + "source": "Rendering::Submission::resolve_tile()", + "target": "Assets::Tilesets::Animation", + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 57, + "member": "Rendering::Submission::resolve_tile()", + "via": "operation signature" + } + ] + }, + { + "source": "Rendering::Submission::resolve_graphic()", + "target": "Assets::Graphics::Asset", + "evidence": [ + { + "file": "projects/engine/include/cheryl/assets/submission/draw2d.h", + "line": 59, + "member": "Rendering::Submission::resolve_graphic()", + "via": "operation signature" + } + ] + } + ], + "overview": true, + "evidence_count": 4 + }, + { + "source": "%rendering", + "target": "%resources", + "optional": false, + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/rendering/renderer.cpp", + "line": 30, + "member": "CE::Rendering::@anon(projects/engine/src/core/rendering/renderer.cpp)::TerminalObservation::~TerminalObservation", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/src/core/rendering/renderer.cpp", + "line": 25, + "member": "CE::Rendering::@anon(projects/engine/src/core/rendering/renderer.cpp)::TerminalObservation::change", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/rendering/renderer.cpp", + "line": 30, + "member": "CE::Rendering::@anon(projects/engine/src/core/rendering/renderer.cpp)::TerminalObservation::~TerminalObservation", + "access": "invalid", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/src/core/rendering/graphics-domain.cpp", + "line": 13, + "member": "CE::Rendering::Detail::GraphicsDomainState::reason", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 33, + "member": "CE::Rendering::DrawPacket2D::geometry", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 33, + "member": "CE::Rendering::DrawPacket2D::geometry", + "access": "public", + "via": "libclang AST" + } + ], + "examples": [ + { + "source": "Rendering::(anonymous)::TerminalObservation", + "target": "Rendering::GraphicsDomainSnapshot", + "evidence": [ + { + "kind": "uses", + "file": "projects/engine/src/core/rendering/renderer.cpp", + "line": 30, + "member": "CE::Rendering::@anon(projects/engine/src/core/rendering/renderer.cpp)::TerminalObservation::~TerminalObservation", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Rendering::(anonymous)::TerminalObservation", + "target": "Rendering::GraphicsDomainStateChanged", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/src/core/rendering/renderer.cpp", + "line": 25, + "member": "CE::Rendering::@anon(projects/engine/src/core/rendering/renderer.cpp)::TerminalObservation::change", + "access": "public", + "via": "libclang AST" + }, + { + "kind": "uses", + "file": "projects/engine/src/core/rendering/renderer.cpp", + "line": 30, + "member": "CE::Rendering::@anon(projects/engine/src/core/rendering/renderer.cpp)::TerminalObservation::~TerminalObservation", + "access": "invalid", + "via": "libclang AST" + } + ] + }, + { + "source": "Rendering::Detail::GraphicsDomainState", + "target": "Rendering::GraphicsDomainReason", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/src/core/rendering/graphics-domain.cpp", + "line": 13, + "member": "CE::Rendering::Detail::GraphicsDomainState::reason", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Rendering::DrawPacket2D", + "target": "Rendering::Geometry::Buffer2D", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 33, + "member": "CE::Rendering::DrawPacket2D::geometry", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 33, + "member": "CE::Rendering::DrawPacket2D::geometry", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Rendering::DrawPacket2D", + "target": "Rendering::Resources::Material", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 34, + "member": "CE::Rendering::DrawPacket2D::material", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 34, + "member": "CE::Rendering::DrawPacket2D::material", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Rendering::DrawStyle2D", + "target": "Rendering::Resources::Material", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 19, + "member": "CE::Rendering::DrawStyle2D::material", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/core/rendering/draw-packet.h", + "line": 19, + "member": "CE::Rendering::DrawStyle2D::material", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Rendering::Materials::Definition", + "target": "Rendering::Resources::Pipeline", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/definitions/pipeline.h", + "line": 90, + "member": "CE::Rendering::Materials::Definition::pipeline", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/definitions/pipeline.h", + "line": 90, + "member": "CE::Rendering::Materials::Definition::pipeline", + "access": "public", + "via": "libclang AST" + } + ] + }, + { + "source": "Rendering::Materials::Definition", + "target": "Rendering::Sampling::Sampler", + "evidence": [ + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/definitions/pipeline.h", + "line": 92, + "member": "CE::Rendering::Materials::Definition::sampling", + "access": "public", + "via": "member declaration" + }, + { + "kind": "stores", + "file": "projects/engine/include/cheryl/assets/definitions/pipeline.h", + "line": 92, + "member": "CE::Rendering::Materials::Definition::sampling", + "access": "public", + "via": "libclang AST" + } + ] + } + ], + "overview": true, + "evidence_count": 6 + } + ], + "notes": [ + "Assets, Text and GUI supply separate preparation paths. Retained frames/resources let playback avoid live application and toolkit objects.", + "CPU preparation and graphics upload have distinct owners. No live widget tree crosses to the renderer." + ], + "level": "simple", + "refs": [ + "docs/site/architecture/interfaces-adapters.md" + ], + "nodes": [ + "%assets", + "%resources", + "%text", + "%gui", + "%input", + "%rendering" + ] + } + ] +} diff --git a/docs/site/engine-systems/diagrams/diagnostics-application.mmd b/docs/site/engine-systems/diagrams/diagnostics-application.mmd new file mode 100644 index 00000000..cd33deb6 --- /dev/null +++ b/docs/site/engine-systems/diagrams/diagnostics-application.mmd @@ -0,0 +1,44 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart TB + accTitle: Diagnostics · application observations + accDescr: Arrows point toward the API or type used, not call order or data flow. Methods beneath type names are selected application entry points; Implement hooks marks engine-invoked overrides. Dashed arrows mark optional choices. + k0(["Application
report"]):::app + k1["Runtime::
GameRuntime


diagnostics(): Runtime::RuntimeStats"]:::neighbor + k2["Runtime::
RuntimeStats"]:::focal + k3(["Application
live observations"]):::app + k4["Workers::
Group


status(): Workers::GroupStatus

policy(): Workers::GroupPolicy"]:::neighbor + k5["Workers::
GroupStatus"]:::focal + k6["Runtime::
PlatformDispatcher


diagnostics(): Diagnostics::DispatchStats"]:::neighbor + k7["Diagnostics::
DispatchStats"]:::focal + k8["Events::
EventBus


diagnostics(): Events::EventStats"]:::neighbor + k9["Events::
EventStats"]:::focal + k10["Logging::
Logger


info(fmt, args...)"]:::neighbor + k11["Workers::
GroupPolicy"]:::focal + k0 --> k1 + k1 --> k2 + k0 --> k2 + k3 --> k4 + k4 --> k5 + k3 --> k6 + k6 --> k7 + k3 --> k8 + k8 --> k9 + k0 -.-> k10 + k4 --> k11 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/diagnostics-internal.mmd b/docs/site/engine-systems/diagrams/diagnostics-internal.mmd new file mode 100644 index 00000000..f36c95b9 --- /dev/null +++ b/docs/site/engine-systems/diagrams/diagnostics-internal.mmd @@ -0,0 +1,44 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Diagnostics · owner-specific observations + accDescr: Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["Runtime::
GameRuntime"]:::neighbor + k1["Runtime::
RuntimeStats"]:::focal + k2["Workers::
Group"]:::neighbor + k3["Workers::
GroupStatus"]:::focal + k4["Runtime::
PlatformDispatcher"]:::neighbor + k5["Diagnostics::
DispatchStats"]:::focal + k6["Runtime::
SimulationDispatcher"]:::neighbor + k7["Events::
EventBus"]:::neighbor + k8["Events::
EventStats"]:::focal + k9["Assets::Loading::
Loader"]:::neighbor + k10["Assets::Loading::
UploadStats"]:::focal + k11["Memory::
Manager"]:::neighbor + k12["Memory::
Stats"]:::focal + k13["Rendering::OpenGL::
ResourceLifetime"]:::neighbor + k14["Rendering::OpenGL::
ResourceStats"]:::focal + k0 --> k1 + k2 --> k3 + k4 --> k5 + k6 --> k5 + k7 --> k8 + k9 --> k10 + k11 --> k12 + k13 --> k14 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/dispatch-application.mmd b/docs/site/engine-systems/diagrams/dispatch-application.mmd new file mode 100644 index 00000000..be2b0fc6 --- /dev/null +++ b/docs/site/engine-systems/diagrams/dispatch-application.mmd @@ -0,0 +1,45 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Dispatch · application owner requests + accDescr: Local names omit Runtime::; ambiguous nested namespaces remain qualified. Arrows point toward the API or type used, not call order or data flow. Methods beneath type names are selected application entry points; Implement hooks marks engine-invoked overrides. Dashed arrows mark optional choices. + k0(["Application
init_platform()"]):::app + k1["Context

platform_dispatcher(): PlatformDispatcher

resources(): Rendering::Resources::Provider"]:::neighbor + k2["PlatformDispatcher

submission(): PlatformDispatcher::Submission"]:::focal + k3["PlatformDispatcher::
Submission


submit(Work): Result"]:::focal + k4(["Application
producer"]):::app + k5(["Application
platform-owner work"]):::app + k6["Rendering::Resources::
Provider


create_image(Assets::Images::Decoded):
Rendering::Resources::Image

upload_geometry(
Rendering::Geometry::Vertex2D[],
Rendering::Geometry::Topology):
Rendering::Geometry::Buffer2D"]:::neighbor + k7(["Application
composition"]):::app + k8["GameRuntime

simulation_dispatcher(): SimulationDispatcher"]:::neighbor + k9["SimulationDispatcher

submission(): SimulationDispatcher::Submission"]:::focal + k10["SimulationDispatcher::
Submission


submit(Work): Result"]:::focal + k11(["Application
simulation-owner work"]):::app + k12["Game::
TickContext"]:::neighbor + k0 --> k1 + k1 --> k2 + k2 --> k3 + k4 --> k3 + k5 --> k1 + k1 --> k6 + k7 --> k8 + k8 --> k9 + k9 --> k10 + k4 --> k10 + k11 --> k12 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/dispatch-internal.mmd b/docs/site/engine-systems/diagrams/dispatch-internal.mmd new file mode 100644 index 00000000..1c22f795 --- /dev/null +++ b/docs/site/engine-systems/diagrams/dispatch-internal.mmd @@ -0,0 +1,46 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Dispatch · engine integration + accDescr: Local names omit Runtime::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["Context"]:::neighbor + k1["PlatformDispatcher"]:::focal + k2["GameRuntime"]:::neighbor + k3["SimulationDispatcher"]:::focal + k4["PlatformDispatcher::
Submission"]:::focal + k5["SimulationDispatcher::
Submission"]:::focal + k6["Diagnostics::
DispatchStats"]:::neighbor + k7["Text::Rendering::
Updater"]:::neighbor + k8["UI::TGUI::
SceneUploader"]:::neighbor + k9["UI::RmlUi::
SceneUploader"]:::neighbor + k10("Events::
platform_delivery()"):::neighbor + k11("Events::
simulation_delivery()"):::neighbor + k0 --> k1 + k2 --> k1 + k2 --> k3 + k1 --> k0 + k1 --> k4 + k3 --> k5 + k1 --> k6 + k3 --> k6 + k7 --> k4 + k8 --> k4 + k9 --> k4 + k10 --> k4 + k11 --> k5 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/display-application.mmd b/docs/site/engine-systems/diagrams/display-application.mmd new file mode 100644 index 00000000..e9e141b0 --- /dev/null +++ b/docs/site/engine-systems/diagrams/display-application.mmd @@ -0,0 +1,46 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Display · application access + accDescr: Local names omit Platform::; ambiguous nested namespaces remain qualified. Arrows point toward the API or type used, not call order or data flow. Methods beneath type names are selected application entry points; Implement hooks marks engine-invoked overrides. Dashed arrows mark optional choices. + k0(["Application
init_platform()"]):::app + k1["Runtime::
Context


display(): DisplaySystem

window(): Window

platform_dispatcher(): Runtime::PlatformDispatcher"]:::neighbor + k2(["Application
platform-owner work"]):::app + k3["Runtime::PlatformDispatcher::
Submission


submit(Work): Result"]:::neighbor + k4["Window

resize(width, height)

set_mode(WindowMode)

hide_cursor(hide)"]:::focal + k5["DisplaySystem

monitors(): Monitor[]

primary_monitor(): Monitor

content_scale(Monitor)"]:::focal + k6["Monitor"]:::focal + k7["Dimensions"]:::focal + k8["WindowMode"]:::focal + k9(["Application
update()"]):::app + k10["Game::
TickContext"]:::neighbor + k11["FramebufferSize"]:::focal + k12["Rendering::
Camera2D


set_framebuffer_size(FramebufferSize)"]:::neighbor + k0 --> k1 + k2 --> k3 + k2 --> k4 + k1 --> k5 + k1 --> k4 + k5 --> k6 + k4 --> k7 + k4 --> k8 + k9 --> k10 + k10 --> k11 + k9 --> k12 + k12 --> k11 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/display-internal.mmd b/docs/site/engine-systems/diagrams/display-internal.mmd new file mode 100644 index 00000000..6a303171 --- /dev/null +++ b/docs/site/engine-systems/diagrams/display-internal.mmd @@ -0,0 +1,46 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Display · engine integration + accDescr: Local names omit Platform::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["Runtime::
Context"]:::neighbor + k1["DisplaySystem"]:::focal + k2["GLFW::
DisplaySystem"]:::focal + k3["Window"]:::focal + k4["GLFW::
Window"]:::focal + k5["Input::
System"]:::neighbor + k6["Rendering::OpenGL::
GLFWContext"]:::neighbor + k7["Dimensions"]:::focal + k8["FramebufferSize"]:::focal + k9["WindowResized"]:::focal + k10["Events::
EventSystem"]:::neighbor + k11["Game::
TickContext"]:::neighbor + k12["Rendering::
Camera2D"]:::neighbor + k0 --> k1 + k2 --> k1 + k1 --> k3 + k4 --> k3 + k5 --> k3 + k6 --> k4 + k3 --> k7 + k3 --> k8 + k4 --> k9 + k4 --> k10 + k11 --> k8 + k12 --> k8 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/events-application.mmd b/docs/site/engine-systems/diagrams/events-application.mmd new file mode 100644 index 00000000..1085af70 --- /dev/null +++ b/docs/site/engine-systems/diagrams/events-application.mmd @@ -0,0 +1,53 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Events · application registration and delivery + accDescr: Local names omit Events::; ambiguous nested namespaces remain qualified. Arrows point toward the API or type used, not call order or data flow. Methods beneath type names are selected application entry points; Implement hooks marks engine-invoked overrides. Dashed arrows mark optional choices. + k0(["Application
producer"]):::app + k1["EventChannel"]:::focal + k2["EventBus::
Publisher


notify(
EventChannel<Payload>,
Value,
EventBus::ErrorHandler)"]:::focal + k3(["Application
listener setup"]):::app + k4["Runtime::
Context


events(): EventBus"]:::neighbor + k5["EventBus

register_listener(
EventChannel<Payload>,
EventChannel<Payload>::Callback,
EventBus::Delivery,
EventBus::ErrorHandler):
EventBus::Registration

publisher(): EventBus::Publisher

unregister_and_wait(EventBus::Registration)"]:::focal + k6["EventBus::
Registration


disconnect()"]:::focal + k7(["Application
callback"]):::app + k8("simulation_delivery(
Runtime::SimulationDispatcher::Submission):
EventBus::Delivery"):::focal + k9["Runtime::SimulationDispatcher::
Submission"]:::neighbor + k10["EventBus::
Delivery"]:::focal + k11["EventBus::
ErrorHandler"]:::focal + k12(["Application
cleanup / quiesce()"]):::app + k0 --> k1 + k0 --> k2 + k3 --> k6 + k5 --> k1 + k5 --> k6 + k5 --> k7 + k3 -.-> k8 + k8 --> k9 + k8 --> k10 + k5 --> k10 + k3 --> k11 + k12 --> k6 + k3 --> k4 + k4 --> k5 + k5 --> k2 + k12 --> k5 + k2 --> k1 + k2 -.-> k11 + k5 -.-> k11 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/events-focus-delivery.mmd b/docs/site/engine-systems/diagrams/events-focus-delivery.mmd new file mode 100644 index 00000000..ee96df3f --- /dev/null +++ b/docs/site/engine-systems/diagrams/events-focus-delivery.mmd @@ -0,0 +1,43 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Events · practical delivery choices + accDescr: Local names omit Events::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0(["Application
listener setup"]):::app + k1["EventBus"]:::focal + k2["EventBus::
Delivery"]:::focal + k3("platform_delivery()"):::focal + k4("simulation_delivery()"):::focal + k5("worker_delivery()"):::focal + k6["Runtime::PlatformDispatcher::
Submission"]:::neighbor + k7["Runtime::SimulationDispatcher::
Submission"]:::neighbor + k8["Workers::
Group"]:::neighbor + k9["EventBus::
ErrorHandler"]:::focal + k0 --> k1 + k1 --> k2 + k0 -.-> k3 + k0 -.-> k4 + k0 -.-> k5 + k3 --> k6 + k4 --> k7 + k5 --> k8 + k3 --> k2 + k4 --> k2 + k5 --> k2 + k0 --> k9 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/events-focus-immediate.mmd b/docs/site/engine-systems/diagrams/events-focus-immediate.mmd new file mode 100644 index 00000000..ffb97811 --- /dev/null +++ b/docs/site/engine-systems/diagrams/events-focus-immediate.mmd @@ -0,0 +1,36 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Events · simple immediate notification + accDescr: Local names omit Events::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0(["Application
producer"]):::app + k1["EventBus"]:::focal + k2["EventChannel"]:::focal + k3(["Application
listener setup"]):::app + k4["EventBus::
Registration"]:::focal + k5(["Application
callback"]):::app + k6(["Application
cleanup / quiesce()"]):::app + k0 --> k1 + k0 --> k2 + k3 --> k1 + k3 --> k4 + k1 --> k2 + k1 --> k4 + k1 --> k5 + k6 --> k4 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/events-internal.mmd b/docs/site/engine-systems/diagrams/events-internal.mmd new file mode 100644 index 00000000..9533bb8d --- /dev/null +++ b/docs/site/engine-systems/diagrams/events-internal.mmd @@ -0,0 +1,52 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Events · engine integration + accDescr: Local names omit Events::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["Runtime::
Context"]:::neighbor + k1["EventBus"]:::focal + k2["Input::
System"]:::neighbor + k3["EventBus::
Publisher"]:::focal + k4["Platform::GLFW::
Window"]:::neighbor + k5["EventSystem"]:::focal + k6["Runtime::
RuntimeServices"]:::neighbor + k7["EventChannel"]:::focal + k8["EventBus::
Registration"]:::focal + k9["EventBus::
Delivery"]:::focal + k10["EventBus::
ErrorHandler"]:::focal + k11("platform_delivery()"):::focal + k12["Runtime::PlatformDispatcher::
Submission"]:::neighbor + k13("simulation_delivery()"):::focal + k14["Runtime::SimulationDispatcher::
Submission"]:::neighbor + k15("worker_delivery()"):::focal + k16["Workers::
Group"]:::neighbor + k0 --> k1 + k2 --> k3 + k4 --> k5 + k6 --> k1 + k1 --> k7 + k1 --> k8 + k1 --> k9 + k1 --> k10 + k11 --> k12 + k11 --> k9 + k13 --> k14 + k13 --> k9 + k15 --> k16 + k15 --> k9 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/gui-application.mmd b/docs/site/engine-systems/diagrams/gui-application.mmd new file mode 100644 index 00000000..06f668d6 --- /dev/null +++ b/docs/site/engine-systems/diagrams/gui-application.mmd @@ -0,0 +1,59 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart TB + accTitle: GUI · application-owned sessions and publication + accDescr: Local names omit UI::; ambiguous nested namespaces remain qualified. Arrows point toward the API or type used, not call order or data flow. Methods beneath type names are selected application entry points; Implement hooks marks engine-invoked overrides. Dashed arrows mark optional choices. + k0(["Application-owned UI"]):::app + k1["TGUI::
Session


gui(): tgui::BackendGui

handle_input(Input::Events::Record[], pointer_selected)

request_keyboard_focus(Input::Routing::KeyboardPolicy)

record(): TGUI::RecordedScene"]:::focal + k2["RmlUi::
Session


context(): Rml::Context

handle_input(Input::Events::Record[], pointer_selected)

request_keyboard_focus(Input::Routing::KeyboardPolicy)

record(): RmlUi::RecordedScene"]:::focal + k3["Input::Events::
Record"]:::neighbor + k4["Input::Routing::
KeyboardPolicy"]:::neighbor + k5["Text::
Font"]:::neighbor + k6["TGUI::
RecordedScene"]:::focal + k7["RmlUi::
RecordedScene"]:::focal + k8(["Application
init_platform()"]):::app + k9["TGUI::
SceneUploader


upload(
Rendering::Resources::Provider,
TGUI::RecordedScene):
TGUI::Scene

submit(
Runtime::PlatformDispatcher::Submission,
TGUI::RecordedScene):
TGUI::Scene"]:::focal + k10["RmlUi::
SceneUploader


upload(
Rendering::Resources::Provider,
RmlUi::RecordedScene):
RmlUi::Scene

submit(
Runtime::PlatformDispatcher::Submission,
RmlUi::RecordedScene):
RmlUi::Scene"]:::focal + k11["TGUI::
Scene


write(Rendering::FrameWriter)"]:::focal + k12["RmlUi::
Scene


write(Rendering::FrameWriter)"]:::focal + k13(["Application
prepare_render_frame()"]):::app + k14["Rendering::
FrameWriter"]:::neighbor + k0 -.-> k1 + k0 -.-> k2 + k0 --> k3 + k0 --> k4 + k0 --> k5 + k1 --> k6 + k2 --> k7 + k8 -.-> k9 + k8 -.-> k10 + k9 --> k11 + k10 --> k12 + k13 -.-> k11 + k13 -.-> k12 + k11 --> k14 + k12 --> k14 + k9 --> k6 + k0 -.-> k9 + k10 --> k7 + k0 -.-> k10 + k1 --> k3 + k1 --> k4 + k2 --> k3 + k2 --> k4 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/gui-focus-rmlui.mmd b/docs/site/engine-systems/diagrams/gui-focus-rmlui.mmd new file mode 100644 index 00000000..f2a2b213 --- /dev/null +++ b/docs/site/engine-systems/diagrams/gui-focus-rmlui.mmd @@ -0,0 +1,42 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: GUI · RmlUi publication + accDescr: Local names omit UI::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0(["Application-owned UI"]):::app + k1["RmlUi::
Session"]:::focal + k2["Input::
System"]:::neighbor + k3["Text::
FontService"]:::neighbor + k4["RmlUi::
RecordedScene"]:::focal + k5["RmlUi::
SceneUploader"]:::focal + k6["Runtime::PlatformDispatcher::
Submission"]:::neighbor + k7["Rendering::Resources::
Provider"]:::neighbor + k8["RmlUi::
Scene"]:::focal + k9(["Application
prepare_render_frame()"]):::app + k10["Rendering::
PassWriter"]:::neighbor + k0 --> k1 + k1 --> k2 + k1 --> k3 + k1 --> k4 + k5 --> k4 + k5 --> k6 + k5 --> k7 + k5 --> k8 + k9 --> k8 + k8 --> k10 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/gui-focus-tgui.mmd b/docs/site/engine-systems/diagrams/gui-focus-tgui.mmd new file mode 100644 index 00000000..20abee22 --- /dev/null +++ b/docs/site/engine-systems/diagrams/gui-focus-tgui.mmd @@ -0,0 +1,42 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: GUI · TGUI publication + accDescr: Local names omit UI::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0(["Application-owned UI"]):::app + k1["TGUI::
Session"]:::focal + k2["Input::
System"]:::neighbor + k3["Text::
FontService"]:::neighbor + k4["TGUI::
RecordedScene"]:::focal + k5["TGUI::
SceneUploader"]:::focal + k6["Runtime::PlatformDispatcher::
Submission"]:::neighbor + k7["Rendering::Resources::
Provider"]:::neighbor + k8["TGUI::
Scene"]:::focal + k9(["Application
prepare_render_frame()"]):::app + k10["Rendering::
PassWriter"]:::neighbor + k0 --> k1 + k1 --> k2 + k1 --> k3 + k1 --> k4 + k5 --> k4 + k5 --> k6 + k5 --> k7 + k5 --> k8 + k9 --> k8 + k8 --> k10 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/gui-internal.mmd b/docs/site/engine-systems/diagrams/gui-internal.mmd new file mode 100644 index 00000000..1bcb33a2 --- /dev/null +++ b/docs/site/engine-systems/diagrams/gui-internal.mmd @@ -0,0 +1,50 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: GUI · engine integration + accDescr: Local names omit UI::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["TGUI::
Session"]:::focal + k1["Input::
System"]:::neighbor + k2["RmlUi::
Session"]:::focal + k3["Text::
FontService"]:::neighbor + k4["TGUI::
RecordedScene"]:::focal + k5["RmlUi::
RecordedScene"]:::focal + k6["TGUI::
SceneUploader"]:::focal + k7["RmlUi::
SceneUploader"]:::focal + k8["Runtime::PlatformDispatcher::
Submission"]:::neighbor + k9["Rendering::Resources::
Provider"]:::neighbor + k10["TGUI::
Scene"]:::focal + k11["RmlUi::
Scene"]:::focal + k12["Rendering::
PassWriter"]:::neighbor + k0 --> k1 + k2 --> k1 + k0 --> k3 + k2 --> k3 + k0 --> k4 + k2 --> k5 + k6 --> k4 + k7 --> k5 + k6 --> k8 + k7 --> k8 + k6 --> k9 + k7 --> k9 + k6 --> k10 + k7 --> k11 + k10 --> k12 + k11 --> k12 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/input-application.mmd b/docs/site/engine-systems/diagrams/input-application.mmd new file mode 100644 index 00000000..5c5f3ff2 --- /dev/null +++ b/docs/site/engine-systems/diagrams/input-application.mmd @@ -0,0 +1,51 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Input · application actions and controllers + accDescr: Local names omit Input::; ambiguous nested namespaces remain qualified. Arrows point toward the API or type used, not call order or data flow. Methods beneath type names are selected application entry points; Implement hooks marks engine-invoked overrides. Dashed arrows mark optional choices. + k0(["Application
init_platform()"]):::app + k1["Runtime::
Context


input(): System"]:::neighbor + k2["System

bindings(): Actions::Bindings

action_contexts(): Actions::ContextSubmission

keyboard_id(): DeviceId"]:::focal + k3["Actions::
Bindings


bind_button(Control, Actions::Id): Actions::BindingId

bind_axis(Control, Actions::Id, Actions::AxisOptions):
Actions::BindingId

unbind(Actions::BindingId)"]:::focal + k4["Control"]:::focal + k5["KeyboardKey"]:::focal + k6(["Application-owned controller"]):::app + k7["Actions::
ContextDefinition"]:::focal + k8["Actions::
ContextSubmission


attach(Actions::ContextDefinition): Actions::Context"]:::focal + k9["Actions::
Context


id(): Actions::ContextId

reset()"]:::focal + k10(["Application
update()"]):::app + k11["Game::
TickContext"]:::neighbor + k12["Tick

actions(Actions::Context): Actions::View

button(Actions::Id): Actions::ButtonTickState

axis(Actions::Id): Actions::AxisTickState"]:::focal + k13["Actions::
View


button(Actions::Id): Actions::ButtonTickState

axis(Actions::Id): Actions::AxisTickState"]:::focal + k2 --> k3 + k3 --> k4 + k4 --> k5 + k6 --> k7 + k6 --> k8 + k8 --> k9 + k6 --> k9 + k10 --> k11 + k11 --> k12 + k12 --> k13 + k6 --> k13 + k0 --> k1 + k1 --> k2 + k8 --> k7 + k12 --> k9 + k2 --> k8 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/input-focus-actions.mmd b/docs/site/engine-systems/diagrams/input-focus-actions.mmd new file mode 100644 index 00000000..1fa6a901 --- /dev/null +++ b/docs/site/engine-systems/diagrams/input-focus-actions.mmd @@ -0,0 +1,37 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Input · simple gameplay actions + accDescr: Local names omit Input::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0(["Application
init_platform()"]):::app + k1["System"]:::focal + k2["Actions::
Bindings"]:::focal + k3["Control"]:::focal + k4["KeyboardKey"]:::focal + k5["PollSnapshot"]:::focal + k6(["Application
update()"]):::app + k7["Tick"]:::focal + k8["Actions::
ButtonTickState"]:::focal + k0 --> k1 + k1 --> k2 + k2 --> k3 + k3 --> k4 + k1 --> k5 + k6 --> k7 + k7 --> k8 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/input-focus-capture.mmd b/docs/site/engine-systems/diagrams/input-focus-capture.mmd new file mode 100644 index 00000000..8be5b230 --- /dev/null +++ b/docs/site/engine-systems/diagrams/input-focus-capture.mmd @@ -0,0 +1,38 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Input · practical UI capture and focus + accDescr: Local names omit Input::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0(["Application-owned UI"]):::app + k1["System"]:::focal + k2["Capture::
Lease"]:::focal + k3["Routing::
Manager"]:::focal + k4["Routing::
Lease"]:::focal + k5["Routing::
KeyboardPolicy"]:::focal + k6["Input::Events::
Record"]:::focal + k7["Input::Events::
Payload"]:::focal + k0 --> k1 + k1 --> k2 + k1 --> k3 + k3 --> k4 + k1 --> k5 + k0 --> k2 + k0 --> k4 + k0 --> k6 + k6 --> k7 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/input-internal.mmd b/docs/site/engine-systems/diagrams/input-internal.mmd new file mode 100644 index 00000000..e98ba029 --- /dev/null +++ b/docs/site/engine-systems/diagrams/input-internal.mmd @@ -0,0 +1,54 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Input · engine integration + accDescr: Local names omit Input::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["Runtime::
GameRuntime"]:::neighbor + k1["System"]:::focal + k2["Platform::GLFW::
InputSystem"]:::focal + k3["Platform::
Window"]:::neighbor + k4["Actions::
Bindings"]:::focal + k5["Actions::
ContextSubmission"]:::focal + k6["Actions::
ContextDefinition"]:::focal + k7["Actions::
Context"]:::focal + k8["Capture::
Collector"]:::focal + k9["Routing::
Manager"]:::focal + k10["PollSnapshot"]:::focal + k11["PollingBacklog"]:::focal + k12["Game::
TickContext"]:::neighbor + k13["Tick"]:::focal + k14["Actions::
View"]:::focal + k15["UI::TGUI::
Session"]:::neighbor + k16["UI::RmlUi::
Session"]:::neighbor + k0 --> k1 + k2 --> k1 + k1 --> k3 + k1 --> k4 + k1 --> k5 + k5 --> k6 + k5 --> k7 + k1 --> k8 + k1 --> k9 + k1 --> k10 + k0 --> k11 + k11 --> k10 + k12 --> k13 + k13 --> k14 + k15 --> k1 + k16 --> k1 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/logging-application.mmd b/docs/site/engine-systems/diagrams/logging-application.mmd new file mode 100644 index 00000000..5ec7b0ff --- /dev/null +++ b/docs/site/engine-systems/diagrams/logging-application.mmd @@ -0,0 +1,53 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart TB + accTitle: Logging · application configuration and writers + accDescr: Local names omit Logging::; ambiguous nested namespaces remain qualified. Arrows point toward the API or type used, not call order or data flow. Methods beneath type names are selected application entry points; Implement hooks marks engine-invoked overrides. Dashed arrows mark optional choices. + k0(["Application
composition"]):::app + k1["ProcessLifetime

ProcessLifetime()"]:::focal + k2["Config

for_logger(name, Profile): Config"]:::focal + k3["Log

Log(FileEventHandlers, Config)

info(format, arguments...)

close(timeout)"]:::focal + k4["Profile"]:::focal + k5["Level"]:::focal + k6["OverflowPolicy"]:::focal + k7["FileEventHandlers"]:::focal + k8(["Application
producer"]):::app + k9["Logger

initialize(FileEventHandlers, Config): Log<name>

info(fmt, args...)

warn(fmt, args...)"]:::focal + k10(["Application
report"]):::app + k11["LogHandle

shared_queue_stats(): QueueStats

backend_stats(): BackendStats

report_diagnostics()"]:::focal + k12["QueueStats"]:::neighbor + k13(["Application
cleanup / quiesce()"]):::app + k14["BackendStats"]:::neighbor + k0 --> k1 + k0 --> k2 + k0 --> k3 + k2 --> k4 + k2 --> k5 + k2 --> k6 + k3 --> k7 + k8 --> k9 + k9 --> k3 + k10 --> k11 + k11 --> k12 + k13 --> k3 + k0 --> k9 + k3 --> k2 + k9 --> k7 + k9 --> k2 + k11 --> k14 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/logging-internal.mmd b/docs/site/engine-systems/diagrams/logging-internal.mmd new file mode 100644 index 00000000..d8275854 --- /dev/null +++ b/docs/site/engine-systems/diagrams/logging-internal.mmd @@ -0,0 +1,44 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Logging · engine integration + accDescr: Local names omit Logging::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["Runtime::
GameRuntime"]:::neighbor + k1["Logger"]:::focal + k2["Assets::Loading::
Loader"]:::neighbor + k3["Workers::
Pool"]:::neighbor + k4["Memory::
Manager"]:::neighbor + k5["Log"]:::focal + k6["Config"]:::focal + k7["LogHandle"]:::focal + k8["QueueStats"]:::neighbor + k9["FileEventHandlers"]:::focal + k10["Level"]:::focal + k11["OverflowPolicy"]:::focal + k0 --> k1 + k2 --> k1 + k3 --> k1 + k4 --> k1 + k1 --> k5 + k5 --> k6 + k7 --> k8 + k5 --> k9 + k6 --> k10 + k6 --> k11 + k5 --> k7 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/memory-application.mmd b/docs/site/engine-systems/diagrams/memory-application.mmd new file mode 100644 index 00000000..a6cf5af6 --- /dev/null +++ b/docs/site/engine-systems/diagrams/memory-application.mmd @@ -0,0 +1,33 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart TB + accTitle: Memory · application object storage + accDescr: Local names omit Memory::; ambiguous nested namespaces remain qualified. Arrows point toward the API or type used, not call order or data flow. Methods beneath type names are selected application entry points; Implement hooks marks engine-invoked overrides. Dashed arrows mark optional choices. + k0(["Application
storage owner"]):::app + k1["Objects::
Pool


get(): Objects::Pool<T>

retrieve_objects(N, args...): T[]

cull(age)

release_culled()"]:::focal + k2["Objects::
Reservation


Reservation(count, Allocator)

emplace(index, args...): T

remaining_ranges(): Block<T>[]"]:::focal + k3(["Application
object type"]):::app + k4(["Application-owned game objects"]):::app + k5(["Application
cleanup / quiesce()"]):::app + k0 --> k1 + k0 -.-> k2 + k2 --> k3 + k4 --> k3 + k5 -.-> k1 + k1 --> k3 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/memory-internal.mmd b/docs/site/engine-systems/diagrams/memory-internal.mmd new file mode 100644 index 00000000..5d64a019 --- /dev/null +++ b/docs/site/engine-systems/diagrams/memory-internal.mmd @@ -0,0 +1,48 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart TB + accTitle: Memory · engine integration + accDescr: Local names omit Memory::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["Assets::Cache::
Store"]:::neighbor + k1["Objects::
Reservation"]:::focal + k2["Allocators::
ObjectPoolAllocator"]:::focal + k3["Objects::
Pool"]:::focal + k4("Assets::Grids::
make_geometry()"):::neighbor + k5["ExactManager"]:::focal + k6["Manager"]:::focal + k7["Objects::
PoolState"]:::focal + k8["Objects::Constructor::
Context"]:::focal + k9["Block"]:::focal + k10["BlockTransactions"]:::focal + k11["ByteReleaseContext"]:::focal + k12["State::
Singleton"]:::neighbor + k13["Stats"]:::neighbor + k0 --> k1 + k1 --> k2 + k2 --> k3 + k4 --> k5 + k5 --> k6 + k3 --> k7 + k7 --> k8 + k1 --> k9 + k6 --> k10 + k6 --> k11 + k10 --> k9 + k6 --> k12 + k6 --> k13 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/overview-content.mmd b/docs/site/engine-systems/diagrams/overview-content.mmd new file mode 100644 index 00000000..19daeea8 --- /dev/null +++ b/docs/site/engine-systems/diagrams/overview-content.mmd @@ -0,0 +1,35 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Subsystems · simple content and presentation + accDescr: Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["Assets"]:::system + k1["Resources"]:::system + k2["Text"]:::system + k3["GUI"]:::system + k4["Input"]:::system + k5["Rendering"]:::system + k0 --> k1 + k2 --> k1 + k3 --> k1 + k3 --> k4 + k3 --> k2 + k3 --> k5 + k5 --> k0 + k5 --> k1 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/overview-execution.mmd b/docs/site/engine-systems/diagrams/overview-execution.mmd new file mode 100644 index 00000000..c399f39d --- /dev/null +++ b/docs/site/engine-systems/diagrams/overview-execution.mmd @@ -0,0 +1,37 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Subsystems · simple execution boundary + accDescr: Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["Runtime"]:::system + k1["Display"]:::system + k2["Input"]:::system + k3["Rendering"]:::system + k4["Dispatch"]:::system + k5["Workers"]:::system + k6["Events"]:::system + k0 --> k1 + k0 --> k2 + k0 --> k3 + k0 --> k4 + k0 --> k5 + k2 --> k1 + k3 --> k1 + k6 --> k4 + k6 --> k5 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/overview-systems.mmd b/docs/site/engine-systems/diagrams/overview-systems.mmd new file mode 100644 index 00000000..9761f7cf --- /dev/null +++ b/docs/site/engine-systems/diagrams/overview-systems.mmd @@ -0,0 +1,73 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Subsystems · interactions + accDescr: Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["Runtime"]:::system + k1["Display"]:::system + k2["Input"]:::system + k3["Rendering"]:::system + k4["Resources"]:::system + k5["Text"]:::system + k6["Events"]:::system + k7["Dispatch"]:::system + k8["Workers"]:::system + k9["Assets"]:::system + k10["GUI"]:::system + k11["Memory"]:::system + k12["Utilities"]:::system + k13["State"]:::system + k14["Audio"]:::system + k15["Logging"]:::system + k16["Diagnostics"]:::system + k0 --> k1 + k0 --> k2 + k0 --> k3 + k0 --> k4 + k0 --> k5 + k0 --> k6 + k0 --> k7 + k0 --> k8 + k2 --> k1 + k2 --> k6 + k1 --> k6 + k3 --> k4 + k3 --> k9 + k5 --> k4 + k5 --> k8 + k5 --> k7 + k10 --> k2 + k10 --> k5 + k10 --> k4 + k10 --> k7 + k10 --> k3 + k9 --> k4 + k9 --> k11 + k9 --> k12 + k9 --> k13 + k14 --> k6 + k6 --> k7 + k6 --> k8 + k11 --> k13 + k11 --> k12 + k15 --> k13 + k0 --> k15 + k8 --> k15 + k0 --> k16 + k7 --> k16 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/rendering-application.mmd b/docs/site/engine-systems/diagrams/rendering-application.mmd new file mode 100644 index 00000000..c61e90c5 --- /dev/null +++ b/docs/site/engine-systems/diagrams/rendering-application.mmd @@ -0,0 +1,43 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Rendering · application frame preparation + accDescr: Local names omit Rendering::; ambiguous nested namespaces remain qualified. Arrows point toward the API or type used, not call order or data flow. Methods beneath type names are selected application entry points; Implement hooks marks engine-invoked overrides. Dashed arrows mark optional choices. + k0(["Application
update()"]):::app + k1["Camera2D

set_framebuffer_size(Platform::FramebufferSize)

set_view_matrix(glm::mat4)

projection_matrix(): glm::mat4

view_matrix(): glm::mat4"]:::focal + k2(["Application
prepare_render_frame()"]):::app + k3["FrameWriter

begin_pass(
glm::mat4,
glm::mat4,
Pipelines::PassConstraints2D,
Parameters::Set):
PassWriter"]:::focal + k4["PassWriter

add(DrawPacket2D)

semantics(): Parameters::PassSemantics

parameters(): Parameters::Set

constraints(): Pipelines::PassConstraints2D"]:::focal + k5["Submission::
Context2D"]:::focal + k6["DrawStyle2D"]:::focal + k7("Submission::resolve_sprite(
Assets::Sprites::Asset,
Assets::Grids::CellIndex,
DrawStyle2D,
Submission::Context2D):
DrawPacket2D"):::focal + k8["Assets::Sprites::
Asset"]:::neighbor + k9["DrawPacket2D"]:::focal + k0 --> k1 + k2 --> k1 + k2 --> k3 + k3 --> k4 + k2 --> k5 + k2 --> k6 + k2 --> k7 + k7 --> k8 + k7 --> k5 + k7 --> k6 + k7 --> k9 + k4 --> k9 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/rendering-focus-playback.mmd b/docs/site/engine-systems/diagrams/rendering-focus-playback.mmd new file mode 100644 index 00000000..eddaf850 --- /dev/null +++ b/docs/site/engine-systems/diagrams/rendering-focus-playback.mmd @@ -0,0 +1,32 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Rendering · simple prepared frame + accDescr: Local names omit Rendering::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["Renderer"]:::focal + k1["Frame"]:::focal + k2["Pass"]:::focal + k3["DrawPacket2D"]:::focal + k4["Resources::
Material"]:::neighbor + k5["Geometry::
Buffer2D"]:::neighbor + k0 --> k1 + k1 --> k2 + k2 --> k3 + k3 --> k4 + k3 --> k5 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/rendering-focus-sprite.mmd b/docs/site/engine-systems/diagrams/rendering-focus-sprite.mmd new file mode 100644 index 00000000..5d020e69 --- /dev/null +++ b/docs/site/engine-systems/diagrams/rendering-focus-sprite.mmd @@ -0,0 +1,39 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Rendering · practical one-sprite draw + accDescr: Local names omit Rendering::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0(["Application
prepare_render_frame()"]):::app + k1["FrameWriter"]:::focal + k2["PassWriter"]:::focal + k3["Camera2D"]:::focal + k4("Submission::
resolve_sprite()"):::focal + k5["Submission::
Context2D"]:::focal + k6["Assets::Sprites::
Asset"]:::neighbor + k7["DrawStyle2D"]:::focal + k8["DrawPacket2D"]:::focal + k0 --> k1 + k1 --> k2 + k0 --> k3 + k0 --> k4 + k4 --> k5 + k4 --> k6 + k4 --> k7 + k4 --> k8 + k2 --> k8 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/rendering-internal.mmd b/docs/site/engine-systems/diagrams/rendering-internal.mmd new file mode 100644 index 00000000..a17d064f --- /dev/null +++ b/docs/site/engine-systems/diagrams/rendering-internal.mmd @@ -0,0 +1,54 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart TB + accTitle: Rendering · engine integration + accDescr: Local names omit Rendering::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["Runtime::
GameRuntime"]:::neighbor + k1["Renderer"]:::focal + k2["PresentationSurface"]:::focal + k3["Frame"]:::focal + k4["OpenGL::
Renderer"]:::focal + k5["OpenGL::
GLFWContext"]:::focal + k6["OpenGL::
Context"]:::focal + k7["FrameWriter"]:::focal + k8["PassWriter"]:::focal + k9["DrawPacket2D"]:::focal + k10["Pass"]:::focal + k11["Resources::
Material"]:::neighbor + k12["Geometry::
Buffer2D"]:::neighbor + k13("Submission::
resolve_sprite()"):::focal + k14["Assets::Sprites::
Asset"]:::neighbor + k15["Submission::
Context2D"]:::focal + k0 --> k1 + k0 --> k2 + k0 --> k3 + k4 --> k1 + k5 --> k6 + k6 --> k2 + k7 --> k3 + k7 --> k8 + k8 --> k9 + k3 --> k10 + k10 --> k9 + k1 --> k3 + k9 --> k11 + k9 --> k12 + k13 --> k14 + k13 --> k15 + k13 --> k9 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/resources-application.mmd b/docs/site/engine-systems/diagrams/resources-application.mmd new file mode 100644 index 00000000..6b6a76fd --- /dev/null +++ b/docs/site/engine-systems/diagrams/resources-application.mmd @@ -0,0 +1,50 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart TB + accTitle: Resources · application uploads and retained draws + accDescr: Local names omit Rendering::Resources::; ambiguous nested namespaces remain qualified. Arrows point toward the API or type used, not call order or data flow. Methods beneath type names are selected application entry points; Implement hooks marks engine-invoked overrides. Dashed arrows mark optional choices. + k0(["Application
loading work"]):::app + k1("Assets::Images::decode(file): Assets::Images::Decoded"):::neighbor + k2["Assets::Images::
Decoded"]:::focal + k3(["Application
init_platform()"]):::app + k4["Runtime::
Context


resources(): Provider"]:::neighbor + k5["Provider

create_image(Assets::Images::Decoded): Image

upload_geometry(
Rendering::Geometry::Vertex2D[],
Rendering::Geometry::Topology):
Rendering::Geometry::Buffer2D

graphics_domain(): Rendering::GraphicsDomain"]:::focal + k6(["Application
platform-owner work"]):::app + k7["Image"]:::focal + k8["Rendering::Geometry::
Buffer2D"]:::focal + k9["Material"]:::focal + k10["Pipeline"]:::focal + k11(["Application
prepare_render_frame()"]):::app + k12["Rendering::
DrawPacket2D"]:::neighbor + k13(["Application
report"]):::app + k14["Rendering::
GraphicsDomain


snapshot(): Rendering::GraphicsDomainSnapshot"]:::focal + k0 --> k1 + k1 --> k2 + k3 --> k4 + k4 --> k5 + k6 --> k5 + k5 --> k2 + k5 --> k7 + k5 --> k8 + k9 --> k10 + k11 --> k12 + k12 --> k9 + k12 --> k8 + k13 --> k14 + k5 --> k14 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/resources-internal.mmd b/docs/site/engine-systems/diagrams/resources-internal.mmd new file mode 100644 index 00000000..0e172bb9 --- /dev/null +++ b/docs/site/engine-systems/diagrams/resources-internal.mmd @@ -0,0 +1,55 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Resources · engine integration + accDescr: Local names omit Rendering::Resources::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["Runtime::
Context"]:::neighbor + k1["Provider"]:::focal + k2["Assets::Loading::
Loader"]:::neighbor + k3["UI::TGUI::
SceneUploader"]:::neighbor + k4["UI::RmlUi::
SceneUploader"]:::neighbor + k5("Text::Rendering::
upload()"):::neighbor + k6["Rendering::OpenGL::
ResourceProvider"]:::focal + k7["Image"]:::focal + k8["Rendering::Geometry::
Buffer2D"]:::focal + k9["Program"]:::focal + k10["Rendering::
GraphicsDomain"]:::focal + k11["Rendering::OpenGL::
ResourceLifetime"]:::focal + k12["Rendering::
DrawPacket2D"]:::neighbor + k13["Material"]:::focal + k14["Pipeline"]:::focal + k15["Rendering::OpenGL::
Pipeline"]:::focal + k16["Rendering::OpenGL::
Program"]:::focal + k0 --> k1 + k2 --> k1 + k3 --> k1 + k4 --> k1 + k5 --> k1 + k6 --> k1 + k1 --> k7 + k1 --> k8 + k1 --> k9 + k1 --> k10 + k6 --> k11 + k12 --> k13 + k12 --> k8 + k13 --> k14 + k15 --> k14 + k15 --> k16 + k16 --> k9 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/runtime-application.mmd b/docs/site/engine-systems/diagrams/runtime-application.mmd new file mode 100644 index 00000000..40367cc0 --- /dev/null +++ b/docs/site/engine-systems/diagrams/runtime-application.mmd @@ -0,0 +1,52 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart TB + accTitle: Runtime · application composition + accDescr: Local names omit Runtime::; ambiguous nested namespaces remain qualified. Arrows point toward the API or type used, not call order or data flow. Methods beneath type names are selected application entry points; Implement hooks marks engine-invoked overrides. Dashed arrows mark optional choices. + k0(["Application
composition"]):::app + k1("Application::GLFWOpenGL::make_context(
Application::GLFWOpenGL::Options):
Context"):::focal + k2["Context"]:::focal + k3["GameRuntime

run()

shutdown(): ShutdownEndpoint

lifecycle(): RuntimeLifecycle

set_shutdown_policy(
ShutdownPolicyCallback,
ShutdownPolicyOptions,
Events::EventBus::Delivery):
ShutdownPolicy"]:::focal + k4(["Application-owned game objects"]):::app + k5["Game::
AbstractGame


Implement hooks
init_platform()

init_simulation()

update(Game::TickContext)

prepare_render_frame(Rendering::FrameWriter)"]:::focal + k6(["Application
update()"]):::app + k7["Game::
TickContext


request_stop()"]:::focal + k8(["Application
prepare_render_frame()"]):::app + k9["Rendering::
FrameWriter


begin_pass(
glm::mat4,
glm::mat4,
Rendering::Pipelines::PassConstraints2D,
Rendering::Parameters::Set):
Rendering::PassWriter"]:::neighbor + k10["ShutdownPolicy

reset()"]:::focal + k11(["Application
producer"]):::app + k12["ShutdownEndpoint

request(ShutdownReason): ShutdownTicket"]:::focal + k13["RuntimeLifecycle

snapshot(): RuntimeLifecycleSnapshot"]:::focal + k14(["Application
report"]):::app + k0 --> k1 + k1 --> k2 + k0 --> k3 + k4 --> k5 + k3 --> k5 + k3 --> k2 + k6 --> k7 + k8 --> k9 + k0 --> k10 + k11 --> k12 + k3 --> k12 + k3 --> k13 + k14 --> k13 + k3 --> k10 + k5 --> k7 + k5 --> k9 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/runtime-focus-assembly.mmd b/docs/site/engine-systems/diagrams/runtime-focus-assembly.mmd new file mode 100644 index 00000000..defee3a8 --- /dev/null +++ b/docs/site/engine-systems/diagrams/runtime-focus-assembly.mmd @@ -0,0 +1,36 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Runtime · simple adapter assembly + accDescr: Local names omit Runtime::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["GameRuntime"]:::focal + k1["Context"]:::focal + k2["Game::
AbstractGame"]:::focal + k3["Platform::
DisplaySystem"]:::neighbor + k4["Input::
System"]:::neighbor + k5["Rendering::
PresentationSurface"]:::neighbor + k6["Rendering::
Renderer"]:::neighbor + k7["Rendering::Resources::
Provider"]:::neighbor + k0 --> k1 + k0 --> k2 + k1 --> k3 + k1 --> k4 + k1 --> k5 + k1 --> k6 + k1 --> k7 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/runtime-focus-frame.mmd b/docs/site/engine-systems/diagrams/runtime-focus-frame.mmd new file mode 100644 index 00000000..d8c715ad --- /dev/null +++ b/docs/site/engine-systems/diagrams/runtime-focus-frame.mmd @@ -0,0 +1,41 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart TB + accTitle: Runtime · practical tick and frame boundary + accDescr: Local names omit Runtime::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["GameRuntime"]:::focal + k1["Game::
AbstractGame"]:::focal + k2["Game::
TickContext"]:::focal + k3["Input::
Tick"]:::neighbor + k4["Rendering::
FrameWriter"]:::neighbor + k5["Rendering::
Frame"]:::neighbor + k6["Rendering::
PassWriter"]:::neighbor + k7["Rendering::
Pass"]:::neighbor + k8["Rendering::
DrawPacket2D"]:::neighbor + k9["Rendering::
Renderer"]:::neighbor + k0 --> k1 + k1 --> k2 + k2 --> k3 + k1 --> k4 + k4 --> k5 + k4 --> k6 + k5 --> k7 + k7 --> k8 + k0 --> k9 + k9 --> k5 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/runtime-focus-shutdown.mmd b/docs/site/engine-systems/diagrams/runtime-focus-shutdown.mmd new file mode 100644 index 00000000..3793ee9c --- /dev/null +++ b/docs/site/engine-systems/diagrams/runtime-focus-shutdown.mmd @@ -0,0 +1,42 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Runtime · practical shutdown and observation + accDescr: Local names omit Runtime::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0(["Application
producer"]):::app + k1["ShutdownEndpoint"]:::focal + k2["ShutdownTicket"]:::focal + k3(["Application
composition"]):::app + k4["ShutdownPolicy"]:::focal + k5["ShutdownDecision"]:::focal + k6["ShutdownRequest"]:::focal + k7["GameRuntime"]:::focal + k8["RuntimeLifecycle"]:::focal + k9(["Application
report"]):::app + k10["RuntimeLifecycleSnapshot"]:::focal + k0 --> k1 + k1 --> k2 + k3 --> k4 + k3 --> k5 + k3 --> k6 + k7 --> k1 + k7 --> k4 + k7 --> k8 + k9 --> k8 + k8 --> k10 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/runtime-internal.mmd b/docs/site/engine-systems/diagrams/runtime-internal.mmd new file mode 100644 index 00000000..d00a3e17 --- /dev/null +++ b/docs/site/engine-systems/diagrams/runtime-internal.mmd @@ -0,0 +1,54 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Runtime · engine integration + accDescr: Local names omit Runtime::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["GameRuntime"]:::focal + k1["Context"]:::focal + k2["Game::
AbstractGame"]:::focal + k3["Rendering::
Frame"]:::neighbor + k4["Rendering::
Renderer"]:::neighbor + k5["Input::
System"]:::neighbor + k6["RuntimeLifecycle"]:::focal + k7["Platform::
DisplaySystem"]:::neighbor + k8["Rendering::
PresentationSurface"]:::neighbor + k9["Rendering::Resources::
Provider"]:::neighbor + k10["PlatformDispatcher"]:::neighbor + k11["Workers::
Group"]:::neighbor + k12["Events::
EventBus"]:::neighbor + k13["Text::
FontService"]:::neighbor + k14["Game::
TickContext"]:::focal + k15["Rendering::
FrameWriter"]:::neighbor + k16["Input::
Tick"]:::neighbor + k0 --> k1 + k0 --> k2 + k0 --> k3 + k0 --> k4 + k0 --> k5 + k0 --> k6 + k1 --> k7 + k1 --> k8 + k1 --> k9 + k1 --> k10 + k1 --> k11 + k1 --> k12 + k1 --> k13 + k2 --> k14 + k2 --> k15 + k14 --> k16 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/state-application.mmd b/docs/site/engine-systems/diagrams/state-application.mmd new file mode 100644 index 00000000..1b39eb04 --- /dev/null +++ b/docs/site/engine-systems/diagrams/state-application.mmd @@ -0,0 +1,41 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart TB + accTitle: State · application state choices + accDescr: Local names omit State::; ambiguous nested namespaces remain qualified. Arrows point toward the API or type used, not call order or data flow. Methods beneath type names are selected application entry points; Implement hooks marks engine-invoked overrides. Dashed arrows mark optional choices. + k0(["Application
state model"]):::app + k1["StateTracker

update(T)

changed()

delta(): decltype(current() - previous())"]:::focal + k2["StateMachine

add_transition(
State,
Trigger,
State,
StateMachine<State, Trigger>::Guard,
StateMachine<State, Trigger>::Action)

trigger(Trigger)

state(): State"]:::focal + k3(["Application
state producer"]):::app + k4["VersionedVariable

set(T)

snapshot(): VersionedSnapshot<T>

wait_for_change(since): VersionedSnapshot<T>"]:::focal + k5["ObservedVariable

ObservedVariable(
T,
ObservedVariable<T, Observers>::Callback[])

set(T)

snapshot(): VersionedSnapshot<T>"]:::focal + k6["VersionedSnapshot"]:::focal + k7(["Application
snapshot reader"]):::app + k8(["Application
transition / observer hooks"]):::app + k0 --> k1 + k0 --> k2 + k3 --> k4 + k3 --> k5 + k4 --> k6 + k7 --> k6 + k5 --> k8 + k2 --> k8 + k7 --> k4 + k7 -.-> k5 + k5 --> k6 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/state-internal.mmd b/docs/site/engine-systems/diagrams/state-internal.mmd new file mode 100644 index 00000000..435a5aac --- /dev/null +++ b/docs/site/engine-systems/diagrams/state-internal.mmd @@ -0,0 +1,35 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: State · engine integration + accDescr: Local names omit State::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["Assets::Loading::
Loader"]:::neighbor + k1["Singleton"]:::focal + k2["Events::
EventSystem"]:::neighbor + k3["Memory::Objects::
Pool"]:::neighbor + k4["ObservedVariable"]:::focal + k5["VersionedVariable"]:::focal + k6["VersionedSnapshot"]:::focal + k7["StateTracker"]:::focal + k8["StateMachine"]:::focal + k0 --> k1 + k2 --> k1 + k3 --> k1 + k4 --> k5 + k5 --> k6 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/text-application.mmd b/docs/site/engine-systems/diagrams/text-application.mmd new file mode 100644 index 00000000..d105676f --- /dev/null +++ b/docs/site/engine-systems/diagrams/text-application.mmd @@ -0,0 +1,60 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart TB + accTitle: Text · application font and label choices + accDescr: Local names omit Text::; ambiguous nested namespaces remain qualified. Arrows point toward the API or type used, not call order or data flow. Methods beneath type names are selected application entry points; Implement hooks marks engine-invoked overrides. Dashed arrows mark optional choices. + k0(["Application
init_platform()"]):::app + k1["Runtime::
Context


fonts(): FontService"]:::neighbor + k2["FontService

configure(FontConfiguration)

default_font(): Font

get(reference): Font"]:::focal + k3(["Application-owned UI"]):::app + k4["Font

reference()"]:::focal + k5(["Application
loading work"]):::app + k6("layout_text(Font, text, LayoutOptions): ShapedText"):::focal + k7["ShapedText

glyphs(): ShapedGlyph[]

lines(): TextLine[]"]:::focal + k8("Text::Rendering::prepare(
ShapedText,
Text::Rendering::RasterOptions):
Text::Rendering::Prepared"):::focal + k9["Text::Rendering::
Prepared"]:::focal + k10("Text::Rendering::upload(
Text::Rendering::Prepared,
Rendering::Resources::Provider,
Rendering::Resources::Material):
Text::Rendering::Generation"):::focal + k11["Rendering::Resources::
Provider"]:::neighbor + k12["Text::Rendering::
Generation"]:::focal + k13(["Application
update()"]):::app + k14["Text::Rendering::
Updater


request(Text::Rendering::Request)

update()

current(): Text::Rendering::Generation"]:::focal + k15["Text::Rendering::
Request"]:::focal + k16(["Application
prepare_render_frame()"]):::app + k17("Rendering::Submission::resolve_text(
Text::Rendering::Generation,
Rendering::DrawStyle2D,
Rendering::Submission::Context2D):
Rendering::DrawPacket2D[]"):::neighbor + k18["Rendering::
DrawPacket2D"]:::neighbor + k0 --> k1 + k1 --> k2 + k3 --> k4 + k2 --> k4 + k5 --> k6 + k6 --> k4 + k6 --> k7 + k5 --> k8 + k8 --> k7 + k8 --> k9 + k0 --> k10 + k10 --> k11 + k10 --> k9 + k10 --> k12 + k13 --> k14 + k14 --> k15 + k14 --> k12 + k16 --> k17 + k17 --> k12 + k17 --> k18 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/text-focus-static.mmd b/docs/site/engine-systems/diagrams/text-focus-static.mmd new file mode 100644 index 00000000..42e99519 --- /dev/null +++ b/docs/site/engine-systems/diagrams/text-focus-static.mmd @@ -0,0 +1,40 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Text · simple static label + accDescr: Local names omit Text::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0(["Application
init_platform()"]):::app + k1["FontService"]:::focal + k2["Font"]:::focal + k3("layout_text()"):::focal + k4["ShapedText"]:::focal + k5("Text::Rendering::
prepare()"):::focal + k6["Text::Rendering::
Prepared"]:::focal + k7("Text::Rendering::
upload()"):::focal + k8["Rendering::Resources::
Provider"]:::neighbor + k9["Text::Rendering::
Generation"]:::focal + k0 --> k1 + k1 --> k2 + k3 --> k2 + k3 --> k4 + k5 --> k4 + k5 --> k6 + k7 --> k6 + k7 --> k8 + k7 --> k9 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/text-focus-updater.mmd b/docs/site/engine-systems/diagrams/text-focus-updater.mmd new file mode 100644 index 00000000..84654f82 --- /dev/null +++ b/docs/site/engine-systems/diagrams/text-focus-updater.mmd @@ -0,0 +1,39 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Text · practical changing label + accDescr: Local names omit Text::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0(["Application
update()"]):::app + k1["Text::Rendering::
Updater"]:::focal + k2["Text::Rendering::
Request"]:::focal + k3["Workers::
Group"]:::neighbor + k4["Runtime::PlatformDispatcher::
Submission"]:::neighbor + k5["Text::Rendering::
Generation"]:::focal + k6(["Application
prepare_render_frame()"]):::app + k7("Rendering::Submission::
resolve_text()"):::neighbor + k8["Rendering::
DrawPacket2D"]:::neighbor + k0 --> k1 + k0 --> k2 + k1 --> k2 + k1 --> k3 + k1 --> k4 + k1 --> k5 + k6 --> k7 + k7 --> k5 + k7 --> k8 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/text-internal.mmd b/docs/site/engine-systems/diagrams/text-internal.mmd new file mode 100644 index 00000000..c25e9e98 --- /dev/null +++ b/docs/site/engine-systems/diagrams/text-internal.mmd @@ -0,0 +1,54 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Text · engine integration + accDescr: Local names omit Text::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["Runtime::
Context"]:::neighbor + k1["FontService"]:::focal + k2["Font"]:::focal + k3["FontCollection"]:::focal + k4("layout_text()"):::focal + k5["ShapedText"]:::focal + k6("Text::Rendering::
prepare()"):::focal + k7["Text::Rendering::
Prepared"]:::focal + k8("Text::Rendering::
upload()"):::focal + k9["Rendering::Resources::
Provider"]:::neighbor + k10["Text::Rendering::
Generation"]:::focal + k11["Rendering::Resources::
Material"]:::neighbor + k12["Text::Rendering::
Updater"]:::focal + k13["Workers::
Group"]:::neighbor + k14["Runtime::PlatformDispatcher::
Submission"]:::neighbor + k15["UI::TGUI::
Session"]:::neighbor + k16["UI::RmlUi::
Session"]:::neighbor + k0 --> k1 + k1 --> k2 + k2 --> k3 + k4 --> k2 + k4 --> k5 + k6 --> k5 + k6 --> k7 + k8 --> k7 + k8 --> k9 + k8 --> k10 + k10 --> k11 + k12 --> k10 + k12 --> k13 + k12 --> k14 + k15 --> k1 + k16 --> k1 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/utilities-application.mmd b/docs/site/engine-systems/diagrams/utilities-application.mmd new file mode 100644 index 00000000..585a7bd6 --- /dev/null +++ b/docs/site/engine-systems/diagrams/utilities-application.mmd @@ -0,0 +1,44 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart TB + accTitle: Utilities · application-local helpers + accDescr: Arrows point toward the API or type used, not call order or data flow. Methods beneath type names are selected application entry points; Implement hooks marks engine-invoked overrides. Dashed arrows mark optional choices. + k0(["Application
loading tool"]):::app + k1["Files::
FileIndex


FileIndex(Files::Path)

get_files_of_type(Files::Extension): Files::Path[]

search_directory(Files::Path)"]:::focal + k2["Time::
DeltaTime


operator()()

elapsed(): Time::DeltaTime<ClockType>::Duration"]:::focal + k3(["Application
bit bookkeeping"]):::app + k4["Math::
BitArray


operator[](index): Math::BitArray<Bits>::Reference

words(): Math::BitArray<Bits>::word_type[]"]:::focal + k5(["Application
geometry preparation"]):::app + k6["Rendering::Geometry::
Anchor


MakeQuad(
Rendering::Geometry::Pivot,
texture_width,
texture_height,
width,
height,
x0,
y0):
Rendering::Geometry::Quad

MakeQuadStrip(
Rendering::Geometry::Pivot,
texture_width,
texture_height,
width,
height,
x0,
y0):
Rendering::Geometry::QuadTriangleStrip"]:::focal + k7["Rendering::Geometry::
Pivot"]:::focal + k8(["Application
storage owner"]):::app + k9("Memory::adjust_length(length, Memory::FitPolicy, gb, gf)"):::focal + k10["Memory::
FitPolicy"]:::focal + k11(["Application
configuration parsing"]):::app + k12("Text::Numbers::parse_integers<is_unsigned>(str):
Text::Numbers::NumberVariant"):::focal + k13("Text::Numbers::parse_floats(str)"):::focal + k0 --> k1 + k0 --> k2 + k3 --> k4 + k8 --> k9 + k9 --> k10 + k5 --> k6 + k6 --> k7 + k11 --> k12 + k11 --> k13 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/utilities-internal.mmd b/docs/site/engine-systems/diagrams/utilities-internal.mmd new file mode 100644 index 00000000..a9cf11ce --- /dev/null +++ b/docs/site/engine-systems/diagrams/utilities-internal.mmd @@ -0,0 +1,41 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Utilities · engine consumers + accDescr: Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["Assets::Loading::
Loader"]:::neighbor + k1["Files::
FileIndex"]:::focal + k2["Memory::
Manager"]:::neighbor + k3("Memory::
adjust_length()"):::focal + k4["Memory::
FitPolicy"]:::focal + k5("Assets::Grids::
make_geometry()"):::neighbor + k6["Rendering::Geometry::
Pivot"]:::focal + k7["Assets::Sprites::
Definition"]:::neighbor + k8("Text::Numbers::
string_to_number()"):::focal + k9("Text::Numbers::
parse_floats()"):::focal + k10("Text::Numbers::
parse_integers()"):::focal + k11["Time::
DeltaTime"]:::focal + k12["Math::
BitArray"]:::focal + k0 --> k1 + k2 --> k3 + k3 --> k4 + k5 --> k6 + k7 --> k6 + k8 --> k9 + k8 --> k10 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/workers-application.mmd b/docs/site/engine-systems/diagrams/workers-application.mmd new file mode 100644 index 00000000..e7e5ea82 --- /dev/null +++ b/docs/site/engine-systems/diagrams/workers-application.mmd @@ -0,0 +1,46 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Workers · application CPU preparation + accDescr: Local names omit Workers::; ambiguous nested namespaces remain qualified. Arrows point toward the API or type used, not call order or data flow. Methods beneath type names are selected application entry points; Implement hooks marks engine-invoked overrides. Dashed arrows mark optional choices. + k0(["Application
composition"]):::app + k1["Runtime::
ExecutionOptions"]:::neighbor + k2["Pool

make_group(GroupOptions): Group

shutdown()"]:::focal + k3["Runtime::
Context


make_worker_group(GroupOptions): Group"]:::neighbor + k4["Group

submit(Work): Result

close()

drain()"]:::focal + k5(["Application
CPU preparation"]):::app + k6["GroupOptions"]:::focal + k7["CpuPolicy"]:::focal + k8["Assets::Loading::
Loader


prepare(): Assets::Loading::Prepared"]:::neighbor + k9["Runtime::PlatformDispatcher::
Submission


submit(Work): Result"]:::neighbor + k10(["Application
cleanup / quiesce()"]):::app + k0 --> k1 + k1 --> k2 + k0 --> k3 + k3 --> k4 + k3 --> k2 + k5 --> k4 + k4 --> k6 + k6 --> k7 + k5 --> k8 + k5 --> k9 + k10 --> k4 + k2 --> k4 + k2 --> k6 + k3 --> k6 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/workers-focus-groups.mmd b/docs/site/engine-systems/diagrams/workers-focus-groups.mmd new file mode 100644 index 00000000..640abe53 --- /dev/null +++ b/docs/site/engine-systems/diagrams/workers-focus-groups.mmd @@ -0,0 +1,34 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Workers · simple Context-owned groups + accDescr: Local names omit Workers::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0(["Application
composition"]):::app + k1["Runtime::
ExecutionOptions"]:::neighbor + k2["Runtime::
Context"]:::neighbor + k3["Pool"]:::focal + k4["Group"]:::focal + k5(["Application
CPU preparation"]):::app + k6["GroupOptions"]:::focal + k0 --> k1 + k2 --> k1 + k2 --> k3 + k2 --> k4 + k5 --> k4 + k4 --> k6 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/workers-focus-shared.mmd b/docs/site/engine-systems/diagrams/workers-focus-shared.mmd new file mode 100644 index 00000000..a8f27352 --- /dev/null +++ b/docs/site/engine-systems/diagrams/workers-focus-shared.mmd @@ -0,0 +1,37 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart LR + accTitle: Workers · practical pool shared by contexts + accDescr: Local names omit Workers::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0(["Application
composition"]):::app + k1["Pool"]:::focal + k2["Runtime::
ExecutionOptions"]:::neighbor + k3["Runtime::
Context
(context A)"]:::neighbor + k4["Runtime::
Context
(context B)"]:::neighbor + k5["Group"]:::focal + k6["Group"]:::focal + k0 --> k1 + k0 --> k2 + k2 --> k1 + k3 --> k2 + k4 --> k2 + k3 --> k1 + k4 --> k1 + k3 --> k5 + k4 --> k6 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/diagrams/workers-internal.mmd b/docs/site/engine-systems/diagrams/workers-internal.mmd new file mode 100644 index 00000000..55bc36de --- /dev/null +++ b/docs/site/engine-systems/diagrams/workers-internal.mmd @@ -0,0 +1,48 @@ +--- +config: + theme: base + themeVariables: + fontFamily: system-ui, sans-serif + fontSize: 14px + flowchart: + htmlLabels: true + curve: linear + nodeSpacing: 22 + rankSpacing: 38 + padding: 12 +--- +flowchart TB + accTitle: Workers · engine integration + accDescr: Local names omit Workers::; ambiguous nested namespaces remain qualified. Arrows point toward dependencies. Application roles are rounded; named API operations have rounded corners. Dashed arrows mark optional choices. + k0["Runtime::
Context"]:::neighbor + k1["Runtime::
ExecutionOptions"]:::neighbor + k2["Pool"]:::focal + k3["Group"]:::focal + k4["PoolOptions"]:::focal + k5["TopologyProvider"]:::focal + k6["Topology"]:::focal + k7["GroupOptions"]:::focal + k8["GroupStatus"]:::neighbor + k9["CpuPolicy"]:::focal + k10["PolicyStrength"]:::focal + k11["Text::Rendering::
Updater"]:::neighbor + k12("Events::
worker_delivery()"):::neighbor + k0 --> k1 + k1 --> k2 + k0 --> k2 + k0 --> k3 + k2 --> k4 + k2 --> k3 + k2 --> k5 + k5 --> k6 + k3 --> k7 + k3 --> k8 + k7 --> k9 + k9 --> k10 + k11 --> k3 + k12 --> k3 + classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px + classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px + classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3 + classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px + linkStyle default stroke:#7e8fa7,stroke-width:1.2px diff --git a/docs/site/engine-systems/dispatch.md b/docs/site/engine-systems/dispatch.md new file mode 100644 index 00000000..f437d0e9 --- /dev/null +++ b/docs/site/engine-systems/dispatch.md @@ -0,0 +1,132 @@ +# Dispatch + +## Purpose + +Dispatch transfers owned work to the platform/graphics or simulation owner. +Use it when an operation requires that owner, rather than running arbitrary CPU +work through an owner queue or calling a thread-affine adapter directly. + +## Interface + +Types below are in `CE::Runtime` unless another namespace is shown. + +### Types and operations + +| API and purpose | +| --- | +| {cpp:class}`PlatformDispatcher `
Submit work taking `Context&` to platform/graphics through the context. [Platform dispatch](../runtime/thread-dispatch.md). | +| {cpp:class}`SimulationDispatcher `
Submit work without a context argument to simulation through the runtime. [Simulation boundary](../runtime/thread-dispatch.md#simulation-boundary). | +| {cpp:class}`PlatformDispatcher::Submission `, {cpp:class}`SimulationDispatcher::Submission `
Save copyable submission endpoints that reject safely after closure/destruction. [Saved endpoints and cancellation](../runtime/thread-dispatch.md). | +| {cpp:struct}`CE::Diagnostics::DispatchStats`
Inspect accepted/completed/pending observations without treating queue emptiness as completion. [Dispatcher statistics](../runtime/subsystem-diagnostics.md#dispatcher-statistics). | + +### Submission and completion + +| Operation | Purpose and details | +| --- | --- | +| `submit()` | Decay-own work/captures and retain its future; borrowed pointees still need a lifetime. [Ownership and future results](../runtime/thread-dispatch.md). | +| `submission()` | Retain queue state independently of the borrowed service facade. [Endpoint lifetime](../runtime/thread-dispatch.md). | +| Future readiness / result | Consume accepted results/errors without waiting on the owner needed to produce them. [Dependency progress](../runtime/runtime-frame-boundary.md#queued-preparation-and-stop-requests). | +| Runtime closure | Settle required accepted dependencies, then cancel unexecuted work before game cleanup. [Shutdown order](../runtime/runtime-frame-boundary.md#shutdown). | + +## Usage + +Save a copyable submission endpoint during initialization. Producers submit owned +inputs through that endpoint and retain the returned future; a later update +adopts the result after readiness is observed. + +```{engine-diagram} dispatch-application +``` + +[Explore dispatcher ownership and engine consumers](diagram-explorer.md#dispatch-internal). + +### Choose the destination + +| Work | Queue or service | Execution boundary | +| --- | --- | --- | +| Upload resources, mutate/query a native window, perform graphics operations | `engine.platform_dispatcher().submission()` | {cpp:class}`Runtime::PlatformDispatcher ` invokes `work(Runtime::Context&)` on platform with the selected graphics context. | +| Apply an owned result to simulation state | `runtime.simulation_dispatcher().submission()` | {cpp:class}`Runtime::SimulationDispatcher ` invokes `work()` at the scheduled simulation boundary before update. Posting does not create an early tick. | +| Decode, shape or otherwise prepare CPU data | {cpp:class}`Workers::Group ` | Independent CPU capacity; the application arranges later owner upload/adoption. [Workers](workers.md). | +| Observe an event on a selected owner | `Events::platform_delivery()` / `simulation_delivery()` | Adapt a saved endpoint into event delivery; retain a registration and independent error sink. [Events](events.md#uses). | + +Submission always queues, even when the producer is already on the destination +owner. Capture owned values, keep borrowed targets alive through execution or +cancellation, and avoid waiting on work that needs the current owner. The +[complete result-adoption example](../runtime/thread-dispatch.md#submit-and-adopt-a-result) +shows endpoint storage, readiness checking and error handling. + +### Uses + +| Dependency | Dispatch's use | +| --- | --- | +| {cpp:class}`Runtime::Context ` | Supply the platform callback's live adapter/resource access. Simulation callbacks receive no Context argument. [Runtime](runtime.md). | +| {cpp:struct}`Diagnostics::DispatchStats ` | Observe accepted, queued, running, completed and cancelled work. These counters do not replace the request's future. [Diagnostics](diagnostics.md). | +| [Logging](logging.md) | Report queue lifecycle and high-water observations; callback failures remain with their futures. | + +`GameRuntime` controls opening, owner binding, draining and closure. A saved +endpoint retains queue state, without retaining Context or native adapters. + +### Used by + +| Consumer | What it submits | +| --- | --- | +| {cpp:class}`Runtime::GameRuntime ` | Service owner batches and settle/cancel outstanding work in the session shutdown order. | +| {cpp:class}`UI::TGUI::SceneUploader `, {cpp:class}`UI::RmlUi::SceneUploader ` | Upload owned prepared scene data to the graphics owner. [GUI](gui.md). | +| [Text updates](../getting-started/how-to/update-text.md) | Prepare away from graphics, submit upload and adopt a ready generation on simulation. | +| [Event delivery adapters](../runtime/event-delivery.md#optional-queued-delivery) | Transfer optional callbacks through a saved platform/simulation endpoint; the bus itself does not own a dispatcher. | +| Application asset preparation or jobs | Submit owner-affine follow-up using saved endpoints. A Loader or worker pool does not automatically schedule that orchestration. [Assets](assets.md). | + +## Contracts + +Each drain detaches one FIFO batch. New/nested posts wait for another drain; +submission never executes inline. Captures are decay-owned, but references and +reference-valued results still borrow targets. Accepted exceptions reach the +future; unexecuted cancellation breaks its promise. Never wait on a future from +the owner needed to complete it. + +Endpoints retain queue state rather than borrowed service pointers. They reject +before opening, after closing and after destruction. `has_pending()` only sees +queued work; an empty queue does not establish completion of a detached batch. + +## Lifecycle + +Runtime opens queues during initialization and binds simulation to its selected +owner. Save endpoints for longer-lived adapters without extending backend +lifetime. During shutdown, runtime services accepted dependencies while joining +simulation and settling tracked CPU work, then cancels remaining requests before +game cleanup. Borrowed captures must survive either execution or cancellation. + +## Execution + +Platform requests run with the runtime's selected graphics context on platform. +Simulation batches drain before scheduled updates without consuming input merely +because mailbox work ran. Cross-domain ordering follows explicit dependencies; +there is no total order between independent platform, simulation and worker work. + +## Integration + +During `init_platform()`, obtain a copyable endpoint and retain it in the producer that +will submit requests. During the live session that producer can request a window +observation on its owner: + +```cpp +auto platform_requests = engine_.platform_dispatcher().submission(); // Save during init_platform(). +// Later, the producer uses its saved endpoint: +auto pending = platform_requests.submit([](CE::Runtime::Context& owner) { + return owner.window().framebuffer_size(); +}); +``` + +The endpoint and `pending` are retained values, not temporary service pointers. +[The complete readiness example](../runtime/thread-dispatch.md#submit-and-adopt-a-result) +shows their member storage and adoption during update. Inspect future readiness +from later updates; do not block simulation while it +depends on platform progress. Ordinary game layout should use tick dimensions +directly; the example shows where an explicitly requested live observation runs. + +## Limits and further reading + +Dispatch is not a general executor, a completion barrier based on queue emptiness +or automatic lifetime extension for captured raw pointers. Read +[thread dispatch](../runtime/thread-dispatch.md), +[runtime shutdown](../runtime/runtime-frame-boundary.md#shutdown) and +[the platform interface](../../../projects/engine/include/cheryl/core/engine/platform-dispatcher.h). diff --git a/docs/site/engine-systems/display.md b/docs/site/engine-systems/display.md new file mode 100644 index 00000000..82b0c5f1 --- /dev/null +++ b/docs/site/engine-systems/display.md @@ -0,0 +1,132 @@ +# Display + +## Purpose + +Display manages native windows, monitor inventory, modes and dimensions. It +supplies the window used by input and presentation; rendering is a separate +responsibility. + +## Interface + +Types below are in `CE::Platform` unless another namespace is shown. + +### Types and operations + +| API and purpose | +| --- | +| {cpp:class}`DisplaySystem `
Enumerate monitors, create/select display-owned windows and query monitor scale. [Inventory and selection](../runtime/display-and-window-contract.md#native-glfw-inventory-and-selection); [adapter catalogue](../architecture/interfaces-adapters.md#runtime-adapters). | +| {cpp:class}`Window `
Read size/mode/close state, request resize/mode/cursor changes and check deferred failures. [Sizes, modes and observation](../runtime/display-and-window-contract.md#sizes-modes-and-observation). | +| {cpp:struct}`Monitor `
Inspect a borrowed monitor inventory entry; copy values before deferred use. [Monitor identity and freshness](../runtime/display-and-window-contract.md#native-glfw-inventory-and-selection). | +| {cpp:struct}`WindowResized `, `CE::Platform::window_resized_event`
Receive copied drawable pixels and a display-borrowed window pointer. [Resize observations](../runtime/display-and-window-contract.md#sizes-modes-and-observation); [typed channels](../runtime/event-delivery.md#typed-channels). | + +[Native GLFW](../modules/native-glfw.md) implements these desktop contracts. + +### Dimensions and modes + +| Type or choice | Purpose and details | +| --- | --- | +| {cpp:struct}`Dimensions ` | Logical window coordinates; creation/resize requests need positive dimensions. [Size units](../runtime/display-and-window-contract.md#sizes-modes-and-observation). | +| {cpp:struct}`FramebufferSize ` | Drawable pixels, which may be zero; runtime copies them into ticks. [Framebuffer observations](../runtime/display-and-window-contract.md#sizes-modes-and-observation). | +| {cpp:enum}`WindowMode ` | `NORMAL`, `BORDERLESS`, `FULLSCREEN`; backend policy determines supported transitions. [Window modes](../runtime/display-and-window-contract.md#sizes-modes-and-observation). | +| Monitor content scale | X/Y scale for a recognized monitor snapshot; separate from per-window notification and explicit UI font scale. [Scale and UI capabilities](../runtime/display-and-window-contract.md#scale-and-ui-capabilities). | + +## Usage + +Use the selected window directly during platform initialization or an explicit +platform-dispatch callback. During updates, use the runtime's copied dimensions; +that route works in both sequential and concurrent mode. + +```{engine-diagram} display-application +``` + +[Explore the engine-side display relationships](diagram-explorer.md#display-internal). +Arrows point from a consumer to the type it uses. + +### Choose a window observation + +| Need | Use | What the value means | +| --- | --- | --- | +| Size gameplay or a camera for this update | {cpp:struct}`Game::TickContext ` | The logical/pixel size pair selected for this simulation tick. | +| Apply required UI sizing before dependent updates | `engine.simulation_view().watch(...)` | A seeded, simulation-owned selection; retain its {cpp:class}`Runtime::ViewAttachment `. | +| Inspect current copied platform state from another thread | `engine.window_state().snapshot()` | A synchronized {cpp:struct}`Runtime::WindowState `, including optional native focus. No native window is retained. | +| Change mode, size or cursor visibility; query a live window | `engine.window()` on platform | A borrowed {cpp:class}`Platform::Window ` with native owner requirements. Dispatch explicit requests from other owners. | +| Observe a raw native framebuffer resize | `Platform::window_resized_event` | Copied pixels plus a borrowed window pointer. Native GLFW publishes on the default `Events::EventSystem` bus. | +| Observe the runtime's sampled main-window close intent | `Platform::window_close_requested_event` on `engine.events()` | An owned intent fact; negotiation belongs to Runtime's shutdown policy. | + +Prefer the selected runtime view for game/UI layout. The raw resize channel and +Context bus are distinct routes. A raw callback may be followed by another native +change before runtime selects its view. See +[committed views](../runtime/display-and-window-contract.md#committed-window-and-simulation-views) +and [native close intent](../runtime/display-and-window-contract.md#native-close-intent). + +### Uses + +| Dependency | Display's use | +| --- | --- | +| {cpp:struct}`Platform::Monitor ` | Identify entries from this display's monitor inventory when creating windows or querying scale. | +| {cpp:struct}`Platform::Dimensions `, {cpp:struct}`Platform::FramebufferSize ` | Keep logical coordinates and drawable pixels distinct. Zero pixel extent can describe a minimized drawable. | +| {cpp:struct}`Events::EventSystem ` | Native GLFW dispatches legacy and typed framebuffer-resize observations through the default bus. [Events](events.md#engine-events). | + +Native-library state belongs to the selected backend; the portable display +interface does not choose or own a renderer. + +### Used by + +| Consumer | Relationship to the window | +| --- | --- | +| {cpp:class}`Runtime::Context `, {cpp:class}`Runtime::GameRuntime ` | Context owns the display; Runtime samples its active window and coordinates dependent teardown. [Runtime](runtime.md). | +| {cpp:class}`Input::System ` | Borrow the live window for native callbacks; detach while it is still alive. [Input](input.md). | +| {cpp:class}`Rendering::OpenGL::GLFWContext ` | Use the compatible native window for presentation/context operations. [OpenGL integration](../modules/opengl.md). | +| {cpp:class}`Rendering::Camera2D ` and application layout | Consume copied drawable dimensions; a camera does not own a native window. [Rendering](rendering.md). | +| {cpp:class}`UI::TGUI::Session `, {cpp:class}`UI::RmlUi::Session ` | Follow copied simulation dimensions through the supported session integration. [GUI](gui.md). | + +## Contracts + +Window dimensions use logical window coordinates; framebuffer dimensions use +drawable pixels and may be zero. Creation/resize requests require positive +logical dimensions. Monitor inventory references are borrowed and expire on +backend refresh or destruction. Copy values before deferred use. +The [current Native GLFW inventory](../runtime/display-and-window-contract.md#native-glfw-inventory-and-selection) +is captured once at construction and has no hotplug refresh. + +Monitor content scale is not a per-window scale notification contract. Window +activation alone does not switch a renderer's graphics-resource domain. A +resize payload's window pointer neither extends its lifetime nor grants access +from another thread. + +## Lifecycle + +The display owns created windows; pointers returned to callers are borrowed. +Construct compatible input/presentation dependents while the display is live. +Detach callbacks and close graphics dependents before destroying their window. +Runtime coordinates the normal session cleanup; application-supplied services +must preserve the same order during partial initialization and failure. + +## Execution + +Native display/window operations run on the platform owner. Queries do not +synchronize access from simulation. Resize callbacks remain on platform; pass +their copied observations through runtime input/frame handoff rather than +mutating simulation state directly. + +## Integration + +Update a simulation-owned camera from the runtime's copied observation: + +```cpp +void update(const CE::Game::TickContext& tick) override { + camera_.set_framebuffer_size(tick.framebuffer_size); + // Update the game without querying engine_.window() here. +} +``` + +Game initialization can read the live window on platform. Later updates use the +tick in either runtime mode, so the same game remains safe when concurrent. + +## Limits and further reading + +Read [display/window contracts](../runtime/display-and-window-contract.md) for +monitor freshness, mode transitions, scale and native failure limits. The +[Window](../../../projects/engine/include/cheryl/core/display/window-interface.h) +and [DisplaySystem](../../../projects/engine/include/cheryl/core/display/display-system-interface.h) +headers define the portable interface; module details stay in the owner guide. diff --git a/docs/site/engine-systems/events.md b/docs/site/engine-systems/events.md new file mode 100644 index 00000000..62e6a1fe --- /dev/null +++ b/docs/site/engine-systems/events.md @@ -0,0 +1,200 @@ +# Events + +## Purpose + +Events delivers notifications through persistent registrations. A bus owns +callbacks and channel identity; it does not create a thread, choose simulation +timing or automatically own the objects borrowed by those callbacks. + +## Interface + +Types below are in `CE::Events` unless another namespace is shown. + +### Types and operations + +| API and purpose | +| --- | +| {cpp:class}`EventBus `
Own listener registrations, dispatch, remove/wait and close acceptance. [Registrations](../runtime/event-delivery.md#registrations); [removal and shutdown](../runtime/event-delivery.md#removal-and-shutdown). | +| {cpp:class}`EventChannel `
Give a copy-constructible payload exact name/type identity. [Typed channels](../runtime/event-delivery.md#typed-channels). | +| {cpp:class}`EventBus::Registration `
Retain an explicit removal identity; dropping it does not unregister. [Registration lifetime](../runtime/event-delivery.md#registrations). | +| {cpp:class}`EventBus::Publisher `
Retain a weak notification route without retaining the bus or Context. [Publishing handles](../runtime/event-delivery.md#weak-publishing-handles). | +| {cpp:type}`EventBus::Delivery `, {cpp:type}`EventBus::ErrorHandler `
Select queued execution and an independent failure sink. [Queued delivery](../runtime/event-delivery.md#optional-queued-delivery); [adapter catalogue](../architecture/interfaces-adapters.md#integrations-alongside-the-runtime). | +| {cpp:struct}`EventSystem `
Access the default bus when an isolated application-owned bus is unnecessary. [Bus ownership](../runtime/event-delivery.md). | + +### Delivery and cleanup choices + +| Choice | Purpose and details | +| --- | --- | +| Immediate delivery | Invoke on each dispatch caller, in registration order; concurrent callers may overlap. [Immediate delivery](../runtime/event-delivery.md#immediate-delivery). | +| Platform / simulation delivery | Queue owned payloads through a saved owner endpoint; never execute inline. [Queued adapters](../runtime/event-delivery.md#optional-queued-delivery); [dispatch](../runtime/thread-dispatch.md). | +| Worker delivery | Give one listener stream serial FIFO execution over shared group capacity. [Worker streams](../runtime/event-delivery.md#worker-stream-acceptance-and-cancellation). | +| Unregister / unregister-and-wait / close | Invalidate new invocation, optionally settle entered callbacks, or close all acceptance. [Removal and shutdown](../runtime/event-delivery.md#removal-and-shutdown). | + +## Usage + +Register on the bus selected by the producer, choose the callback's execution +owner, and retain the registration for explicit cleanup. Use the Context bus for +runtime observations or an application-owned bus for an isolated domain. + +```{engine-diagram} events-focus-delivery +``` + +[Explore the immediate-only path](diagram-explorer.md#events-focus-immediate), +[application registrations](diagram-explorer.md#events-application), or +[engine producers and delivery dependencies](diagram-explorer.md#events-internal). + +### Choose delivery and participation + +| Need | Choose | Responsibility | +| --- | --- | --- | +| Observe immediately on the publishing owner | No delivery adapter | The callback runs synchronously; overlapping publishers may enter it concurrently. | +| Change platform/graphics-owned state | `Events::platform_delivery(endpoint)` | Save a platform submission endpoint and supply an independently owned, nonthrowing error sink. | +| Change simulation-owned gameplay or UI | `Events::simulation_delivery(endpoint)` | Save a simulation endpoint; callback entry occurs at a scheduled simulation boundary. | +| Process observations serially away from owners | `Events::worker_delivery(group)` | Keep physical worker capacity alive; copies of one adapter share one FIFO stream. | +| Apply required sizing or recurring runtime work | View/service attachment | Use the owner's `watch()`, `on_maintenance()` or `on_step()` API and retain its attachment. An optional event cannot gate dependent runtime work. | + +A bus subscription has no replay. Read the producer's retained snapshot or use +its seeded watch when current state matters. The full +[observation/participation contract](../runtime/event-delivery.md#observe-a-change-or-participate-in-an-operation) +and [queued-delivery example](../runtime/event-delivery.md#optional-queued-delivery) +cover setup, failure handling and teardown. + +### Uses + +| Dependency | Where it belongs | +| --- | --- | +| {cpp:class}`Events::EventChannel ` and the exact payload type | The bus identifies typed subscriptions by channel name and payload type. | +| {cpp:type}`Events::EventBus::Delivery `, {cpp:type}`Events::EventBus::ErrorHandler ` | Optional queued transport and its required error authority. The immediate bus itself owns no dispatcher or worker pool. | +| {cpp:class}`Runtime::PlatformDispatcher::Submission `, {cpp:class}`Runtime::SimulationDispatcher::Submission ` | Retained by the optional owner-delivery adapters. [Dispatch](dispatch.md). | +| {cpp:class}`Workers::Group ` | Shared CPU capacity used by the optional worker-delivery adapter. [Workers](workers.md). | +| {cpp:type}`Diagnostics::DomainId ` | Identify bus observations and failures; diagnostics does not settle callbacks. [Diagnostics](diagnostics.md). | + +### Used by + +| Consumer | How it connects | +| --- | --- | +| [Runtime](runtime.md), [Input](input.md), [Resources](resources.md) and [Rendering](rendering.md) | Runtime connects supported producers to its Context bus at session start. View/lifecycle/service facts are also published there. | +| Native GLFW [Display](display.md) | Publish raw framebuffer resize through the default `Events::EventSystem` bus. | +| [Asset loaders/catalogues](assets.md) | Publish executed upload outcomes and committed exact-key catalogue changes through configured weak routes. | +| [Audio output](audio.md) | Stage terminal playback outcomes and explicitly publish them on its designated owner. | +| Application producers/listeners | Define their own channels and bus boundaries; own callback targets and explicit registration removal. | + +### Engine events + +The following are the public typed channels supplied by Engine and its modules. +Names omit the leading `CE::`. Register on the producer's bus, not merely on a +channel with the same spelling. Unless stated otherwise, Context-connected +producers below publish through `engine.events()`; the default bus is a separate +instance. Payloads describe observations, not authority to change the producing +system. + +#### Window, view and input observations + +| Channel and payload | Producer and meaning | +| --- | --- | +| `Platform::window_resized_event`
{cpp:struct}`Platform::WindowResized ` | Native GLFW, on platform via `Events::EventSystem::get()`: changed drawable pixels and a borrowed window pointer. The legacy named channel is offered first. Retain the size alone for deferred use. [Resize contract](../runtime/display-and-window-contract.md#sizes-modes-and-observation). | +| `Platform::window_close_requested_event`
{cpp:struct}`Platform::WindowCloseRequested ` | Runtime, on platform: one sampled main-window close intent with owned context/session/source/intent IDs. No window pointer or shutdown decision capability. [Close bridge](../runtime/display-and-window-contract.md#native-close-intent). | +| `Runtime::window_state_changed_event`
{cpp:struct}`Runtime::WindowStateChanged ` | Runtime's platform selection: previous/current logical size, pixels and optional native focus. Read `engine.window_state().snapshot()` for current state. [Committed views](../runtime/display-and-window-contract.md#committed-window-and-simulation-views). | +| `Runtime::simulation_view_changed_event`
{cpp:struct}`Runtime::SimulationViewChanged ` | Runtime's simulation selection before a dependent tick: previous/current copied size pairs. Required sizing uses `engine.simulation_view().watch()` instead. [View contract](../runtime/display-and-window-contract.md#committed-window-and-simulation-views). | +| `Input::gamepad_availability_changed_event`
{cpp:struct}`Input::GamepadAvailabilityChanged ` | Input's platform owner after complete polls and explicit deinitialization: current/previous convenience-slot state. Deinitialization does not mean physical disconnection; destruction closes the source silently. Subscribe then read `gamepad_availability()` on platform. [Availability](../runtime/input-state-model.md#gamepad-availability). | + +#### Session and shutdown observations + +All six channels run their immediate observers on platform and carry an owned +lifecycle selection. `runtime.lifecycle().snapshot()` retains current/terminal +state after runtime destruction. Milestone delivery does not replay to a late +subscriber. + +| Channel and payload | When it is observed | +| --- | --- | +| `Runtime::runtime_ready_event`
{cpp:struct}`Runtime::RuntimeReady ` | Successful owner/game initialization, before the first update. Simulation is still gated during immediate Ready fanout; do not synchronously wait for it. | +| `Runtime::shutdown_starting_event`
{cpp:struct}`Runtime::ShutdownStarting ` | Shutdown commits, before simulation joins; partial startup can reach this milestone without Ready. | +| `Runtime::runtime_quiescing_event`
{cpp:struct}`Runtime::RuntimeQuiescing ` | Simulation/mailbox has stopped and applicable game quiescence has been attempted, before accepted CPU work settles. | +| `Runtime::runtime_stopped_event`
{cpp:struct}`Runtime::RuntimeStopped ` | Ordered cleanup was attempted and the terminal outcome/counters were retained. Failed cleanup does not prove successful native resource release. | +| `Runtime::shutdown_requested_event`
{cpp:struct}`Runtime::ShutdownRequested ` | An ordinary request was admitted Pending. Only the installed host policy receives the decision capability. | +| `Runtime::shutdown_request_cancelled_event`
{cpp:struct}`Runtime::ShutdownRequestCancelled ` | An ordinary request was cancelled, with its identity and reason. Forced supersession does not fabricate cancellation. | + +Read the [lifecycle milestones](../runtime/runtime-frame-boundary.md#lifecycle-observation) +and [negotiable shutdown protocol](../runtime/runtime-frame-boundary.md#negotiable-shutdown) +for their ordering and retained request state. + +#### Runtime service observations + +| Channel and payload | Producer and meaning | +| --- | --- | +| `Runtime::platform_maintenance_event`
{cpp:struct}`Runtime::PlatformMaintenance ` | A real platform service pass, including its reason and pass identity. Maintenance does not pump native input. | +| `Runtime::simulation_step_starting_event`
{cpp:struct}`Runtime::SimulationStepStarting ` | Simulation selected input/view and an attempted step's timing before the game hook. This does not prove that update completed. | + +Use [required service attachments](../runtime/runtime-frame-boundary.md#runtime-service-phases) +for work that must finish before dependent runtime operations. + +#### Asset, graphics and audio observations + +| Channel and payload | Producer, setup and meaning | +| --- | --- | +| `Assets::Loading::asset_upload_settled_event`
{cpp:struct}`Assets::Loading::AssetUploadSettled ` | Loader's serialized loading owner: one aggregate outcome per executed attempt, with counts and an owned failure. Configure `loader.publish_events_to(...)`; CPU preparation and pre-execution cancellation produce no settlement event. Direct return/exception/future remains authoritative. [Upload settlement](../assets/asset-loading.md#upload-settlement-notifications). | +| `Assets::Cache::asset_catalogue_changed_event`
{cpp:struct}`Assets::Cache::AssetCatalogueChanged ` | Catalogue drainer, potentially a producer thread: an exact-key publish/replace or reset, with generations and loss metadata. Each asset/key type pair is a distinct channel. Runtime routes provider catalogues; standalone sources opt in. Read a snapshot or seeded watch to reconcile current keys. [Catalogue changes](../assets/asset-loading.md#catalogue-changes). | +| `Rendering::graphics_domain_state_changed_event`
{cpp:struct}`Rendering::GraphicsDomainStateChanged ` | Graphics owner: actual native availability/invalidation, with a historical snapshot and retained observation token. Runtime configures renderer routing. The token retains no native objects. [Graphics domains](../resources/resource-residency.md#graphics-domain-observation). | +| `Audio::playback_ended_event`
{cpp:struct}`Audio::PlaybackEnded ` | Designated audio publication owner: a logical voice generation's terminal reason. The application configures a weak publisher and explicitly pumps staged records, optionally through runtime maintenance. Counters expose bounded-record loss. [Playback outcomes](../runtime/audio.md#playback-outcomes). | + +No public event family currently reports `FontService` catalogue/configuration changes, monitor hotplug, +full physical-device topology, or OS suspend/resume. Input's ordered `Input::Events::Record` stream is +read from ticks/polls, separately from these bus channels. Runtime pressure and +shutdown-policy transport channels are implementation details. + +## Contracts + +Registration persists until explicit removal or bus destruction; dropping its +identifier does not unregister. Immediate callbacks run in registration order +on each dispatch caller, and overlapping dispatches can overlap listeners. +Typed channels require the exact copy-constructible payload type. + +Unregister prevents fresh invocation but a callback already entered may finish. +Use `unregister_and_wait()` before destroying a borrowed target, without holding +a lock that callback needs. Waiting from that same listener rejects. Queued +payloads own their copied values; pointees remain governed by the payload type. + +## Lifecycle + +Construct an owned bus or deliberately use the default. Retain registration IDs +for cleanup. Stop producers, invalidate registrations and settle entered +callbacks before destroying borrowed targets. `close()` invalidates and rejects +new work but does not wait for running callbacks. An independent queued error +sink must survive pending-ticket destruction even after its listener is removed. + +## Execution + +Immediate delivery is synchronous on dispatch's caller. Queued delivery owns +the work without invoking it inline, preserving each listener's FIFO stream. +Platform/simulation adapters follow those owners; copies of one worker-delivery +adapter share a serial stream. Independent listeners/targets have no global +completion order. Immediate `dispatch()` failures propagate and stop its remaining +listeners; `notify()` isolates failures and attempts later listeners. Queued +failures reach the registration-owned error sink. + +## Integration + +An application-owned bus can notify simulation-owned listeners synchronously: + +```cpp +CE::Events::EventBus bus; +const CE::Events::EventChannel score_changed{"score-changed"}; +int displayed_score = 0; +auto registration = bus.register_listener(score_changed, [&](const int& value) { + displayed_score = value; +}); +bus.dispatch(score_changed, 10); +static_cast(bus.unregister_and_wait(registration)); +``` + +Here the caller supplies synchronization by performing all operations on one +owner. Choose a delivery adapter before registering when producers use other +threads. + +## Limits and further reading + +Channel names/RTTI form an in-process identity, not a serialization protocol. +The default bus does not provide automatic callback lifetime or UI ownership. +Read [event delivery and failure contracts](../runtime/event-delivery.md), +[thread dispatch](../runtime/thread-dispatch.md) and +[the bus interface](../../../projects/engine/include/cheryl/core/subsystems/event-bus.h). diff --git a/docs/site/engine-systems/gui.md b/docs/site/engine-systems/gui.md new file mode 100644 index 00000000..418569a0 --- /dev/null +++ b/docs/site/engine-systems/gui.md @@ -0,0 +1,157 @@ +# GUI + +## Purpose + +GUI integrations connect toolkit-native widgets/documents to Engine input and +retained rendering. The application controls session lifetime, UI ownership, +focus, pointer selection and integration with game state. + +## Interface + +Types below are in `CE::UI` unless another namespace is shown. + +The [TGUI](../modules/tgui.md) and [RmlUi](../modules/rmlui.md) modules each own +their session, native authoring API and recording/publication types. + +### Types and operations + +| API and purpose | +| --- | +| {cpp:class}`TGUI::Session `, {cpp:class}`RmlUi::Session `
Own toolkit lifetime; supply copied dimensions, time and input, and manage explicit keyboard focus. [Session construction](../gui/index.md#construct-widgets-on-the-ui-owner); [routing](../gui/index.md#route-input-and-focus-explicitly). | +| {cpp:class}`TGUI::RecordedScene `, {cpp:class}`RmlUi::RecordedScene `
Retain owned CPU recordings without live toolkit objects crossing to graphics. [Update and publication](../development/ui-adapters.md#update-publication-and-shutdown). | +| {cpp:class}`TGUI::SceneUploader `, {cpp:class}`RmlUi::SceneUploader `
Transfer owned recordings to the provider owner and publish complete scenes. [Upload and cleanup](../gui/index.md#publish-and-clean-up). | +| {cpp:class}`TGUI::Scene `, {cpp:class}`RmlUi::Scene `
Append retained UI draw packets during frame preparation. [Retained publication](../development/ui-adapters.md#update-publication-and-shutdown); [GUI task](../getting-started/how-to/create-gui.md). | + +### Configuration and capabilities + +| Type or choice | Purpose and details | +| --- | --- | +| {cpp:struct}`TGUI::SessionOptions `, {cpp:struct}`RmlUi::SessionOptions ` | Set maximum texture size; TGUI also supplies explicit font scale. RmlUi uses native document styling. [Adapter ownership](../development/ui-adapters.md#module-and-application-ownership); [TGUI](../modules/tgui.md), [RmlUi](../modules/rmlui.md). | +| {cpp:struct}`TGUI::Capabilities `, {cpp:struct}`RmlUi::Capabilities ` | Report keyboard focus, committed text, clipboard, cursor and IME availability; RmlUi also reports clipping/effects support. [Unavailable services](../development/ui-adapters.md#routing-and-unavailable-services). | +| {cpp:enum}`CE::Input::Routing::KeyboardPolicy` and pointer selection | `Exclusive`/`PassThrough` controls keyboard gameplay. The application separately selects pointer recipients. [GUI routing](../gui/index.md#route-input-and-focus-explicitly). | +| {cpp:struct}`TGUI::Materials `, {cpp:struct}`RmlUi::Materials ` | Optionally override automatic defaults with compatible loaded material handles. [Custom UI materials](../rendering/pipelines-and-materials.md#custom-ui-materials). | +| {cpp:struct}`TGUI::WidgetLayout ` | Typed placement/resizing for native TGUI widgets; RmlUi uses native RML/RCSS layout. [Choose an interface](../gui/index.md#choose-an-interface); [TGUI layout declarations](../../../projects/modules/ui/tgui/include/cheryl/ui/tgui/layout.h). | +| {cpp:class}`CE::Text::FontService` and {cpp:class}`CE::Text::Font` | Context-aware sessions inherit the Engine default and catalogue; per-widget/document overrides accept the same retained handle as direct text. [GUI fonts](../gui/index.md#select-fonts); [font service](../text/index.md). | + +## Usage + +Choose a toolkit, construct its session on the UI owner and retain native +widgets/documents there. Record owned CPU data, upload a complete scene on +graphics, then append that scene during frame preparation. + +```{engine-diagram} gui-focus-tgui +``` + +The [RmlUi variant](diagram-explorer.md#gui-focus-rmlui) follows the same retained +publication boundary with its own native document API. The +[application map](diagram-explorer.md#gui-application) includes runtime, view and +focus connections. + +### Choose an authoring interface + +| Choice | Authoring and layout | Rendering/material rule | +| --- | --- | --- | +| [TGUI](../modules/tgui.md) | Native widgets through `Session::gui()`; optional typed `WidgetLayout` rules. | Colored triangles with straight alpha. The uploader supplies defaults unless explicit `Materials` overrides are passed. | +| [RmlUi](../modules/rmlui.md) | Native RML documents, RCSS styles and element/event APIs through `Session::context()`. | Colored triangles with premultiplied alpha; rectangular clipping is supported, effects are reported unavailable. | + +Both use retained Engine fonts and CPU image decoding. Their session, recording, +scene and material types remain distinct. Each integration owns process-global +toolkit state with its own coexistence restrictions; consult its module owner +before composing sessions. Toolkit capabilities also define which input and +platform services are actually available. + +### Uses + +| Consumer | Dependency | Integration boundary | +| --- | --- | --- | +| Toolkit `Session` | [Input](input.md) {cpp:class}`Input::System ` and copied {cpp:struct}`Input::Events::Record ` values | Borrow input for the session lifetime; feed the entire ordered batch once. Keyboard focus leases and pointer selection are separate application decisions. | +| Font/image bridges | [Text](text.md) retained fonts and `FontRasterizer`; [Assets](assets.md) CPU image decoding | Share CPU data/operations without sharing live toolkit objects or using the direct text renderer as the toolkit's layout engine. | +| `SceneUploader` | [Resources](resources.md) provider and [Dispatch](dispatch.md) `PlatformDispatcher::Submission` | Upload on the original provider/owner. Submitted work owns its recording/material handles and contains no live widgets/documents. | +| Uploaded `Scene` | [Rendering](rendering.md) `FrameWriter` and retained draw packets | `write(frame)` appends a pass; rendering later needs no toolkit access. | +| Optional view/time attachments | [Runtime](runtime.md) `SimulationViewSource` and `RuntimeServices` | Apply copied dimensions or selected simulation-step time on the UI owner, not by reading a live window from simulation. | + +### Used by + +| Consumer | Entry point | Responsibility | +| --- | --- | --- | +| Application UI controller | `UI::TGUI::Session` or `UI::RmlUi::Session` | Own native authoring, focus policy, pointer selection and callbacks into simulation-owned application state. [GUI construction](../gui/index.md). | +| Menus, HUD components and editors | Native widgets/documents in that session | Share the enclosing session's routing identity and lifetime; release native references before toolkit closure. [HUD tutorial](../getting-started/create-a/hud.md). | +| Application frame preparation | `UI::TGUI::Scene::write()` or `UI::RmlUi::Scene::write()` | Select a complete uploaded scene and its pass order. `adopt_scene()` polls without waiting and preserves the current scene on failure. | + +### Decide who supplies view and time + +The Context constructor supplies input and the font catalogue, but automatic +view/time participation still requires the corresponding options. Enable them +on the initialized simulation owner, normally during `init_simulation()`; +the automatic view source is not ready during `init_platform()`. + +| Mode | View and time work | Work still supplied by the application | +| --- | --- | --- | +| Automatic | Enable `SessionOptions::automatic_view` and/or `automatic_time` when constructing with Context, or explicitly call `follow_view()` / `follow_time()`. | Feed records, choose focus/pointer recipients, author UI, record/upload/adopt scenes and append them to frames. | +| Manual | Call `set_view()` with copied logical/framebuffer dimensions and `update_time()` with simulation elapsed seconds. | Initialize dimensions before input/recording and refresh changes; keep advancement on the UI owner. | + +A valid `set_view()` switches sizing to manual. Call `detach_time()` before +manual time advancement when automatic time is attached. Automatic time starts +with future selected simulation steps; it does not replay the current step. +See the [TGUI](../modules/tgui.md#automatic-view-and-time) and +[RmlUi](../modules/rmlui.md#automatic-view-and-time) contracts for required +attachment failure and teardown behavior. + +## Contracts + +Create the session before native toolkit objects and call them only on the UI +owner. Input must outlive its session; external widgets/fonts/textures/documents +must not outlive toolkit shutdown. Toolkit global state imposes session +coexistence limits described by each owner. + +Feed the complete ordered input batch once so modifier releases remain visible. +Keyboard focus and pointer selection are separate choices. Copied tick +dimensions determine layout; monitor scale or a live window query must not be +invented on simulation. Recording/upload must preserve earlier immutable +generations already retained by frames. + +## Lifecycle + +Prepare the scene uploader during platform-owned initialization. It creates +default materials lazily through the selected resource provider. Create +the toolkit session on its UI/simulation owner, then native objects. Update input, +time and layout there, record CPU state, dispatch upload and retain the latest +complete scene. During shutdown, stop UI calls and release application-held +toolkit references before `close_after_quiescence()` and session destruction. +Keep accepted upload dependencies alive until runtime settles them. + +## Execution + +In concurrent runtime mode, UI mutation/recording belongs on simulation and +resource upload belongs on graphics. Sessions permit final platform teardown +after simulation has joined. Toolkit callbacks must update UI/simulation-owned +state or submit owned work, not directly perform native graphics operations. + +## Integration + +With `session_` constructed on its owner as shown in +[GUI construction](../gui/index.md#construct-widgets-on-the-ui-owner), create a native +TGUI button. A session constructed with `Context` already inherits the Engine +default font and configured catalogue; no widget font setup is needed for this case: + +```cpp +auto button = tgui::Button::create("Apply"); +button->onPress([this] { apply_requested_ = true; }); +session_->gui().add(button); +session_->request_keyboard_focus(); +``` + +The application retains `session_` and the callback target. It must also select +pointer delivery, feed records and publish the recorded scene. The +[GUI construction guide](../gui/index.md) explains those surrounding steps; +[the HUD tutorial](../getting-started/create-a/hud.md) connects TGUI widgets to a +game, and [the focused GUI task](../getting-started/how-to/create-gui.md) shows +the RmlUi document approach. + +## Limits and further reading + +There is no universal Engine widget API or context-owned UI manager. Reported +capabilities define clipboard, cursor, text/focus and IME availability. Read the +[module owner guides](../modules/index.md), +[UI adapter contracts](../development/ui-adapters.md) and +[the maintained toolkit demo](../../../projects/apps/demo/src/tgui-demo.cpp). diff --git a/docs/site/engine-systems/index.md b/docs/site/engine-systems/index.md new file mode 100644 index 00000000..89e80d36 --- /dev/null +++ b/docs/site/engine-systems/index.md @@ -0,0 +1,140 @@ +# Engine Systems + +Cheryl provides the execution, input, resource and rendering boundaries around an +application-owned game. Start here after [First Game](../getting-started/first-game.md) +when you need to understand how those boundaries fit together. The detailed topic +guides remain the authority for individual operations and failure cases. + +```{toctree} +:hidden: +:maxdepth: 1 +:titlesonly: + +Diagram explorer +Runtime +Display +Input +Assets +Resources +Rendering +Text +GUI +Audio