Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,6 @@ gradle-app.setting

# Local History for Visual Studio Code
.history/

# Generated architecture documentation (./gradlew doc)
docs/output/
91 changes: 27 additions & 64 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,64 +1,27 @@
# Working Guidelines

## Principles
- Start with a short plan before editing.
- Prefer correctness, safety, and minimal diffs.
- Ask before large refactors, new dependencies, public API changes, or cross-project behavior changes.
- Use `karpathy-guidelines` instructions: state assumptions, keep changes surgical, and define verification.
- Use `code-simplifier` instructions after edits to remove needless complexity without changing behavior.
- Talk like a caveman

## Repo Map
- `application`: desktop app, CLI, bundled files, configs, and packaging.
- `plugins/compiler`: assemblers and compilers.
- `plugins/cpu`: CPU implementations.
- `plugins/memory`: memory plugins.
- `plugins/device`: device plugins.

Related projects:
- `emuLib`: shared runtime and plugin API.
- `edigen`: instruction decoder/disassembler generator.
- `edigen-gradle-plugin`: Gradle integration for `edigen`.
- `cpu-testsuite`: CPU instruction test framework.
- `emustudio.github.io`: website and user/developer documentation.

## Before Editing
- Identify the affected Gradle module and entrypoint.
- Read nearby tests, configs, README/docs, and existing patterns.
- Check related projects when changing plugin APIs, generated CPU code, CPU tests, or public docs.
- Treat changes as large if they touch many modules, exceed about 200 lines, add dependencies, or change shared behavior.

## Editing Rules
- Keep behavior unchanged unless the task requires it.
- Follow `.editorconfig`; keep Java 11 compatibility.
- Preserve SPDX headers and GPL-3.0-or-later licensing. Warn and ask the user about further action if some code or files might violate this license.
- Avoid unrelated cleanup, broad formatting, generated-file churn, and speculative abstractions.
- Do not hardcode paths, versions, or plugin contracts when Gradle config or existing constants own them.

## Java and Gradle
- Match existing package, module, and plugin structure.
- Keep application code in `application`; keep plugin code under the owning `plugins/...` module.
- Keep emulation timing, device I/O, CPU semantics, and binary formats explicit and tested.
- Update docs, configs, or examples when user-facing behavior or bundled computers change - modify user documentation in `emustudio.github.io` project.

## Validation
- Run the narrowest practical check first:
- `./gradlew :<module>:test`
- `./gradlew test`
- `./gradlew build`
- For docs-only changes, run `git diff --check -- AGENTS.md`.
- If tests cannot run, say why and name the command that should be run.

## Final Response
- State what changed, files changed, tests run, docs/config impact, and related-project impact.

# Commits

- Commit messages must start with the Github issue
- based on changes, and known github open tickets, guess to what Github issue belongs the changes being commited. If not certain, guess it from the git branch name, which should be prefixed with `feature-XYZ`
- when asked to amend, check the commit message and format and change it accordingly.
- Use this format:
- first line: `[#XYZ] Add churn job`
- blank line
- bullet list of detailed changes, one short sentence each, max 80 chars
- Keep each commit scoped to one tightly related unit of change.
# emuStudio Repo Routing

## Current Repository
- `emuStudio` owns the desktop application, CLI launcher, bundled official plugins, bundled virtual computers, configs, and distribution packaging.
- Main locations in this repository: `application`, `plugins/compiler`, `plugins/cpu`, `plugins/memory`, `plugins/device`, and `application/src/main/files/config`.

