Add high-level code organization docs (#297)

pull/357/head
nyanpasu64 2019-08-22 19:19:27 -07:00 zatwierdzone przez GitHub
rodzic 2a6aafb02f
commit 6b953572ce
2 zmienionych plików z 116 dodań i 2 usunięć

Wyświetl plik

@ -1,3 +1,8 @@
"""
The most important class in this module is `DumpableAttrs`.
See its docstring for details.
"""
import pickle
import warnings
from enum import Enum
@ -113,8 +118,73 @@ def copy_config(obj: T) -> T:
class DumpableAttrs:
""" Marks class as attrs, and enables YAML dumping (excludes default fields).
- Subclass parameter `always_dump` contains
whitespace-separated list of fields to always dump.
It is subclassed for
(statically-typed key-value objects which will be dumped/loaded as YAML files).
## Subclassing `DumpableAttrs` and converting to YAML
- This class works like `dataclasses` or `attrs.dataclass` decorators,
and wraps the latter.
- Subclass `DumpableAttrs`, then add class-level type annotations.
These annotations are converted into `__init__(...)` constructor parameters.
```py
class Config(DumpableAttrs):
path: str
priority: int = 0
```
Unlike many other libraries and usual JSON,
the YAML string representation of a `DumpableAttrs` object
encodes what Python type the object is.
For example, Config("foo.wav", 1) is dumped as:
```yaml
!Config
path: foo.wav # Required
priority: 1 # If not present, defaults to 0
# See DumpableAttrs docstring for details.
```
## Polymorphism
The YAML file determines what type is loaded,
so the config type can be used to pick a object type at runtime.
- For example, putting `!CorrelationTriggerConfig` in YAML
loads a `CorrelationTriggerConfig` config object,
which tells corrscope to create a `CorrelationTrigger` algorithm object.
- Putting `!NullTriggerConfig` in YAML
instead loads a `NullTriggerConfig` config object,
which tells corrscope to create a `NullTrigger` algorithm object.
(This is only used for unit tests, and is incompatible with GUI.)
## `KeywordAttrs` are similar, with 2 differences:
- Subclasses can have non-default arguments after default arguments.
- Its constructor can only be called with keyword (a=1, b=2) arguments,
not positional (1, 2).
## Optional Parameters
class Config(DumpableAttrs, always_dump="", exclude=""): ...
- `always_dump` contains whitespace-separated list of fields to always dump
(if equal to default).
- If always_dump="*", `exclude` contains whitespace-separated list of fields
to not dump (if equal to default).
## Loading from YAML: Alias and Ignored
YAML loading uses __getstate__ and __setstate__.
If `old = Alias("new")`,
then loading a YAML file `old: value` initializes `new = value`.
If `old = Ignored`,
then loading a YAML file `old: value` silently discards `value`.
Ignored is unused in my code,
since __setstate__ automatically discards unrecognized fields (with a warning).
"""
if TYPE_CHECKING:

Wyświetl plik

@ -1,3 +1,47 @@
# Module/Class Structure and Organization
When corrscope is launched, it first executes `__main__.py` and `cli.py`.
## cli.py
If -w is present, it writes a .yaml file. If -p are present, it runs `corrscope.CorrScope` directly. If neither is present, it imports and runs the `gui` subdirectory.
## CorrScope, Config
`corrscope.py` defines classes `CorrScope`, `Config` (and `Arguments`).
- `CorrScope` is the main loop of the program, and only communicates with the GUI through `Arguments`. `CorrScope` requires a `Config` and `Arguments`.
- `Arguments` is constructed by the GUI and used to update rendering progress dialogs, etc.
- `Config` is a dataclass (see `config.py`) which can be edited through the GUI, or loaded/saved to a YAML file.
- When `cli.py` creates new configs, `default_config()` is used as a template to supply values. When loading existing YAML files, only dataclass default values are used.
-----
`Config` holds `channels: List[ChannelConfig]`, which store all per-channel settings.
`CorrScope` turns `channels` into a list of `Channel` objects. Each channel uses its own `ChannelConfig` parameters to create:
- self.trigger_wave: Wave
- self.render_wave: Wave
- self.trigger: MainTrigger
-----
Each frame:
`CorrScope` reads data from Wave objects, asks MainTrigger
Each channel has its own `trigger_wave`, `render_wave`, and `trigger` instance. For each channel, `trigger_wave` is used by `trigger` to find a "trigger time" near the frame's center time.
The `Renderer` and `Output` instances are shared among all channels.
- `Renderer` turns a list of N-channel "wave file portions" (from `render_wave`) into a RGB video frame.
- `Output` sends a RGB video frame to ffmpeg, ffplay, or (in the future) an interactive GUI player window.
## config.py
See docstring at top of file.
# Design Notes (cross-cutting concerns)
## Renderer to Output