mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
Comprehensively update user manual and contributor guide, add Adam to core team
This commit is contained in:
@@ -5,13 +5,219 @@ page_template = "book.html"
|
||||
|
||||
[extra]
|
||||
order = 2 # Chapter number
|
||||
js = ["video-embed.js"]
|
||||
js = ["youtube-embed.js"]
|
||||
+++
|
||||
|
||||
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="video-embed aspect-16x9">
|
||||
<img data-video-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>
|
||||
|
||||
The Graphite editor is built as a web app powered by Svelte and TypeScript in the frontend and Rust in the backend which is compiled to WebAssembly and run in the browser. The editor makes calls into Graphene, the node graph engine which manages and renders the documents.
|
||||
<!-- ## 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 -->
|
||||
|
||||
The Editor's frontend web code lives in `/frontend/src`. The backend Rust code is located in `/editor`. Graphene is found in `/node-graph`.
|
||||
## 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/wasm-communication/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.
|
||||
|
||||
<br />
|
||||
<details>
|
||||
<summary>To view the hierarchical subsystem file structure: click here</summary>
|
||||
<br />
|
||||
<!--
|
||||
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
|
||||
│ │ ├── imaginate_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
|
||||
```
|
||||
|
||||
<br />
|
||||
</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.
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
+++
|
||||
title = "Code structure"
|
||||
|
||||
[extra]
|
||||
order = 1 # Page number after chapter intro
|
||||
+++
|
||||
|
||||
## 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 -->
|
||||
|
||||
## Frontend/backend communication
|
||||
|
||||
The Graphite editor frontend is the web code which displays the user interface. It passes user interactions to the backend. The Graphite editor backend handles all the day-to-day logic and responsibilities of a user-facing interactive application. Some duties include: user input, GUI state management, viewport tool behavior, layer management and selection, and handling of multiple document tabs.
|
||||
|
||||
Frontend (TS) -> backend (Rust/wasm) communication is achieved through a thin Rust translation layer in `/frontend/wasm/src/editor_api.rs` which wraps the Editor backend's complex Rust data type API and provides the TS with a simpler API of callable functions. These wrapper functions are compiled by wasm-bindgen into autogenerated TS functions that serve as an entry point into the wasm.
|
||||
|
||||
Backend (Rust) -> frontend (TS) 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 (Rust) 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/wasm-communication/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 systems, 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, which enables us to nest message handler systems hierarchically. By convention, regular data must be written as struct-style named fields (shown above), while a sub-message must be written as an unnamed 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.
|
||||
@@ -1,47 +0,0 @@
|
||||
+++
|
||||
title = "Contributing guidelines"
|
||||
|
||||
[extra]
|
||||
order = 3 # Page number after chapter intro
|
||||
+++
|
||||
|
||||
## Code style
|
||||
|
||||
The Graphite project prizes code quality and accessibility to new contributors. Therefore, we ask you please make all efforts to contribute readable, well-documented code according to these best practices.
|
||||
|
||||
### Naming
|
||||
|
||||
Please use descriptive variable/function/symbol names and keep abbreviations to a minimum. Prefer to spell out full words most of the time, so `gen_doc_fmt` should be written out as `generate_document_format` instead.
|
||||
|
||||
This avoids the mental burden of expanding abbreviations into semantic meaning. Monitors are wide enough to display long variable/function names, so descriptive is better than cryptic. To streamline code review, it's recommended that you set up a spellcheck plugin in your editor. The project uses American English spelling conventions.
|
||||
|
||||
### Linting
|
||||
|
||||
Please ensure Clippy is enabled. This should be set up automatically in VS Code. Try to avoid committing code with lint warnings.
|
||||
|
||||
### Comments
|
||||
|
||||
For consistency, please try to write comments in *Sentence case* (starting with a capital letter). End with a period only if multiple sentences are used in the same comment. For doc comments (`///`), always write in full sentences (ending with a period).
|
||||
|
||||
Comments should be placed on a separate line, but exceptions are permitted where sensible. They should target the maximum line length of 200 characters (don't go over, and don't target a considerably lower number like 80 for line breaks).
|
||||
|
||||
### Imports
|
||||
|
||||
At the top of Rust files, please follow the convention of separating imports into three blocks, in this order:
|
||||
1. Local (`use super::` and `use crate::`)
|
||||
2. First-party crates (e.g. `use editor::`)
|
||||
3. Third-party libraries (e.g. `use std::` or `use glam::`)
|
||||
|
||||
Combine related imports with common paths at the same depth. For example, the lines `use crate::A::B::C;`, `use crate::A::B::C::Foo;`, and `use crate::A::B::C::Bar;` should be combined into `use crate::A::B::C::{self, Foo, Bar};`. But do not combine imports at mixed path depths. For example, `use crate::A::{B::C::Foo, X::Hello};` should be split into two separate import lines. In simpler terms, avoid putting a `::` inside `{}`.
|
||||
|
||||
## Tests
|
||||
|
||||
It's great if you can write tests for your code, especially if it's a tricky stand-alone function. However at the moment, we are prioritizing rapid iteration and will usually accept code without associated unit tests. That stance will change in the near future as we begin focusing more on stability than iteration speed.
|
||||
|
||||
## Draft pull requests
|
||||
|
||||
Once you begin writing code, please open a pull request immediately and mark it as a **Draft**. Please push to this on a frequent basis, even if things don't compile or work fully yet. It's very helpful to have your work-in-progress code up on GitHub so the status of your feature is less of a mystery.
|
||||
|
||||
Open a new PR as a draft / convert an existing PR to a draft:
|
||||
|
||||
<img src="https://static.graphite.rs/content/volunteer/guide/draft-pr.avif" onerror="this.onerror = null; this.src = this.src.replace('.avif', '.png')" alt="Screenhots showing GitHub's "Create pull request (arrow) > Create draft pull request" and "Still in progress? Convert to draft" buttons" />
|
||||
@@ -0,0 +1,36 @@
|
||||
+++
|
||||
title = "Debugging tips"
|
||||
|
||||
[extra]
|
||||
order = 4 # Page number after chapter intro
|
||||
+++
|
||||
|
||||
The Wasm-based editor has some unique limitations about how you are able to debug it. This page offers tips and best practices to get the most out of your problem-solving efforts.
|
||||
|
||||
## Comparing with deployed builds
|
||||
|
||||
When tracking down a bug, first check if the issue you are noticing also exists in `master` or just your branch. Open up [dev.graphite.rs](https://dev.graphite.rs) which always deploys the lastest commit, compared to [editor.graphite.rs](https://editor.graphite.rs) which is manually deployed from time to time for the sake of stability.
|
||||
|
||||
Use *Help* > *About Graphite* in the editor to view any build's Git commit hash.
|
||||
|
||||
Beware of one potential pitfall: all deploys and build links are built with release optimizations enabled. This means some bugs (like crashes from bounds checks or debug assertions) may exist in `master` and would appear if run locally, but not in the deployed version.
|
||||
|
||||
## Printing to the console
|
||||
|
||||
Use the browser console (<kbd>F12</kbd>) to check for warnings and errors. Use the Rust macro `debug!("The number is {}", some_number);` to print to the browser console. These statements should be for temporary debugging. Remove them before your code is reviewed. Print-based debugging is necessary because breakpoints are not supported in WebAssembly.
|
||||
|
||||
Additional print statements are available that *should* be committed.
|
||||
|
||||
- `error!()` is for descriptive user-facing error messages arising from a bug
|
||||
- `warn!()` is for non-critical problems that likely indicate a bug somewhere
|
||||
- `trace!()` is for verbose logs of ordinary internal activity, hidden by default
|
||||
|
||||
To show `trace!()` logs, activate *Help* > *Debug: Print Trace Logs*.
|
||||
|
||||
## Message system logs
|
||||
|
||||
To also view logs of the messages dispatched by the message system, activate *Help* > *Debug: Print Messages* > *Only Names*. Or use *Full Contents* for a more verbose view containing the actual data being passed. This is an invaluable window into the activity of the message flow and works well together with `debug!()` printouts for tracking down message-related defects.
|
||||
|
||||
## Node/layer and document IDs
|
||||
|
||||
In debug mode, hover over a layer's name in the Layers panel, or a layer/node in the node graph, to view a tooltip with its ID. Likewise, document IDs may be read from their tab tooltips.
|
||||
@@ -1,30 +0,0 @@
|
||||
+++
|
||||
title = "Debugging"
|
||||
|
||||
[extra]
|
||||
order = 2 # Page number after chapter intro
|
||||
+++
|
||||
|
||||
## Deployed builds
|
||||
|
||||
When tracking down a bug, first check if the issue you are noticing also exists in `master` or just your branch. Use [dev.graphite.rs](https://dev.graphite.rs) which should always deploy the lastest commit on `master`. By comparison, [editor.graphite.rs](https://editor.graphite.rs) is manually updated every few days or weeks to ensure stability. Use *Help* > *About Graphite* in the editor to view the build's [commit hash](https://github.com/GraphiteEditor/Graphite/commits/master).
|
||||
|
||||
## Printing to the console
|
||||
|
||||
Use the browser console (<kbd>F12</kbd>) to check for warnings and errors. Use the Rust macro `debug!("A debug message");` to print to the browser console. These statements should be for temporary debugging. Remove them before committing to `master`. Print-based debugging is necessary because breakpoints are not supported in WebAssembly.
|
||||
|
||||
Additional print statements are available that *should* be committed.
|
||||
|
||||
- `error!()` is for descriptive user-facing error messages arising from a bug
|
||||
- `warn!()` is for non-critical problems that likely indicate a bug somewhere
|
||||
- `trace!()` is for verbose logs of ordinary internal activity, hidden by default
|
||||
|
||||
To show `trace!()` logs, activate *Help* > *Debug: Print Trace Logs*.
|
||||
|
||||
## Message system logs
|
||||
|
||||
To also view logs of the messages dispatched by the message system, activate *Help* > *Debug: Print Messages* > *Only Names*. Or use *Full Contents* for more verbose insight with the actual data being passed. This is an invaluable window into the activity of the message flow and works well together with `debug!()` printouts for tracking down message-related issues.
|
||||
|
||||
## Node/layer and document IDs
|
||||
|
||||
In debug mode, hover over a layer's name in the Layers panel, or a layer/node in the node graph, to view a tooltip with its ID. Likewise, document IDs may be read by hovering over their tabs.
|
||||
Reference in New Issue
Block a user