Skip to content

Commit

Permalink
Merge branch 'main' into feat/adr-278
Browse files Browse the repository at this point in the history
  • Loading branch information
juanmahidalgo authored Jan 17, 2025
2 parents add1edf + ee8a49f commit 5533d46
Show file tree
Hide file tree
Showing 3 changed files with 527 additions and 0 deletions.
187 changes: 187 additions & 0 deletions content/ADR-255-texture-tweens.md
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,
})
```
171 changes: 171 additions & 0 deletions content/ADR-280-binaries-creator-hub.md
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)
Loading

0 comments on commit 5533d46

Please sign in to comment.