diff --git a/content/ADR-255-texture-tweens.md b/content/ADR-255-texture-tweens.md new file mode 100644 index 00000000..530840a0 --- /dev/null +++ b/content/ADR-255-texture-tweens.md @@ -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, +}) +``` diff --git a/content/ADR-280-binaries-creator-hub.md b/content/ADR-280-binaries-creator-hub.md new file mode 100644 index 00000000..b8f8fd98 --- /dev/null +++ b/content/ADR-280-binaries-creator-hub.md @@ -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) diff --git a/content/ADR-281-items.md b/content/ADR-281-items.md new file mode 100644 index 00000000..e580332f --- /dev/null +++ b/content/ADR-281-items.md @@ -0,0 +1,169 @@ +--- +adr: 281 +date: 2025-01-16 +title: Items in Decentraland tooling +authors: + - cazala +status: Final +type: Standards Track +spdx-license: CC0-1.0 +--- + +# Abstract + +This document describes the Items abstraction in Decentraland tooling, which provides a way to add pre-configured entities with components into scenes through a drag-and-drop interface, without requiring coding knowledge. + +# Context + +Decentraland provides an Entity Component System (ECS) SDK for building interactive scenes. Items are an abstraction built on top of this ECS system, allowing creators to easily add pre-configured entities with components into their scenes through a drag-and-drop interface, without requiring coding knowledge. When an item is dropped into a scene, it creates one or more entities and applies the appropriate components to them. + +# Types of Items + +## Static Items + +Basic 3D models with no interactive behavior, including but not limited to: + +- Building elements: windows, doors, beams, decks, fences +- Furniture: chairs, tables, beds, benches, drawers +- Nature: trees, plants, mountains, terrain variations +- Decorative: vases, lamps, statues, cultural items +- Infrastructure: train tracks, mine carts, street elements +- Materials: wood, stone, metal, glass variations +- Props: books, tools, weapons, musical instruments + +### Smart Items + +Interactive items with programmable behaviors, primarily defined through actions and triggers. They can include various types: + +- Interactive objects (e.g., "Wooden Door" with open/close actions) +- Media items (e.g., Custom Text, Image, Video items) +- Game elements (e.g., Health Bar with state management) + +Smart items typically include: + +- Predefined actions (e.g., toggle, play animation) +- Trigger conditions +- State management +- User interaction handling + +### Custom Items + +User-created items that can be either static or interactive, allowing creators to: + +- Create reusable components from existing entities +- Share and reuse complex setups +- Maintain consistency across scenes + +## Item Sources + +### Asset Packs Repository + +- Central storage for default items and smart items ([asset-packs repository](https://github.com/decentraland/asset-packs)) +- Organized in themed collections (cyberpunk, fantasy, etc.) +- Distributed via npm package `@dcl/asset-packs` +- Accessible through CDN at builder-items.decentraland.org + +Custom items can be added to the default asset-packs registry by copying their folder and contents from the custom items folder into the appropriate asset-pack folder in the [asset-packs repository](https://github.com/decentraland/asset-packs). + +### Custom Items Storage + +Custom items are stored locally with the following structure: + +``` +custom/ + item-name/ + data.json # metadata + composite.json # entity data + thumbnail.png # preview image + resources/ # associated assets (models, textures, etc.) +``` + +## Technical Implementation + +### Composites + +Items are defined using composites - a versioned collection of components and their values: + +```typescript +interface Composite { + version: number; + components: Array<{ + name: string; + data: { + [entityId: string]: { + json: any; + }; + }; + }>; +} +``` + +### ID Mapping System + +When instantiating items, the system handles entity references through special notation: + +- `{self}` references the self entity once instantiated in runtime +- `{self:componentName}` (e.g., `{self:Actions}`) references a component in the self entity +- `{entityId:componentName}` (e.g., `{512:Actions}`) references a component on another entity by their composite ID (which differs from runtime ID) + +### Resource Path Management + +- Resources are stored with relative paths +- Uses `{assetPath}` notation for template paths +- Automatically maps paths during instantiation +- Components can have resources in arbitrary properties at any nesting level +- Requires manual tracking of which properties contain resources + +### Custom Item Creation + +The process involves: + +1. Selecting existing entities in the scene +2. Creating a composite from the selection +3. Processing and copying all required resources +4. Generating metadata and thumbnails +5. Storing the item in the custom items directory + +## Usage + +### Library Integration + +Items are available through a drag-and-drop interface where users can: + +- Browse available items by category +- Preview items before placement +- Drag items directly into the scene +- Configure smart item properties + +### Instantiation + +When an item is added to a scene: + +1. The composite is loaded +2. New entities are created +3. Components are instantiated with proper references +4. Resources are loaded with correct paths +5. For smart items, behaviors are initialized + +## Consequences + +### Positive + +- Reusable components improve scene creation efficiency +- Smart items enable non-technical users to add complex interactions +- Custom items allow sharing of complex setups +- Standardized storage format ensures compatibility + +### Negative + +- Resource management adds significant complexity: + - Properties containing resources can be nested at any level + - No standard way to identify resource properties + - New SDK components with resource fields require manual tracking + - Resource copying and path mapping needs careful handling +- Multiple item sources require consistent handling + +# References + +- [Decentraland Asset Packs Repository](https://github.com/decentraland/asset-packs) +- [Decentraland SDK Repository](https://github.com/decentraland/js-sdk-tooling)