## Sibling Repositories
- [emuLib](https://github.com/emustudio/emuLib): shared plugin API, runtime services, shared UI helpers, and reusable utilities.
- [edigen](https://github.com/emustudio/edigen): decoder/disassembler generator from `.eds` specifications.
- [emuStudio](https://github.com/emustudio/emuStudio): desktop application, bundled plugins, virtual computers, configs, and packaging.
- [emustudio.github.io](https://github.com/emustudio/emustudio.github.io): website, user documentation, developer documentation, and release-facing pages.
- [edigen-gradle-plugin](https://github.com/emustudio/edigen-gradle-plugin): Gradle task and DSL integration for Edigen source generation.
- [cpu-testsuite](https://github.com/emustudio/cpu-testsuite): shared CPU instruction test framework and reusable verification helpers.

When a task calls for checking or updating a sibling repository, first look for it as a local checkout (typically alongside this repository). If it is present locally, work with it there. If it is not present locally, do not guess its location or assume changes were made; report that the repository is not available locally and continue with what can be done in this repository.

## When To Update Which Repository
- Desktop app behavior, CLI behavior, plugin wiring, bundled virtual computers, bundled configs, or packaging: update `emuStudio`.
- Shared plugin API or runtime behavior used across plugins: update `emuLib`; then check `emuStudio`, `edigen`, and `cpu-testsuite`.
- Generated decoder or disassembler behavior for CPU plugins: update `edigen`; also check affected CPU plugins in `emuStudio` and Gradle integration in `edigen-gradle-plugin`.
- Shared CPU instruction testing support: update `cpu-testsuite`; check CPU plugin tests in `emuStudio`.
- User or developer documentation, website pages, or download/release pages: update `emustudio.github.io`.

## Tickets And Commits
- Every change must have an existing GitHub ticket.
- Every commit subject must start with the ticket prefix: `[#123] Short summary`.
- If one task touches multiple emuStudio repositories, use the same ticket prefix in each related commit.
43 changes: 43 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
SPDX-License-Identifier: GPL-3.0-or-later */
import java.text.SimpleDateFormat

plugins {
id 'org.asciidoctor.jvm.convert' version '4.0.4'
}

apply from: 'test_report.gradle'

ext.versions = [
Expand Down Expand Up @@ -96,3 +100,42 @@ subprojects {
mavenCentral()
}
}

// Architecture documentation (C4 model, AsciiDoc -> HTML)
repositories {
mavenCentral()
}

asciidoctorj {
modules {
diagram.use() // enables PlantUML/C4 diagrams via asciidoctor-diagram
}
}

tasks.register('doc', org.asciidoctor.gradle.jvm.AsciidoctorTask) {
group = 'documentation'
description = 'Generates HTML of the docs/ AsciiDoc documents into docs/output/'

baseDirFollowsSourceDir()
sourceDir file('docs')
sources {
include '*.adoc'
}
outputDir file('docs/output')

outputOptions {
backends 'html5'
separateOutputDirs = false
}

attributes(
'toc' : 'left',
'icons' : 'font',
'source-highlighter': 'rouge',
'sectanchors' : '',
'docdate' : new Date().format('yyyy-MM-dd'),
'imagesdir' : 'images',
'imagesoutdir' : file('docs/output/images')
)
}

167 changes: 167 additions & 0 deletions docs/code.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
= C4 Level 4 — Code
:toc: left
:icons: font
:sectanchors:

xref:index.adoc[← Back to overview]

The *Code* level illustrates how a couple of the key responsibilities are
realized in classes. C4 recommends keeping this level selective, so it focuses on
the two contracts that make the platform extensible: *plugin loading /
construction* and *plugin communication via contexts*.

== The plugin contract and type hierarchy

Every plugin implements the emuLib `Plugin` interface (or one of its four
specializations) and is annotated with `@PluginRoot`. The application discovers
that root class, then instantiates it through a fixed three-argument
constructor.

[plantuml,c4-code-plugin-model,svg]
----
@startuml
skinparam classAttributeIconSize 0
hide empty members

package "emuLib (SDK)" {
interface Plugin {
+initialize()
+reset()
+destroy()
+getTitle() : String
+getVersion() : String
+isAutomationSupported() : boolean
}
interface Compiler
interface CPU
interface Memory
interface Device

Plugin <|-- Compiler
Plugin <|-- CPU
Plugin <|-- Memory
Plugin <|-- Device

interface ApplicationApi {
+getContextPool() : ContextPool
+getDialogs() : Dialogs
+getGUI() : GUI
+getDebuggerTable() : DebuggerTable
}
interface ContextPool {
+register(pluginId, context, type)
+getContext(pluginId, type) : Context
}

annotation PluginRoot
}

package "emuStudio application" {
class VirtualComputer {
+create(config, api, settings) : VirtualComputer
-constructPlugins(...) : Map<Long, PluginMeta>
-createPluginInstance(id, class, api, settings) : Plugin
+initialize(contextPool)
+isConnected(a, b) : boolean
+getCompiler()/getCPU()/getMemory()/getDevices()
}
class PluginLoader {
+loadPlugins(files) : List<Class<Plugin>>
-trustedPlugin(class) : boolean
}
class ContextPoolImpl
class ApplicationApiImpl
class "VirtualComputer.PluginMeta" as PluginMeta {
+pluginInstance : Plugin
+pluginConfig : PluginConfig
+pluginSettings : PluginSettings
}
}

VirtualComputer --> PluginLoader : uses
VirtualComputer --> PluginMeta : holds *
PluginMeta --> Plugin : instance
VirtualComputer ..> ApplicationApi : passes to plugins
ApplicationApiImpl ..|> ApplicationApi
ContextPoolImpl ..|> ContextPool
PluginLoader ..> PluginRoot : requires on root class
Plugin ..> ApplicationApi : receives (constructor)
@enduml
----

The construction contract (as enforced in `VirtualComputer.createPluginInstance`)
is a single constructor with the signature:

[source,java]
----
@PluginRoot(type = PLUGIN_TYPE.CPU, title = "…")
public SamplePlugin(long pluginId, ApplicationApi emustudio, PluginSettings settings) { … }
----

`PluginLoader.trustedPlugin(...)` accepts a class only if it is not an interface,
is annotated with `@PluginRoot`, and implements `Plugin`. `VirtualComputer` then
additionally checks that the class implements the interface matching its declared
`PLUGIN_TYPE` before instantiating it.

== Loading and initializing a virtual computer

The following sequence traces the code path from the launcher to a running
machine.

[plantuml,c4-code-load-sequence,svg]
----
@startuml
autonumber
actor User
participant "Runner" as Runner
participant "VirtualComputer" as VC
participant "PluginLoader" as PL
participant "Plugin\n(root class)" as Plugin
participant "ContextPoolImpl" as CP

User -> Runner : select configuration
Runner -> VC : create(config, applicationApi, appSettings)
VC -> PL : loadPlugins(jarFiles)
PL -> PL : findMainClass() + trustedPlugin()
PL --> VC : List<Class<Plugin>>

loop for each plugin (compiler, CPU, memory, devices)
VC -> VC : check type implements PLUGIN_TYPE interface
VC -> Plugin : new (pluginId, applicationApi, pluginSettings)
Plugin --> VC : instance (wrapped in PluginMeta)
end

Runner -> VC : initialize(contextPool)
VC -> CP : setComputer(this)
loop order: compiler -> memory -> CPU -> devices
VC -> Plugin : initialize()
Plugin -> CP : register/getContext(...)
CP -> VC : isConnected(pluginA, pluginB)
VC --> CP : true / false
end
@enduml
----

=== Why the order and the connection check matter

* Plugins are initialized *compiler → memory → CPU → devices* so that, by the
time a plugin asks for another plugin's context, that context has already been
registered.
* Plugins must not fetch contexts in their constructor — only in
`initialize()` — precisely because registration order is fixed and earlier
plugins may not have published their contexts yet.
* Before the `ContextPool` hands one plugin a context owned by another, it calls
`VirtualComputer.isConnected(a, b)`, which consults the *connections* declared
in the TOML configuration. This is what makes wiring explicit and prevents
accidental coupling between plugins.

== Emulation control at the code level

For interactive runs, `EmulationController` funnels every CPU operation through a
single-threaded executor and synchronizes each state transition with a
`CountDownLatch`, updating its view of `CPU.RunState` from a `CPUListener`
callback. Automation instead attaches its own `CPUListener`, calls
`cpu.execute()` directly and waits for a terminal run state before reporting the
outcome.

xref:index.adoc[← Back to overview] · xref:components.adoc[← Components]
Loading
Loading