initial commit
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
# Events, threading and lifetime
|
||||
|
||||
## The bus
|
||||
|
||||
`IPluginEvents` is a type-based publish/subscribe bus owned by LiveWall.
|
||||
|
||||
```csharp
|
||||
IDisposable Subscribe<TEvent>(Action<TEvent> handler) where TEvent : class;
|
||||
void Publish<TEvent>(TEvent payload) where TEvent : class;
|
||||
```
|
||||
|
||||
* `Subscribe` returns a token: **dispose it** in `Shutdown` (or you leak your plugin into the load
|
||||
context and it will never unload).
|
||||
* Handlers are matched on the exact runtime type (`Subscribe<WallpaperChangedEvent>` only receives
|
||||
`WallpaperChangedEvent`), not on interfaces or base classes.
|
||||
* Handlers run **on the WPF UI thread**, regardless of which thread published. Never block in one and
|
||||
never touch a plugin whose state you cannot see.
|
||||
* Host publications are delivered synchronously as part of the host's own operation; a slow handler
|
||||
therefore slows the wallpaper change. Do the work in `Task.Run` if it is not instant.
|
||||
* Exceptions thrown by a handler are logged and the handler is unsubscribed from that publication
|
||||
onward — a broken handler cannot repeatedly break the app, and cannot break the other handlers.
|
||||
|
||||
## Catalogue
|
||||
|
||||
| Event | When | Useful fields |
|
||||
|---|---|---|
|
||||
| `AppStartedEvent` | After settings load, wallpaper attach and plugin load | `HostVersion`, `PluginApiVersion` |
|
||||
| `AppStoppingEvent` | On exit, before plugins are unloaded | `Reason` |
|
||||
| `WallpaperChangedEvent` | A new source finished loading (or failed to) | `Kind`, `Source`, `RendererName`, `FromPlugin` |
|
||||
| `PlaybackChangedEvent` | Play/pause/suspend, fullscreen pause, battery pause | `IsPlaying`, `IsPaused`, `Reason` |
|
||||
| `InteractionChangedEvent` | Interactive mode toggled | `Interactive` |
|
||||
| `SettingsChangedEvent` | Any settings write; only for non-plugin-related keys | `Property`, `Snapshot` |
|
||||
| `DisplayChangedEvent` | Monitor topology change or WorkerW re-attach | `Devices`, `Reattached` |
|
||||
| `SessionChangedEvent` | Lock screen, battery, RDP | `Locked`, `OnBattery`, `RemoteSession` |
|
||||
| `HostErrorEvent` | Something in the host failed and the user was told | `Message`, `Source` |
|
||||
| `PluginMessageEvent` | Plugin-to-plugin convention | `FromPluginId`, `Topic`, `Payload` |
|
||||
|
||||
Events are **not** queued: you miss whatever happened before you subscribed. Pull the current state
|
||||
from `context.Host.Current` / `context.Settings` during `Initialize` if you need a starting point.
|
||||
|
||||
## Threading cheat sheet
|
||||
|
||||
| Place | Thread |
|
||||
|---|---|
|
||||
| `Initialize`, `Shutdown` | UI thread (Shutdown may be called while the app tears down) |
|
||||
| Overlay `CreateView` / `Update` / `OnVisibilityChanged` | UI thread |
|
||||
| Settings page `CreateView` | UI thread |
|
||||
| Command `Run`, hotkey action | UI thread |
|
||||
| Filter `Apply` | UI thread, inside the wallpaper change |
|
||||
| Renderer `Create` / `Load` / `Play` / `Pause` / `Unload` / `ApplyPlayback` | UI thread |
|
||||
| Event handlers | UI thread |
|
||||
|
||||
Anything you start yourself is your business: use `Task.Run`, dispatch UI updates with
|
||||
`Application.Current.Dispatcher`, and pass `context.ShutdownToken` to cancellable work.
|
||||
|
||||
## Lifetime in order
|
||||
|
||||
```
|
||||
Rescan / enable
|
||||
├─ manifest parsed, assembly hashed, permissions checked
|
||||
├─ assembly loaded into a collectible AssemblyLoadContext
|
||||
├─ PluginInfo filled in, IPluginContext created (storage opened, sub-bus created)
|
||||
└─ Initialize(context) ← you register everything here
|
||||
Disable / rescan / exit
|
||||
├─ ShutdownToken cancelled
|
||||
├─ Shutdown()
|
||||
├─ your overlays, pages, commands, filters, renderers and hotkeys are dropped
|
||||
├─ renderers disposed, overlays unhooked
|
||||
└─ AssemblyLoadContext.Unload()
|
||||
```
|
||||
|
||||
There is no "re-initialise": a plugin that needs to restart shows as **Disabled** then **Active** after
|
||||
a Rescan, with a brand-new context object. Any state you want to survive goes in `IPluginStorage`.
|
||||
Reference in New Issue
Block a user