Skip to content

agera-sdk/previous-version-2

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rialight

  • Note (1): Rialight is not yet done. Here are the current tasks.
  • Note (2): Rialight is not in development. You might want to look at the Bevy Engine instead.

Rialight aims to be a multi-purpose gaming and graphical application framework combining reactivity and nodes and shipping various fundamental APIs, requiring you to just know the Rust standard library and the Rialight API. Rialight is designed for the Rust language.

Rialight can be used for creating graphical applications, both two-dimensional (2D) and three-dimensional (3D), but cannot be used for creating websites. Rialight applications can be embedded in websites.

Rialight experiences can be run in mobile, desktop, gaming consoles and web browsers.

Draft Ideas

Project Template

When using the rialight command, you create new projects instantly:

  • rialight new my-app
    • Creates an empty graphical application.
  • rialight new --game my-game
    • Creates an empty game.
  • rialight new --cli my-command
    • Creates an empty command-line interface application.

The project templates share common functionality, including translation resources which use the Fluent syntax.

There is always a build script, build.rs, at the root of the project, which uses an empty rialight::build_main!({ /* your build script */ });. It is used internally by Rialight, but you don't need to touch it.

The Cargo.toml file contains a package.metadata.rialight section, which contains configuration for the Rialight application, contains features.default = ["rialight_default_export"] and it also passes two features to the rialight crate to indicate whether the build target is the browser or not. You don't need to touch this.

Debugging and Exporting

Exporting a project should bundle its assets files into the installer, which can be later retrieved through the File API using an app: URI.

Rialight uses the Rust's package manager that comes with its installation, Cargo. You can debug either with Cargo or the Rialight command interface, through rialight run or rialight debug.

To export your application, use a Rialight command such as:

rialight export --platform browser

Graphics

