Plugin basics
Each plugin is a separate Java module (source code can be written in any JVM language), compiled into a single JAR file. The JAR file is then placed in the proper directory (e.g. compiler/
, cpu/
, memory/
, and device/
) in emuStudio installation.
In the source code, plugins are located in plugins/
subdirectory, then branched further by plugin type.
Naming conventions
Plugin names are derived from JAR file names. A naming convention defines how the name should be picked. Each plugin type has a different naming convention. General idea is that the JAR file name should say clearly what the plugin is about.
A plugin JAR file name should be in the form of:
[specific abbreviation]-[plugin type].jar
where [specific abbreviation]
means some custom abbreviation of the real world “device” the plugin emulates or is part of, optionally preceded with the manufacturer (e.g. intel-8080
, adm-3A
, etc.). Then [plugin type]
follows, but in a form, as it is shown in the following table:
Plugin type | Naming convention | Examples |
---|---|---|
Compiler | [language]-compiler (for compiler of higher language), or as-[cpu type] (for assembler) | as-8080 , as-z80 , brainc-compiler , ram-compiler |
CPU | [cpu model]-cpu , or [computer type]-cpu | 8080-cpu , z80-cpu , ram-cpu , brainduck-cpu |
Memory | [some feature]-mem , or [computer type]-mem | byte-mem , ram-mem , rasp-mem |
Device | [device model]-[device type] | 88-dcdd , 88-sio , adm3a-terminal , simh-pseudo , vt100-terminal |
Plugin names can contain digits, small and capital letters (regex: [a-zA-Z0-9]+
). Capital letters shall be used only just for word separation (e.g. zilogZ80
).
Plugin structure
A plugin must contain a public class which is considered as plugin root. The plugin root is automatically found, then instantiated by emuStudio, then assigned into a virtual computer and used.
A class which is to be plugin root, must:
- implement some plugin interface (i.e. CPU, Device, Memory or Compiler)
- annotate the class with PluginRoot annotation
- implement a public constructor with three arguments of types (
long
, ApplicationApi, PluginSettings)
A sample plugin root class might look like this:
@PluginRoot(type = PLUGIN_TYPE.CPU, title = "Sample CPU emulator")
public class SamplePlugin implements CPU {
public SamplePlugin(long pluginId, ApplicationApi emustudio, PluginSettings settings) {
...
}
...
}
If more classes implement some plugin interface, just one of them has to be annotated with PluginRoot
. If there are more classes like this, the plugin might not work correctly.
The constructor parameters have the following meaning:
pluginId
is a unique plugin identification, assigned by emuStudio. Some operations require it as an input argument.emustudio
is a runtime API implementation, provided by emuStudio application, to be used by plugins.settings
are plugin’s settings. A plugin can use it for reading/writing its custom or emuStudio settings. Updated settings are saved immediately in the configuration file, in the same thread.
Third-party dependencies
Each plugin can depend on third-party libraries (or other plugins). In this case, the dependencies should be either bundled with the plugin, or the location should be present in the Class-Path
attribute in the plugin’s Manifest
file.
Some libraries are preloaded by emuStudio and those shouldn’t be included in plugin JAR file:
- emuLib
- ANTLR4 runtime
- SLF4J logging
- Picoli for command-line parsing
Plugins that want to use the dependencies above should specify them as “provided” in the project.
Incorporating a plugin in emuStudio
A new plugin is another Gradle submodule, which should be “registered” in settings.gradle
file.
If a plugin is part of a new computer, the new configuration should be created (in TOML format) and put in application/src/main/files/config
directory.
Plugin can have static example files, or shell scripts. Plugin must copy them into build directory, e.g. plugins/compiler/as-8080/build/libs/examples
or plugins/compiler/as-8080/build/libs/scripts
. Then, in application/build.gradle
are sections marked with // Examples
or // Scripts
comments:
...
// Examples
["as-8080", "as-z80", "as-ssem", "brainc-brainduck", "ramc-ram", "raspc-rasp"].collect { compiler ->
from(examples(":plugins:compiler:$compiler")) {
into "examples/$compiler"
}
}
// Scripts
["as-8080", "as-z80", "as-ssem", "brainc-brainduck", "ramc-ram", "raspc-rasp"].collect { compiler ->
from(scripts(":plugins:compiler:$compiler")) {
into "bin"
}
}
["88-dcdd"].collect { device ->
from(scripts(":plugins:device:$device")) {
into "bin"
}
}
...
It is necessary to put your plugin name in the particular collection.