File size: 10,971 Bytes
2409829
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
+++
title = "Codebase overview"
template = "book.html"
page_template = "book.html"

[extra]
order = 2 # Chapter number
js = ["/js/youtube-embed.js"]
css = ["/component/youtube-embed.css"]
+++

The best introduction for getting up-to-speed with Graphite contribution comes from watching this webcast recording. Before asking questions in Discord, please watch the full video because it gives a comprehensive overview of most things you will need to know.

<div class="youtube-embed aspect-16x9">
	<img data-youtube-embed="vUzIeg8frh4" src="https://static.graphite.rs/content/volunteer/guide/workshop-intro-to-coding-for-graphite-youtube.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Workshop: Intro to Coding for Graphite" />
</div>

<!-- ## Tech stack -->
<!-- - rustc: Compiler for node graph generics and custom nodes -->
<!-- - rust-gpu: Compiler backend to generate compute shaders from Rust source code -->
<!-- - wgpu: Portable graphics API for running compute shaders on desktop and web -->
<!-- - Tauri: lightweight desktop web UI shell while the backend runs natively (experimental) -->
<!-- - Vello: GPU-accelerated vector graphics renderer -->
<!-- - COSMIC Text: Text shaping and typesetting -->
<!-- - Wasmer or Wasmtime: Portable, sandboxed runtime for custom nodes -->
<!-- - Tokio: parallelized job execution in the node graph pipeline -->
<!-- - Xilem: High-performance native UI framework, to replace Tauri when ready -->

## Codebase structure

Graphite is built from several main software components. New developers may choose to specialize in one or more area without having to attain a working knowledge of the full codebase.

### Frontend

*Location: `/frontend/src`*

The frontend is the interface for Graphite which users see and interact with. It is built using web technologies with TypeScript and Svelte (HTML and SCSS). The frontend's philosophy is to be as lightweight and minimal as possible. It acts as the entry point for user input and then quickly hands off its work to the WebAssembly editor backend via its Wasm wrapper API. That API is written in Rust but has TypeScript bindings generated by the [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) tooling that is part of the Vite-based build chain. The frontend is built of many components that recursively form the window, panels, and widgets that make up the user interface.

### Editor

*Location: `/editor`*

The editor is the core of the Graphite application, and it's where all the business logic occurs for the tooling and user interaction. It is written in Rust and compiled to WebAssembly. At its heart is the message system described below. It is responsible for communicating with Graphene as well as handling the actual logic, state, tooling, and responsibilities of the interactive application.

### Graphene

*Location: `/node-graph`*

[Graphene](../graphene/) is the node graph engine which manages and renders the documents. It is itself a programming language, where Graphene programs are compiled while being edited live by the user, and where executing the program renders the document.

## Frontend/backend communication

Frontend-to-backend communication is achieved through a thin Rust translation layer in `/frontend/wasm/src/editor_api.rs` which wraps the editor backend's Rust-based message system API and provides the TypeScript-compatible API of callable functions. These wrapper functions are compiled by [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) into autogenerated TS functions that serve as an entry point from TS into the Wasm binary.

Backend-to-frontend communication happens by sending a queue of messages to the frontend message dispatcher. After the TS has called any wrapper API function to get into backend code execution, the editor's business logic runs and queues up `FrontendMessage`s (defined in `/editor/src/messages/frontend/frontend_message.rs`) which get mapped from Rust to TS-friendly data types in `/frontend/src/messages.ts`. Various TS code subscribes to these messages by calling `subscribeJsMessage(MessageName, (messageData) => { /* callback code */ });`.

## The message system

The Graphite editor backend is organized into a hierarchy of subsystems, called *message handlers*, which talk to one another through message passing. Messages are pushed to the front or back of a queue and each one is processed sequentially by the backend's dispatcher.

The dispatcher lives at the root of the application hierarchy and it owns its message handlers. Thus, Rust's restrictions on mutable borrowing are satisfied because only the dispatcher mutably borrows its message handlers, one at a time, while each message is processed.

### Messages

Messages are enum variants that are dispatched to perform some intended activity within their respective message handlers. Here are two `DocumentMessage` definitions:
```rs
pub enum DocumentMessage {
	...
	// A message that carries one named data field
	DeleteLayer {
		id: NodeId,
	}
	// A message that carries no data
	DeleteSelectedLayers,
	...
}
```

As shown above, additional data fields can be included with each message. But as a special case denoted by the `#[child]` attribute, that data can also be a sub-message enum, which enables hierarchical nesting of message handler subsystems.

<details>
<summary>To view the hierarchical subsystem file structure: click here</summary>

<!--
Generated with:
cd editor/src/messages
tree -P '*_message.rs|*_message_handler.rs|*_tool.rs' --prune
Then the first line's "." was replaced with "messages"
-->

