This document describes the structure of the Gosub API.
First, we have a GosubEngine
. We instantiate this only once in our application/useragent and is capable of dealing
with multiple pages that can be rendered and used. We can send a configuration to the GosubEngine
to set up the
engine and to define certain behaviors.
Next, a GosubEngine must have at least one Zone
. A Zone
is a container for multiple Tab
s and all share certain
structures, like a cookie jar, session storage, and local storage. A Zone
can be thought of as a browser window where
you can have multiple tabs open. A HTTP session (for instance, through cookies) in one tab, is also visible in another
tab in that same zone.
The engine can contain multiple zones, and each zone is independent of each other. However, they can share underwater the same render processes and threads, but from the user's perspective, they are independent.
This means that you can have a single window, with multiple tabs, where some tabs are in zone 1, and others are in zone 2.
This basically mimics the behavior of container-like browsers.
Zones can also be created as persistent data structures, with titles, descriptions, colors and other metadata. They could also have its own set of history, bookmarks and such.
Inside a Zone
, we can have multiple Tab
s. A Tab
is a single page that can be rendered and interacted with.
However, it doesn't mean that the user agent can only show a single tab at a time. It's possible for a user agent to
have a page where two tabs are shown side by side, or even multiple tabs in a grid-like fashion. Such a tab-manager
is not part of the Gosub API, but can be implemented on top of it.
Each tab holds an EngineInstance
. This is the actual instance that is responsible for rendering the page, handling
events, and managing the state of the tab. The EngineInstance
is responsible for loading the page, rendering it. It is
basically a "mini" gosub engine, but can share the same resources as the main GosubEngine, like the GPU renderer,
parser etc.
There are two ways of communicating with a tab. First, we can send events to the tab, which will then be delegated
to the engine instance of that tab. This could be a "mouse move" event, a "key press" event, or any other event that
the user agent wants to send to the tab. The Tab
will then handle the event and pass it on to the EngineInstance
.
The second way of communicating with a tab is through the tick
method. This method will return a TickResult
, which
contains the current state of the tab. Depending on the result, the user agent can see if the tab is still loading
HTML content, if it's parsing, or if it is in the process of rendering the page onto a framebuffer.
If for instance the TickResult
contains the "needs_redraw" flag being set to true, the user agent can then can fetch
the surface / framebuffer of the tab, and rennder it to screen how it sees fit.
The tick system allows the engine to do "work" during a call from the user-agent. Most often, you want to call the tick
method of the engine once every 16 milliseconds, which is roughly 60 frames per second. This allows the engine to do be
fluent and responsive, while also allowing the user agent to render the page to the screen.
It is user-agent / ui toolkit dependend how this is done. For instance, with a 16ms timer interval, or through a vsync system.
Note that the tick()
method of the GosubEngine
will return a BTreeMap<TabId, TickResult>
, which contains the results
of ALL tabs in the engine. Even though this seems a lot of work, it doesn't mean that the engine has to do a lot of work.
Any tabs that are not active, will be run (or tick()
ed) with a different interval. Background tabs that have css
animations running for instance, can run at a 100ms or even 1 second interval, while active tabs (most often, only one)
can run at a 16ms (full speed).
Tabs that do not require any work will not be ticked at all. Only when it receives an event, it will wake up and tick. This also means that an inactive tab can still receive events like a scroll-event, or a mouse move event (even though it's unknown how that would be accomplished from a user-agent perspective), but it will handle it.
If something has changed in the tab (an event triggers a change in scroll, or we hover over an element), then we need to see where in the render pipeline we must trigger. We often do not need to trigger the whole render pipeline, but could only activate part of it (for instance, scrolling a page does not require a full re-render, but only a composition of the rendered tiles).
Based on the tab state, we either do not render anything, or when active, we could render it, which in turn will set the "needs_redraw" flag in the next tickresult to true.
There is no point in trigger a render when a tab is not active, however, there might be good reasons to do the rendering nevertheless. Since we already are in a slower tick() rate (100ms, or even 1s), we can render on each of these ticks. I don't know if this is pointless though, because as soon as the tab becomes active, we trigger a render anyway before we can show something. Maybe we should not render anything unless a tab is active?
classDiagram
class GosubEngine {
- config: GosubEngineConfig
- runtime: Arc<Runtime>
- zones: HashMap<ZoneId, Zone>
+ tick(): BTreeMap<TabId, TickResult>
+ create_zone(): ZoneId
+ open_tab(zone: ZoneId): TabId
}
class Zone {
- id: ZoneId
- cookie_jar: CookieJar
- session_storage: Storage
- local_storage: Storage
- tabs: HashMap<TabId, Tab>
}
class Tab {
- id: TabId
- instance: EngineInstance
- title: String
- favicon: Vec<u8>
- mode: TabMode
- last_tick: Instant
- state: TabState
+ handle_event(event: EngineEvent)
+ tick(): TickResult
}
class EngineInstance {
- dirty: DirtyFlags
- current_url: Option<String>
- raw_html: String
- loading_task: Option<JoinHandle<Result<Response,Error>>>
- surface: Option<ImageSurface>
- failed: bool
+ start_loading(url: String)
+ tick(): TickResult
+ render()
+ get_surface(): Option<&ImageSurface>
}
GosubEngine "1" o-- "many" Zone : contains
Zone "1" o-- "many" Tab : contains
Tab "1" *-- "1" EngineInstance : owns