The rialight::graphics and rialight::ui APIs co-work together.

  • Nodes, the primary way to construct a visual application.
    • The Node object is the primary item of the graphics API, which has various variants, such as Rectangle, Canvas, Button, TabBar and Modal. All of them share full customisation and common properties like visibility, skin and transform (including 3D matrix) which are inherited by default from their parent.
      • Reference: A Node is a thread-safe reference type that uses reference counting internally. If you need a weak reference to a node, you can downgrade it to a WeakRefNode.
      • Children: The Node type supports common child operations. It also supports node paths described later in this list. node.children() returns an iterator. Methods like append_children are chainable and accept an iterable.
      • Meta data (optional mapping from String to MetaDataValue for attaching any data)
        • pub type MetaDataValue = Box<dyn Any + Send + Sync + Clone>;
    • Events: _They also emit events, accessed as node.on_some_event().listen(listen_fn), such as on_enter_frame and on_click events.
      • Somes nodes may not have a certain event, which is a rare case, in which case you may want to access that event from the node kind instead (as by node.to::<SpecificKind>().unwrap().on_some_event()). In that case, for an event that is not supported by all node kinds, the documentation can list the only supported node kinds.
      • Few events are not accessed as listeners, using a single callback instead:
        • node.on_enter_frame(enter_frame_fn) sets listener for the enter frame event
        • node.on_user_input(user_input_fn) sets listener for an user input event
      • The enter frame event receives the delta (the time elapsed since the last frame).
    • Identifier: A node has an identifier. node.id() and node.set_id(id) (optional, so id is Option<String>). If an identifier is none, node paths take it as if it were the zero-based index of the node in the children collection as a string.
    • Finding nodes: The most common method for finding nodes by identifier is by_path, which accepts a node path.
    • Node paths: Node paths are paths using the slash (/) separator and .. and . portions. A .. portion resolves to the parent and a . portion resolves to the current node. If a node path is absolute, that is, it starts with a path separator, it resolves a node relative to the topmost parent.
      • node.get_path() returns the absolute node path.
    • Node kinds: The node.is::<NodeKind> method can be used to test if a node is of a specific kind and node.to::<NodeKind> performs a conversion for accessing very specific properties. node.to() returns an Arc<SpecificNodeKind>. node.try_into::<K>() can be used to convert optionally. Every node kind implements NodeKind, and Rialight implements NodeKind as well for custom UI components (it includes a reference_cast method that results into an Option<Arc<K>>, which is used by the general Node type itself).
      • Children: Any node can contain other child nodes. For instance, a button can contain further content inside, whether label, SVG or anything.
      • Focus: Any node that supports focus can be focused by default. This can be disabled for a specific node through set_focusable().
      • Building nodes: K::new() constructs an empty node. Although property and children addition methods are chainable, you can use markup! to build nodes appropriately.
      • Custom UI components can contain a NodeOutlet, which is a node that is replaced by input child nodes.
      • Button: The Button node kind has variants, such as primary(), secondary() and warning().
        • Highly-customized buttons are often created as user UI components.
      • Bitmap: The Bitmap node kind identifies a pixel grid including transparency. It is optimized and uses different representations inside (like RGB, RGBA and maybe more targetting the GPU).
      • Svg: The Svg node kind represents scalable vector graphics, specifically the SVG file format. It can be configured to use RGBA bitmap caching (use_bitmap_cache) at any size.
        • set_src takes a file string supported by File from the file system API. It can also take a base-64 data: URL.
        • Bitmap caching is clever and will generate a limited amount of bitmap caches.
          • The limit of caches could be something like 7.
          • If the size isn't near the size of any of the existing bitmap caches and the limit of caches is reached, no new bitmap cache is created and the nearest-size cache is used, yielding a blinear resized bitmap.
          • If the size is near to any of the existing bitmap caches, that cache is used, yielding a blinear resized bitmap.
          • If the size is not near to any of the existing bitmap caches and the limit of caches has not been reached yet, create a new bitmap cache by rendering the SVG again.
      • NodeOutlet: The NodeOutlet node kind represents an empty node that meant to be replaced by other nodes. It is used, for instance, by the markup! macro for user UI components.
    • Cameras: think of a good design that allows for cameras that follow a certain node, similiar to Godot. Multiple cameras can be interesting (in which case the UI only works properly outside the camera's environment).
    • ZOrdered: a container that does not position children and re-orders their Z-index from top-down perspective.
    • Very specific properties: Very specific properties from node kinds that are hold as Node are manipulated after a .to::<SuchKind> conversion.
    • Node representation: Internally, a node kind holds internal data that is stored behind a Arc inside Node. The Node type contains a single internal Arc that refers to further data, including common properties and a Arc<dyn Any> that refers to the node kind's data (which is downcasted to another Arc via Arc::downcast).
    • Chaining: Most of the Node methods, such as set_visibility, are chainable, returning a clone of the node's reference. Node kinds also have chainable methods. These methods are defined similiarly to:
impl Node {
    pub fn set_something(&self) -> Self {
        // set something
        self.clone()
    }
}
  • Cloning: Node is cloned by reference by default, not by content. Use node.clone_by_content() to clone a node by content and not by reference.
  • UI: Node kinds that are user interface specific (such as Button) are exported at the rialight::graphics::ui submodule to avoid confusion. They are also exported by the user interface API.
    • Text selection: Optional text selection on non text inputs (text labels)
  • Inheritance: Properties such as visibility, opacity, rotation and scale are inherited by default, with an inherited variant. There may be more of such properties other than these that are inherited.
  • Responsivity: Node measures are device-oriented. They use the mathematical API.
  • Positioning: A node's position can be either derived, absolute or relative.
    • Derived means the node's position is determined by the parent.
    • Absolute means the node is positioned at a given global position.
    • Relative means the node is positioned relative to the parent's global position with given coordinates.
  • Common properties: Skin, scale, opacity, visibility, position, rotation, size and maybe some more.
  • Sizing: A node can have a size variant: none, full and specific (given measurement units). Nodes whose position is not derived often need to specify a size variant, otherwise they may not be displayed.
  • Not covered here yet: Alignment, minimum sizing and maybe more.
  • Skins
    • Nodes share skins. Skins are inherited by default if a node's skin is None. Every application has a default skin that applies to the root node. Skins describe styles and style transitions.
// get_skin() and set_skin() work across all nodes,
// even if a specific node kind doesn't need a skin,
// such as `Column` and `Row`. the specified skin
// is **inherited** by children.
node.set_skin(Some(custom_skin));
  • Skins are divided by node kind. That is, a specific style applies to a specific node kind. Only native node kinds have applicable skins.
  • Skins are described in Rust code.
  • RenderingTarget: The RenderingTarget can be constructed manually, however the application comes with its own.
    • The RenderingTarget can only render Rialight nodes, a RGB pixel rectangle and a RGBA (transparent) pixel rectangle. This includes 3D nodes. Separate methods are used: render_2d, render_3d, render_rgb_grid, render_rgba_grid.
    • Support rendering a 3D world from different viewpoints at the same rendering target at different rectangles inside a RenderingTarget, useful for multiple cameras.
      • I think this might involve generating triangles from the 3D nodes and subtracting parts of these triangles that overflow the viewpoint rectangle.
      • See if there is a way to implement an efficient method for that which doesn't need another RenderingTarget as a screenshot. (To start with, I don't even think another RenderingTarget is possible to construct as a native form of rendering is used.)
    • A RenderingTarget can be converted into pixels without the alpha channel, which can be useful for screenshots.
  • Canvas:
    • The Canvas node, although normally rendered automatically, can also be rendered separately to a RGBA (transparent) channel through a method. Useful for drawing tools.
  • WebView:
    • A simple WebView node should be supported. It's not meant as a building block for browsers.