```
messages
β”œβ”€β”€ broadcast
β”‚Β Β  β”œβ”€β”€ broadcast_message.rs
β”‚Β Β  └── broadcast_message_handler.rs
β”œβ”€β”€ debug
β”‚Β Β  β”œβ”€β”€ debug_message.rs
β”‚Β Β  └── debug_message_handler.rs
β”œβ”€β”€ dialog
β”‚Β Β  β”œβ”€β”€ dialog_message.rs
β”‚Β Β  β”œβ”€β”€ dialog_message_handler.rs
β”‚Β Β  β”œβ”€β”€ export_dialog
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ export_dialog_message.rs
β”‚Β Β  β”‚Β Β  └── export_dialog_message_handler.rs
β”‚Β Β  β”œβ”€β”€ new_document_dialog
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ new_document_dialog_message.rs
β”‚Β Β  β”‚Β Β  └── new_document_dialog_message_handler.rs
β”‚Β Β  └── preferences_dialog
β”‚Β Β      β”œβ”€β”€ preferences_dialog_message.rs
β”‚Β Β      └── preferences_dialog_message_handler.rs
β”œβ”€β”€ frontend
β”‚Β Β  └── frontend_message.rs
β”œβ”€β”€ globals
β”‚Β Β  β”œβ”€β”€ globals_message.rs
β”‚Β Β  └── globals_message_handler.rs
β”œβ”€β”€ input_mapper
β”‚Β Β  β”œβ”€β”€ input_mapper_message.rs
β”‚Β Β  β”œβ”€β”€ input_mapper_message_handler.rs
β”‚Β Β  └── key_mapping
β”‚Β Β      β”œβ”€β”€ key_mapping_message.rs
β”‚Β Β      └── key_mapping_message_handler.rs
β”œβ”€β”€ input_preprocessor
β”‚Β Β  β”œβ”€β”€ input_preprocessor_message.rs
β”‚Β Β  └── input_preprocessor_message_handler.rs
β”œβ”€β”€ layout
β”‚Β Β  β”œβ”€β”€ layout_message.rs
β”‚Β Β  └── layout_message_handler.rs
β”œβ”€β”€ portfolio
β”‚Β Β  β”œβ”€β”€ document
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ document_message.rs
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ document_message_handler.rs
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ graph_operation
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ graph_operation_message.rs
β”‚Β Β  β”‚Β Β  β”‚Β Β  └── graph_operation_message_handler.rs
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ navigation
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ navigation_message.rs
β”‚Β Β  β”‚Β Β  β”‚Β Β  └── navigation_message_handler.rs
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ node_graph
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ node_graph_message.rs
β”‚Β Β  β”‚Β Β  β”‚Β Β  └── node_graph_message_handler.rs
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ overlays
β”‚Β Β  β”‚Β Β  β”‚Β Β  β”œβ”€β”€ overlays_message.rs
β”‚Β Β  β”‚Β Β  β”‚Β Β  └── overlays_message_handler.rs
β”‚Β Β  β”‚Β Β  └── properties_panel
β”‚Β Β  β”‚Β Β      β”œβ”€β”€ properties_panel_message.rs
β”‚Β Β  β”‚Β Β      └── properties_panel_message_handler.rs
β”‚Β Β  β”œβ”€β”€ menu_bar
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ menu_bar_message.rs
β”‚Β Β  β”‚Β Β  └── menu_bar_message_handler.rs
β”‚Β Β  β”œβ”€β”€ portfolio_message.rs
β”‚Β Β  └── portfolio_message_handler.rs
β”œβ”€β”€ preferences
β”‚Β Β  β”œβ”€β”€ preferences_message.rs
β”‚Β Β  └── preferences_message_handler.rs
β”œβ”€β”€ tool
β”‚Β Β  β”œβ”€β”€ tool_message.rs
β”‚Β Β  β”œβ”€β”€ tool_message_handler.rs
β”‚Β Β  β”œβ”€β”€ tool_messages
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ artboard_tool.rs
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ brush_tool.rs
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ ellipse_tool.rs
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ eyedropper_tool.rs
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ fill_tool.rs
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ freehand_tool.rs
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ gradient_tool.rs
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ line_tool.rs
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ navigate_tool.rs
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ path_tool.rs
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ pen_tool.rs
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ polygon_tool.rs
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ rectangle_tool.rs
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ select_tool.rs
β”‚Β Β  β”‚Β Β  β”œβ”€β”€ spline_tool.rs
β”‚Β Β  β”‚Β Β  └── text_tool.rs
β”‚Β Β  └── transform_layer
β”‚Β Β      β”œβ”€β”€ transform_layer_message.rs
β”‚Β Β      └── transform_layer_message_handler.rs
└── workspace
    β”œβ”€β”€ workspace_message.rs
    └── workspace_message_handler.rs
```

</details>

By convention, regular data must be written as struct-style named fields (shown above), while a sub-message enum must be written as a tuple/newtype-style field (shown below). The `DocumentMessage` enum of the previous example is defined as a child of `PortfolioMessage` which wraps it like this:

```rs
pub enum PortfolioMessage {
	...
	// A message that carries the `DocumentMessage` child enum as data
	#[child]
	Document(DocumentMessage),
	...
}
```

Likewise, the `PortfolioMessage` enum is wrapped by the top-level `Message` enum. The dispatcher operates on the queue of these base-level `Message` types.

So for example, the `DeleteSelectedLayers` message mentioned previously will look like this as a `Message` data type:

```rs
Message::Portfolio(
	PortfolioMessage::Document(
		DocumentMessage::DeleteSelectedLayers
	)
)
```

Writing out these nested message enum variants would be cumbersome, so that `#[child]` attribute shown earlier invokes a proc macro that automatically implements the `From` trait, letting you write this instead to get a `Message` data type:

```rs
DocumentMessage::DeleteSelectedLayers.into()
```

Most often, this is simplified even further because the `.into()` is called for you when pushing a message to the queue with `.add()` or `.add_front()`. So this becomes as simple as:

```rs
responses.add(DocumentMessage::DeleteSelectedLayers);
```

The `responses` message queue is composed of `Message` data types, and thanks to this system, child messages like `DocumentMessage::DeleteSelectedLayers` are automatically wrapped in their ancestor enum variants to become a `Message`, saving you from writing the verbose nested form.