-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into feat/adr-278
- Loading branch information
Showing
3 changed files
with
527 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
--- | ||
layout: adr | ||
adr: 255 | ||
title: Texture tweens | ||
date: 2024-12-02 | ||
status: Draft | ||
type: RFC | ||
spdx-license: CC0-1.0 | ||
authors: | ||
- nearnshaw | ||
--- | ||
|
||
# Abstract | ||
|
||
This document describes an approach for making it possible for creators to animate textures. This can be used to simulate running liquids, as well as many other interesting texture effects. | ||
|
||
This new feature combines very well with ADR-254 (GLTF Nodes), as it enables to do the same effects on the textures of any .gltf model. | ||
|
||
## Offset and tiling | ||
|
||
Today the settings available on a texture are limited. We need to be able to also change the offset and the scale of a texture. As part of this ADR we're also adding two new fields to all textures: | ||
|
||
- `offset`: _Vector2_, shifts the image from starting on the 0,0 mark. Default value `0,0` | ||
- `tiling`: _Vector2_, determines how many times the texture fits on the surface. Default value `1,1`. The behavior of the remaining space on the texture will depend on the value of `wrapMode`. | ||
|
||
A creator could theoretically write a system that changes the `offset` value on every frame, but that would be very bad for performance. If something will continually change their offset, it's recommended to instead use a Tween for that. | ||
|
||
## Texture tween | ||
|
||
We'll define a new type of tween that affects the Material rather than the Transform. This is enabled by using the already existing Tween and TweenSequence components, with a new `TextureMove` option to be added to the existing `Move`, `Rotate`, and `Scale`. | ||
|
||
The new `TextureMove` option on the `Tween` component will have the following fields: | ||
|
||
- `TextureMovementType`: _(optional)_, defines if the movement will be on the `offset` or the `tiling` field (see section above) | ||
- `start`: _Vector2_ the initial value of the offset or tiling | ||
- `end`: _Vector2_ the final value of the offset or tiling | ||
- `duration`: _number_ how long the transition takes, in milliseconds | ||
- `easingFunction`: How the curve of movement over time changes, the default value is `EasingFunction.EF_LINEAR` | ||
|
||
The scene can also use a `TweenSequence` to make continuos movements possible, just like with other kinds of tweens. | ||
|
||
## Texture layers | ||
|
||
Materials have several textures besides the albedo_texture, including bump_texture, alpha_texture, emissive_texture. The `TextureMove` Tween affects the base texture, so all textures move together with it. | ||
|
||
This applies to changing the `offset` and `tiling` fields manually, as well as using a texture tween. | ||
|
||
## Serialization | ||
|
||
```yaml | ||
parameters: | ||
COMPONENT_ID: 1102 | ||
COMPONENT_NAME: core::Tween | ||
CRDT_TYPE: LastWriteWin-Element-Set | ||
``` | ||
```protobuf | ||
message Texture { | ||
string src = 1; | ||
optional TextureWrapMode wrap_mode = 2; // default = TextureWrapMode.Clamp | ||
optional TextureFilterMode filter_mode = 3; // default = FilterMode.Bilinear | ||
|
||
// Final uv = offset + (input_uv * tiling) | ||
optional Vector2 offset = 4; // default = Vector2.Zero; Offset for texture positioning, only works for the texture property in PbrMaterial or UnlitMaterial. | ||
optional Vector2 tiling = 5; // default = Vector2.One; Tiling multiplier for texture repetition, only works for the texture property in PbrMaterial or UnlitMaterial. | ||
} | ||
|
||
message PBTween { | ||
float duration = 1; // in milliseconds | ||
EasingFunction easing_function = 2; | ||
|
||
oneof mode { | ||
Move move = 3; | ||
Rotate rotate = 4; | ||
Scale scale = 5; | ||
TextureMove texture_move = 8; | ||
} | ||
|
||
optional bool playing = 6; // default true (pause or running) | ||
optional float current_time = 7; // between 0 and 1 | ||
} | ||
|
||
// This tween mode allows to move the texture of a PbrMaterial or UnlitMaterial. | ||
// You can also specify the movement type (offset or tiling) | ||
message TextureMove { | ||
decentraland.common.Vector2 start = 1; | ||
decentraland.common.Vector2 end = 2; | ||
optional TextureMovementType movement_type = 3; // default = TextureMovementType.TMT_OFFSET | ||
} | ||
|
||
enum TextureMovementType { | ||
TMT_OFFSET = 0; // default = TextureMovementType.TMT_OFFSET | ||
TMT_TILING = 1; | ||
} | ||
``` | ||
|
||
## Semantics | ||
|
||
### Example | ||
|
||
Offset texture: | ||
|
||
```ts | ||
Material.setPbrMaterial(myEntity, { | ||
texture: Material.Texture.Common({ | ||
src: 'assets/materials/wood.png', | ||
wrapMode: TextureWrapMode.TWM_REPEAT, | ||
offset: Vector2.create(0, 0.2), | ||
tiling: Vector2.create(1, 1), | ||
}), | ||
}) | ||
``` | ||
|
||
Simple continuos flow: | ||
|
||
```ts | ||
const myEntity = engine.addEntity() | ||
|
||
MeshRenderer.setPlane(myEntity) | ||
|
||
Transform.create(myEntity, { | ||
position: Vector3.create(4, 1, 4), | ||
}) | ||
|
||
Material.setPbrMaterial(myEntity, { | ||
texture: Material.Texture.Common({ | ||
src: 'materials/water.png', | ||
wrapMode: TextureWrapMode.TWM_REPEAT, | ||
}), | ||
}) | ||
|
||
Tween.create(myEntity, { | ||
mode: Tween.Mode.TextureMove({ | ||
start: Vector2.create(0, 0), | ||
end: Vector2.create(0, 1), | ||
}), | ||
duration: 1000, | ||
easingFunction: EasingFunction.EF_LINEAR, | ||
}) | ||
|
||
TweenSequence.create(myEntity, { sequence: [], loop: TweenLoop.TL_RESTART }) | ||
``` | ||
|
||
Square-moving tween sequence: | ||
|
||
```ts | ||
//(...) | ||
|
||
Tween.create(myEntity, { | ||
mode: Tween.Mode.TextureMove({ | ||
start: Vector2.create(0, 0), | ||
end: Vector2.create(0, 1), | ||
}), | ||
duration: 1000, | ||
easingFunction: EasingFunction.EF_LINEAR, | ||
}) | ||
|
||
TweenSequence.create(myEntity, { | ||
sequence: [ | ||
{ | ||
mode: Tween.Mode.TextureMove({ | ||
start: Vector2.create(0, 1), | ||
end: Vector2.create(1, 1), | ||
}), | ||
duration: 1000, | ||
easingFunction: EasingFunction.EF_LINEAR, | ||
}, | ||
{ | ||
mode: Tween.Mode.TextureMove({ | ||
start: Vector2.create(1, 1), | ||
end: Vector2.create(1, 0), | ||
}), | ||
duration: 1000, | ||
easingFunction: EasingFunction.EF_LINEAR, | ||
}, | ||
{ | ||
mode: Tween.Mode.TextureMove({ | ||
start: Vector2.create(1, 0), | ||
end: Vector2.create(0, 0), | ||
}), | ||
duration: 1000, | ||
easingFunction: EasingFunction.EF_LINEAR, | ||
}, | ||
], | ||
loop: TweenLoop.TL_RESTART, | ||
}) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
--- | ||
adr: 280 | ||
date: 2025-01-15 | ||
title: Binary Management in Creator Hub | ||
authors: | ||
- cazala | ||
status: Final | ||
type: Standards Track | ||
spdx-license: CC0-1.0 | ||
--- | ||
|
||
# Abstract | ||
|
||
This document describes the approach for managing Node.js binaries and their execution within the Creator Hub Electron application. It outlines the challenges of distributing Node.js-based tools within an ASAR archive and presents a solution for cross-platform binary execution. | ||
|
||
# Introduction | ||
|
||
The Creator Hub needs to run various Node.js-based tools and binaries (like `@dcl/sdk-commands`) to function properly. This presents several challenges in the context of an Electron application: | ||
|
||
1. The app needs Node.js to run these binaries | ||
2. NPM is required to manage and install dependencies | ||
3. The app is packaged in an ASAR archive for distribution | ||
4. Binary execution needs to work cross-platform (Windows and macOS) | ||
|
||
## ASAR Archive Requirements | ||
|
||
ASAR (Atom Shell Archive) is crucial for our distribution process, particularly for macOS: | ||
|
||
- Apple's requirements for app distribution mandate proper signing and notarization | ||
- ASAR provides a way to package the application into a single file that can be properly signed | ||
- It's similar to a ZIP file but designed specifically for Electron apps | ||
- Files within ASAR can be read using Node.js APIs as if they were in a normal directory | ||
- However, binaries within ASAR cannot be executed directly, which is why some files must remain outside | ||
|
||
## The PATH Environment Variable | ||
|
||
The `$PATH` environment variable is fundamental to how operating systems locate executable programs: | ||
|
||
- It's a list of directories separated by colons (Unix) or semicolons (Windows) | ||
- When a command is run, the system searches these directories in order to find the executable | ||
- In our case, we need to modify the `$PATH` to ensure our forked processes can find: | ||
1. Our Node.js binary (the Electron binary symlink/cmd) | ||
2. The NPM binaries | ||
3. Any other binaries installed by NPM | ||
4. The system's original executables | ||
|
||
# Decision | ||
|
||
We've implemented a multi-layered approach to handle binary execution: | ||
|
||
## Node.js Binary Management | ||
|
||
Instead of bundling a separate Node.js binary, we utilize Electron's built-in Node.js runtime. Electron is built on Node.js, making its binary capable of running Node.js code. To make this work: | ||
|
||
- On macOS: Create a symlink named `node` pointing to the Electron binary | ||
- On Windows: Create a `.cmd` file that redirects to the Electron binary | ||
|
||
## NPM and Package Management | ||
|
||
To handle NPM and package management: | ||
|
||
1. The `package.json` and NPM binaries are kept outside the ASAR archive | ||
2. NPM binaries are accessed using the Node.js symlink/cmd created above | ||
3. The `$PATH` environment variable is modified in forked processes to include: | ||
- The directory containing our Node.js symlink/cmd | ||
- The directory containing NPM binaries | ||
- The system's original `$PATH` | ||
|
||
## Installation Process | ||
|
||
The installation process follows these steps: | ||
|
||
1. Create Node.js symlink/cmd pointing to Electron binary | ||
2. Set up proper `$PATH` environment variable | ||
3. Use the Node.js symlink to run NPM | ||
4. Install dependencies from the unpackaged `package.json` | ||
5. Track installation versions to handle updates | ||
|
||
## Running Arbitrary Binaries | ||
|
||
To run binaries like `sdk-commands`: | ||
|
||
1. Fork a new process using Electron's `utilityProcess` | ||
2. Inject the modified `$PATH` containing Node.js and NPM locations | ||
3. Execute the binary using the forked process | ||
4. Provide utilities for output handling and process management | ||
|
||
## Process Monitoring | ||
|
||
The forked processes are augmented with monitoring capabilities through a wrapper that provides: | ||
|
||
1. Pattern-based Output Monitoring | ||
|
||
- `on(pattern, handler)`: Subscribe to output matching a RegExp pattern | ||
- `once(pattern, handler)`: Listen for a single pattern match | ||
- `off(index)`: Unsubscribe from a pattern | ||
- Supports filtering by stream type (stdout, stderr, or both) | ||
|
||
2. Process Control | ||
|
||
- `wait()`: Promise that resolves with complete output or rejects on error | ||
- `waitFor(resolvePattern, rejectPattern)`: Promise that resolves/rejects based on output patterns | ||
- `kill()`: Graceful process termination with force-kill fallback | ||
- `alive()`: Check if process is still running | ||
|
||
3. Logging and Debugging | ||
- Automatic logging of process execution details | ||
- Process lifecycle events (spawn, exit) | ||
- Output streaming to application logs | ||
- Error handling with detailed context | ||
|
||
Example monitoring usage: | ||
|
||
```typescript | ||
const process = run("@dcl/sdk-commands", "sdk-commands", { | ||
args: ["start"], | ||
}); | ||
|
||
// Wait for specific output | ||
await process.waitFor(/Server is listening/); | ||
|
||
// Monitor errors | ||
process.on(/Error:/, (error) => { | ||
console.error("SDK error:", error); | ||
}); | ||
|
||
// Graceful shutdown | ||
await process.kill(); | ||
``` | ||
|
||
# Consequences | ||
|
||
## Positive | ||
|
||
- No need to bundle separate Node.js runtime | ||
- Cross-platform compatibility | ||
- Clean separation between packaged and executable files | ||
- Reliable binary execution environment | ||
|
||
## Negative | ||
|
||
- Complex setup process | ||
- Dependency on Electron's Node.js version | ||
- Need to maintain files outside ASAR archive | ||
|
||
## Risks | ||
|
||
- Electron Node.js version updates might affect compatibility | ||
- Cross-platform differences in binary execution | ||
- Installation process interruption handling | ||
|
||
# Implementation Details | ||
|
||
```typescript | ||
// Example of PATH setup | ||
PATH = joinEnvPaths( | ||
path.dirname(nodeCmdPath), // Node.js location | ||
path.dirname(npmBinPath), // NPM location | ||
process.env.PATH // System PATH | ||
); | ||
|
||
// Example of binary execution run(package, bin, args) | ||
const child = run("@dcl/sdk-commands", "sdk-commands", { | ||
args: ["start"], | ||
}); | ||
``` | ||
|
||
# References | ||
|
||
- [Electron ASAR Documentation](https://www.electronjs.org/docs/latest/tutorial/asar-archives) | ||
- [Node.js Process Documentation](https://nodejs.org/api/process.html#process_process_env) |
Oops, something went wrong.