Accessibility:

  • Focus
    • Focus neighbors
      • Automatic focus neighbors on containers
      • The focus neighbors are set as node paths
    • Focus configuration
      • You can optionally allow user inputs other than touch or pointer to switch control focus.
  • Touch

Common skin pratices:

  • Static skins are conventionally created as lazily-evaluated statics:
use rialight::{prelude::*, graphics::*};

lazy_static! {
    static ref SOME_SKIN: Skin = {
        // initialize skin here.
    };
}

// use `SOME_SKIN` where desired!

Graphics Markup and Custom Nodes

Define two procedural macros that facilitate defining nodes and custom UI components. For example, define_node! generates a separate internal K__Internal structure, which is contained by K (node kind) itself. K contains (base, data). K::new() constructs an empty K. K implements NodeKind, inheriting everything from base (such as set_skin and parent()). data is an Arc<K__Internal>.

Syntax:

  • define_node! is given field-like attributes somewhere, aggregating public fields to the K__Internal structure and automatically aggregating set_ prefixed methods to impl K. There must be support for specifying set_ methods explicitly too if special processing of the attribute value is desired (this is common, including for Svg's src, which is not a field in fact).
    • Expressions are internally parsed via syn (let expr: syn::Expr = syn::parse(token_tree_stream);) and expanded back to tokens later inside K::new().
    • The aggregated K__Internal fields are public because the native nodes hold data used by the render for instance. It doesn't matter whether they're public or internal though, since K__Internal is internal.

It makes sense for UI components to be nodes, therefore UiComponent implements NodeKind. They are defined with a similiar macro define_ui_component!.

Ideally there'll be three macros: markup!, define_node! and define_ui_component!.

Here's what define_node! looks like:

define_node! {
    pub type Example {
        foo: RwLock<i64> = RwLock::new(0),

        // `explicit_setter` tells `define_node!`
        // to not generate a `set_`.
        #[explicit_setter]
        bar: RwLock<i64> = RwLock::new(0),
    }
}

This will generate:

  • An Example structure.
    • A Example::new() method that returns Example and initialises the fields foo and bar from the internal Arc<ExampleKindData>.
    • A Example::set_foo(value) method. set_foo takes self (not &self), returning Self (not &Self).
  • An internal ExampleKindData structure with fields foo and bar.

Here's what markup! looks like:

markup!(
    <Button>
        <Svg src="app://res/img/foo.svg"/>
        { iterable }
        <Row></Row>
    </Button>
)

markup! constructs a single node with any child nodes.

How Nodes Are Implemented

All node methods are in NodeInherited.

  • Node contains an Arc<NonRefNode>
  • NonRefNode contains common fields and the stores the actual node kind data as Arc<dyn Any>.
  • K::new() takes no arguments and returns K
  • K contains (base, kind data).
  • kind data is of type Arc<K__Internal>
  • The macros define_node! and define_ui_component! generate a K__Internal structure
    • K__Internal must have a #[doc(hidden)] attribute
  • NodeKind has all common methods from Node by delegating to base, including append_children.
  • K implements NodeKind and therefore also NodeInherited (just giving the base)
  • K will have a #[derive(Copy)] attribute and a Clone implementation that clones the (base, data) by reference.
  • K will have PartialEq, which verifies base reference equality.
  • Chainable set_ methods all return K, not &K
  • K will implement From<K> to Node, evaluating to base (the kind as the Node type), allowing .into() calls.
  • NodeKind has a static function reference_cast that takes a node: Node and returns Option<K>. This is used by Node methods such as .to, which unfortunately have no access to the type from the node kind's data structure (K__Internal). This involves using Arc::downcast::<K__Internal>.
  • The markup! macro will build nodes using something like K::new(), chaining set_ methods after ::new(). Children tags are appended after an .into() call and interpolated children are taken as IntoIterator<Item = Node>.
  • Node implements NodeInherited (just giving the base) and NodeKind inherits NodeInherited.

Additionally:

  • K__Internal has a &'static str that contains the name of the kind (K itself in this case).
  • Node and K implement Debug and Display to display the name of the kind.
  • Consider retaining definition's #[attribute], however interpret #[explicit_setter] specifically.

3D Graphics

The 3D graphics API, rialight::graphics_3d.

  • The most important type is Node3d. It is not compatible with the two-dimensional Node type and cannot be mixed with it.

UI

The UI API, rialight::ui.

  • The UI API exports all the node kinds related to user interface (such as Button) from the submodule rialight::graphics::ui of the graphics API. Such node kinds are not exported directly by the graphics API to avoid confusion. Even define_ui_component! comes from there.
  • The UI API exports interfaces for reactive UI components which are defined by the developer.
    • An UI component may use graphics nodes from the graphics API, rialight::graphics. Inclusively, it is already a node too.
    • Reactive data can be shared across all UI components. There may be a proper API for that. In that case, when a state changes, it causes parts of a component that use that state to render again.

UI components are graphical nodes, since UiComponent implements NodeKind. They are usually defined with define_ui_component!, which is similiar to define_node!.

Reactivity

The define_ui_component! probably should define states in a way that they can be used "lexically" inside the render markup, allowing only very specific parts that rely on a set of states to render again and not the whole component. In this case, the https://crates.io/crates/syn crate should be considered for deep lookup of states in a markup, maybe ignoring any let bindings for instance (doesn't make sense anyway...).

File System

Ideas for the File System API, rialight::filesystem.

The File object can support the file:, app: and app-storage: URIs.

  • file: refers to files in the user's device file system.
  • app: refers to files in the application installation directory. They are assets originally included in the application source that are bundled within the application installer. These files are read-only and cannot be manipulated.
    • In the browser, these files are stored in the RAM.
  • app-storage: refers to files in the application data storage directory. They are data stored dynamically in the application with persistence.

If you need to use app-storage: in the browser, never use synchronous operations (these with the _sync suffix) as they will currently panic since the browser has no support for synchronous operations.

Web Compatibility in the File System

Synchronous operations do not work for the app-storage: URI when exporting the project to the browser currently. Any synchronous operation on File will panic. If you need to target the browser, always use asynchronous operations.

For the browser, Rialight uses its origin-private file system API for the app-storage: URI.

For the browser, Rialight uses the RAM for the app: URI; that is, the files all load together with the runtime; therefore, both synchronous and asynchronous operations work.

Gaming

Rialight supports a gaming API based on the Entity-Component-System pattern, which is essential for game developers, with support for physics. This API runs concurrent systems, however platforms without multi-threading support (browser) do not run systems concurrently. Since the nodes from the graphics API use atomic reference counting, they are used successfully the gaming systems.

The Gaming API is an optional feature that can be turned on or off.

Events

Ideas for the event API, rialight::event.

  • Event<T>
    • An event that can be listened to. event.listen(|e| {});
  • EventListener
    • Object returned by event.listen(listen_fn).
    • Can be cancelled: event_listener.stop();
  • Structures for native events, including touch and keyboard events.

Mathematics

Ideas for the mathematics API, rialight::math.

  • Geometry
    • Defines shapes and intersections.
    • Shapes have coordinates, including rectangles.
  • SI (Système International d'unités)
    • Measurement units and their conversions.

Utilities

Ideas for the utilities API, rialight::util. The utilities API is standalone and does not require the other Rialight APIs, so it can be used for unrelated Rust projects.

  • Temporal API
  • Lazy Statics
  • Collection Literals (map and set)
  • Big Integer
  • Futures
  • Flags
  • Bytes for working with binaries
  • Serialization
  • Regular Expression
  • Observable
  • Generic File Paths
  • String Incognito
  • URI and URI Component Encoding
  • Timing API, including handy animation frame functions

Network

The network API, rialight::net. The internationalization API uses the HTTP part of this API for loading developer translations occasionally if the developer desires.

  • HTTP client (not server)
    • For multi-thread platforms: use the crate hyper internally (not reqwest due to how it detects the browser via wasm32 arch.)
      • However, if seanmonstar/reqwest#1917 is solved, use:
        • reqwest::browser
        • reqwest::tokio
        • And perform two #[cfg(...)] according to export platform.
  • Sockets (TCP abstraction; in the browser it uses WebSockets)
  • UDP

Media

The media API, rialight::media.

  • Video
  • Camera

Sound

The sound API, rialight::sound.

  • No ideas yet.

Crypto

The crypto API, rialight::crypto.

  • No ideas yet.

Security

The security API, rialight::security.

  • No ideas yet.

Accessibility

The accessibility API, rialight::a11y.

  • No ideas yet.

Internationalization

The internationalization API, rialight::intl.

  • Locale object
    • Text direction
  • Display Names and More
  • Translations
    • This API can use the network API for downloading translations if the developer desires; however, most developers will simply use the app: file URI from the file system API.

Concurrency

The concurrency API, rialight::concurrent.

  • Workers
    • Workers behind an optional feature that is enabled by default.
    • A worker is created by evaluating a given Rune script (written in the Rune scripting language) as a string (usually given by an include_str! macro). Or it can be written as simply worker!("./worker_script.rune") which expands to Worker::new(include_str!("./worker_script.rune")).
      • Rialight will choose one of these languages:
    • Allows exchanging bytes and primitives such as strings between workers and sharing byte arrays.
    • It uses SharedArrayBuffer internally in the browser. The SharedArrayBuffer HTTP header should be set properly.
    • For the browser, here's research on how the JavaScript worker will load the worker scripting language and call developer functions:

Core

The core API, rialight::core, basically defines the application interfaces. It can cover:

  • Application Translations
  • Application Input Maps
    • They can be remapped in the runtime.
  • Application Shortcuts
    • They can be remapped in the runtime.
    • Used for instance by media editing softwares.
  • Command Line Interface
    • Allows a graphical application to also be used as a command in a terminal. An application can be configured to be launched graphically manually, allowing to only launch it according to the given command line arguments.
    • Help should be included by default, not launching the graphical application if --help or -h is specified.
  • Open link function (useful for authentication, so there might be variatns of that function that return a Future, allowing to receive data from the browser)
    • This should work at all platforms. In the browser, it simply uses the Web API for opening links.

The core internals, rialight::core_internals, should not be used anywhere. They are used by the APIs, including file system, for instance, to determine the application's installation directory.

Prelude

The rialight::prelude crate can be used to include commonly used things in scope. It includes:

  • Some of the Rust standard library, including:
    • Any
    • Future
    • Map and Set as aliases to HashMap an HashSet
    • Types for concurrency and reference counted boxes
  • Map and Set Collections that use a hash algorithm (same from the standard library, std::collections)
    • Some more
  • Collection Literals
  • Regular expressions
  • Bitwise Flags
  • Lazily Evaluated Statics
  • JSON Serialization
  • Observables
  • Temporal API (temporal as a global module)
  • Futures
    • exec_future
    • Other methods, like future_race
  • Big Integer

These other than the Rust standard library come from the utilities API.

JavaScript

When a developer wants to run a portion of Rust code for the browser only, it is recommended to detect the browser via #[cfg(feature = "rialight_browser_export")] and not #[cfg(target_arch = "wasm32")] as WebAssembly is used for unknown platforms.

Rialight provides an alias API for communicating with the browser and JavaScript, rialight::javascript, which is only available for the browser export.

This crate provides aliases for these crates with basic documentation, so no worries:

  • js_sys
  • web-sys
  • wasm-bindgen
  • wasm-bindgen-futures

Frame Control

For gaming, some might want control on how the game frames loop. In that case, either an animation interval or a default interval from the timing API is used inside the node renderer, according to developer configuration.

Visual Editor

Once Rialight develops, it can have a visual editor for the following use-cases:

  • Generic software
  • Gaming

This visual editor will require an external IDE for logical programming, such as Visual Studio Code.

Features:

  • Construct runtime styles and nodes from visually-edited styles and nodes. Visually-edited UI components? Might be possible.

Additional Platform Detection

Internally, Rialight uses Cargo features, including rialight_browser_export, to detect certain platforms that are not operating systems, including browsers, and futurely gaming consoles, since most of these use a WebAssembly target such as wasm32-unknown-unknown, where the OS does not exist.

Current features used for platform detection:

  • rialight_default_export
  • rialight_browser_export

You should not worry about specifying these Cargo features as you'll be using the Rialight commands to build or export your application to a specific platform, such as rialight export --platform browser.

Comparison to Other Technologies

  • The concept of nodes is similiar to the concept of DOM elements: you cannot subtype a specific DOM element kind and instead use the existing ones. Although the framework strives to have as many node kinds as possible, you may need to wrap it into an unrelated type or create an UI component from the UI API (rialight::ui).

Rust Setup

The framework currently requires the nightly Rust to be used. It is easy to switch to nightly:

rustup default nightly

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published