What is happening 3

Another update on the Piston project!

Follow @PistonDevelopers at Twitter!

This year we have focused on upgrading, improving docs and stabilizing libraries. The overall architecture of the core is finished, and minor features are being added to flesh out the API. A lot of libraries that can be used independently also saw improvements and are getting closer to stable.

The most complex upgrade in the history of Piston is done! This was due to Gfx getting an overhaul to support next-gen APIs. It took up most of my time this year, and the rest I spent developing a new scripting language (more about this later!). This is why there have not been that many blog posts about the Piston project in general.

Image

A library for encoding and decoding images.

In-tree JPEG decoder was replaced with jpeg-decoder. This adds support for progressive JPEGs.

The image library has now Radiance HDR and ICO support.

Docs were improved, performance was improved, and lot of bugs were fixed!

List of contributors (78)

Conrod

A backend agnostic UI framework.

Widgets are now composed from a small set of primitive widgets. This makes it easier to add custom backends for Conrod.

A lot of new widgets were added, a lot of bugs were fixed, and performance was improved!

Conrod has now a guide to help you get started.

For more information, see the previous blog post.

List of contributors (51)

Imageproc

A library for image processing.

Features for contrast, canny edge and other algorithms were added.

List of contributors (8)

Piston-Graphics

Performance was improved significantly (6x) in the Gfx and Glium backends.

RustType is now replacing FreeType as the library for font rendering.

Colors are now standardized to use sRGB, to get same behavior across platforms.

You can now override triangulation in the graphic backend for faster or high quality rendering.

List of contributors (25)

Dyon

One unexpected surprise this year was the creation of a whole new dynamically typed language, which like Rust provides safety without a garbage collector!

It takes the object model from Javascript, some nice syntax from Rust and Go, replaces null with opt and res, and adds optional type system with support for ad-hoc types. A new concept for dynamical scope called “current objects” does the job of globals, but better. 4D vectors, vector algebra and swizzling, HTML hex colors are built-in features. For template programming it uses an optimized link structure (faster than arrays, uses less memory) which is useful when generating web pages or code. It has ? like Rust, with tracing error messages on unwrap. Syntax is standardized through Piston-Meta’s meta language.

It is a language that focuses on “scripting experience” and borrows ideas from mathematics. Indexed, packed loops, inference of range from body, and something called “secrets” helps solving problems with less typing. These features were tested for game prototyping, machine learning and linear algebra.

Current performance (interpreted) is in the same ballpark as Python, sometimes faster, sometimes slower. Loading a “hello world” program takes 0.3 milliseconds (type checking and AST construction happens in parallel), and dynamical modules makes interactive programming environments easy to set up.

The 0.8 release was epic, and the 0.9 release adds some important polish. Like a baby, it took 9 months to get out of the womb, and it is now usable! We will try to keep breaking changes minimal from now.

List of contributors (3)

Other projects

Some libraries that are not stable yet:

This Year In Conrod

Conrod has landed some long-desired features and overhauls over the past year. I’ve been excited about writing this post for the past few months now, though I have managed to continuously put it off until we just land that one extra special feature!

I realise that there’s probably always going to be one more exciting feature, so I think it is about time for what is turning into The Annual Conrod Update!

imgur

a WIP of a personal project that uses Conrod for a non-trivial GUI

Highlights

Here are some of the highlights before we dive into the details:

  • Dropped from 11 crate dependencies to 4.
  • Greatly improved the rendering API (now renders as a list of render::Primitives making it possible to batch drawing).
  • Introduced a new event system which interprets high-level UI events from raw window events.
  • Simplified the back-end agnostic story.
  • Changed from FreeType to RustType.
  • Added lots of new useful widgets (multi-line TextEdit, List, Scrollbar and more).
  • Simplified and improved many of the existing widgets.
  • Removed the .react closure convention in favour of having widgets return a Widget::Event associated type, greatly improving control-flow.
  • Simplified implementation of custom widgets.
  • Began work on The Guide.

I’ll address each of these in roughly the same order in more detail below.

Simplifying “Backend Agnostic”

The changes that I am most excited about are those that relate to simplification of conrod’s backend agnostic story.

To clarify, when I say “backend agnostic” I’m talking about enabling people to use Conrod with different window+graphics combinations - a commonly requested feature due to the variety of growing window and graphics libraries throughout the ecosystem. For example, some users wish to use sdl2 for the window context and gfx for the graphics, while others want to use glutin for the window context and glium for the graphics, etc.

The Old Problem

Implementing a back-end for conrod used to require numerous traits from various crates around the Piston ecosystem. This would frequently cause problems with version conflicts, strange error messages and highly complex generic types.

The window and event traits required wrapping the entire event loop in a new type, making it very intrusive to introduce into existing projects.

The graphics trait bound required that every primitive widget (lines, shapes, text, images) needed a unique draw call, which quickly became impractical for use with conrod’s modular, granular approach to widgets (the GUI above is composed of over 3000 unique widgets). Even after applying some culling optimisations (only drawing visible primitives), the number of opengl draw calls was clearly showing itself as a severe bottleneck when profiling.

The primary reason that conrod originally took this approach is that there were no other abstractions for drawing simple 2D graphics at the time. As a newbie to graphics programming, these piston traits at least made it possible for me to get started. However, in the following two years I have learned a lot and finally managed to find the time and solution to address these issues.

The New Solution

The key to simplifying all of this was realising that there are two primary ways in which the UI interacts with an application loop.

  • Receive window events as input.
  • Produce graphics as output.

I decided to abandon the wrapper-like, trait-heavy approach in favour of treating conrod as a pipeline that takes a new event::Raw type as input and renders to a list of depth-sorted render::Primitives as output.

event::Raw as input -> Ui -> render::Primitives as output

The event::Raw type describes a set of basic events (window resize, mouse press, key release, etc) that conrod can use to interpret higher level events (widget A was double clicked, widget B has captured the keyboard, widget C was dragged, etc) which may then be delivered to widgets. When a widget requests events, it is only delivered those that apply to it unless it specifically requests for global events.

A render::Primitive describes a set of 6 graphics primitives, from which all conrod GUIs can be drawn: Rectangle, Lines, Polygon, Text, Image and Other. The first 5 are self-explanatory. Other allows for custom user primitives which conrod does not yet support natively (i.e. video playback). Calling ui.draw() produces a depth-sorted list of render::Primitives, allowing the user to batch draw calls or render to some other format that I’m unaware of. Calling render::Primitives::owned creates an owned instance of the primitive list which can be sent across threads or stored for rendering at some other time.

This means that there are now only two steps a user must take to integrate conrod into their project:

  1. Convert their window events into conrod::event::Raws.
  2. Draw the conrod::render::Primitives using their graphics back-end of choice.

In order to reduce some of the boilerplate involved for users, I’ve added cargo features that provide implementations of these two steps for a handful of popular back-ends (piston, glutin, glium (WIP)) and hope to add more in the future (winit, glfw, sdl2, gfx).

From FreeType to RustType

For many folks, Conrod was a non-starter due to its dependency on FreeType - a font rendering library implemented in C. It particularly caused a lot of issues for Windows users. Cue regular thanks to windowsbunny for the help!

By switching to RustType, we were able to complete The Road to Pure Rust, add support for kerning and finally require nothing but cargo build to build conrod from scratch.

RustType has been a real pleasure to work with and IMO is a great example of a backend agnostic rendering API. It was the RustType rendering API that really inspired conrod’s pipeline-esque rendering solution described in the previous section. A massive thanks to @dylanede for both the lib and the inspiration! I believe dylanede also has a GUI lib in the works, so I recommend keeping an eye out! I sure will be.

More Widgets!

New widgets include:

  • primitive widgets: A variety of primitive graphical elements which can be used as building blocks for more complex widgets. These include:

    All provided conrod widgets are now built from these primitives.

    image.rs text.rs primitives.rs

  • TextEdit: A multi-line text editing widget that allows for:
    • Auto-wrapping via either character or whitespace.
    • Left, center and right justification.
    • Control over line-spacing.
    • Block selection.

    text_edit.rs

    The TextBox widget is now a thin wrapper around the TextEdit widget.

  • List: A widget that abstracts common logic between all list-like widgets including:
    • Generating a dynamic number of widget::Ids.
    • Automatic positioning and sizing of items.
    • Scrollability.
    • Optimised item instantiation i.e. only instantiating visible items. Very useful for massive lists (100+ items).
  • ListSelect: Extends the List widget providing an abstraction over item selection. Includes support for both single and multiple item selection. Used internally within the DropDownList widget and the new FileNavigator widget.

    list_select.rs

  • FileNavigator: An OS X Finder inspired file navigator with adjustable columns, auto-scrolling, multiple selection and a variety of useful events.

    file_navigator.rs

  • Scrollbar: Used for manually scrolling some scrollable widget along the x or y axes. Supports auto-hiding.
  • PlotPath: Allows generating a PointPath from some given function X -> Y, mapped to its dimensions (This is how the waveform is drawn in the top image).

    plot_path.rs

Removing .react closures - Adding Widget::Event

Previously, the conventional way for a Widget to support reacting to certain interactions was by implementing a .react closure. Something like this:

Button::new()
    .react(|event| match event {
        button::Event::Pressed(mouse_button) => /* react to press, may get called multiple times */,
        button::Event::Released(mouse_button) => /* react to release, may get called multiple times */,
        button::Event::Clicked(mouse_button) => /* react to click, may get called multiple times */,
    })
    .set(ID, &mut ui);

There are three primary issues that arose from this convention:

  • Difficulty with control flow:
    • No easy way to return data from the closure.
    • Awkward Result handling (can’t use try!).
  • Ownership issues: especially when using FnMut closures.
  • Awkward builder method: We can’t write custom Fn/FnMut/FnOnce types to use as default .react functions for widgets, meaning the user must call the builder method with an empty closure even if they don’t care about its interactions.

We were able to solve all of these issues by introducing a Widget::Event type, returned via the Widget::update method. The above example now looks more like this:

for event in Button::new().set(ID, &mut ui) {
    match event {
        button::Event::Pressed(mouse_button) => /* react to press */,
        button::Event::Released(mouse_button) => /* react to release */,
        button::Event::Clicked(mouse_button) => if mouse_button.is_left() {
            try!(load_file()); // Using `try!` like this would not have been possible using the `react` convention.
        },
    }
}

For more details and reasoning behind this change, see this issue.

Simplifying Custom Widget impls

Over the past year, the process of implementing custom Widgets has been simplified significantly:

  • You can now build new widgets by instantiating other widgets. All non-primitive widgets in conrod now do this, so you can see their implementations for examples of this.
  • The widget_ids! macro simplifies the generation of identifiers for child widgets.
  • The widget_style! macro allows for generating a collection of optional styling parameters for a widget, with automatic fallback to parameters specified within the Ui’s Theme or some other custom expression.
  • Several unnecessary Widget trait methods have been removed.
  • Rather than having to manually track raw input, widgets can now receive auto-generated high level widget events:

    for event in ui.widget_input(id).events() {
        match event {
            event::Widget::Click(click) => { ... },
            event::Widget::DoubleClick(click) => { ... },
            event::Widget::Drag(drag) => { ... },
            event::Widget::Scroll(scroll) => { ... },
            // etc
        }
    }
    

    See an enumeration of the available events here. This method of listening for events has been especially nice as it also simplifies the process of listening to relative (child, parent, grandchild, etc) widget events, i.e. ui.widget_input(child_id).clicks().left().

    Widgets can also listen for globally occurring events if necessary.

    for event in ui.global_input().events() {
        match event {
            event::Ui::Press(widget_id, press) => { ... },
            event::Ui::WidgetCapturesKeyboard(widget_id) => { ... },
            // etc
        }
    }
    

    See an enumeration of these global events here.

The Guide

The first couple of chapters have been written, though are already getting a little stale following all of the advancements mentioned above. The Guide is one of my top priorities for the near future now that conrod is less likely to go through any more major API overhauls.

If you are new to rust and are interested in contributing to conrod, this is probably the most valuable area in which one could do so! I’d be more than happy to mentor, so feel free to leave an issue or catch me on #rust if you are interested.

TODO

Here are some of the issues that are closest on my radar.

  • Complete the glutin_glium.rs example as an example of batched rendering. Once this is done, abstract the boilerplate into the glium feature and change the rest of the examples to use this instead of piston_window in order to demonstrate greater efficiency.
  • Add a gfx-rs example.
  • Consider the trade-offs involved in moving these backend cargo features into unique crates within the same repo. This may help to stabilise the core of conrod sooner as progress on the stability of the compatible back-ends cannot be guaranteed.
  • Remove the num crate dependency.
  • Finish The Guide.
  • Address all existing bugs.
  • Start thinking about how to extend conrod to support multiple native windows.
  • Investigate how much work would be involved in providing a set of “native look and feel” widgets.

Otherwise, you can get an idea of what remains by checking out the 1.0.0 milestone.

Contributors

A lot of the above would not have happened without help from the following:

  • @christolliday for his work on TextEdit, fixing loads of bugs, adding new-line insertion and a bunch of other useful key commands.
  • @Boscop for also adding some handy TextEdit key commands.
  • @tmerr for some TextEdit fixes and for inspiring simplification of the widget identifier system.
  • @Hyperchaotic for kicking off the ListSelect widget and helping to hide hidden files in the FileNavigator.
  • @pierrechevalier83 for adding support for Images on Buttons.
  • @|||Shaman||| for moving Ui construction over to the builder method convention.
  • @psFried for their 2kloc PR that kicked off the new event system.
  • Many other broken-link-fixers, spell-checkers and find-and-replacers :)

Written as of Conrod version 0.44.0

Dyon 0.8 is released!

Dyon is a scripting language that I am working on, and has now reached version 0.8.

Yeeehaaaaaa!

In the last blog post I wrote a summary of v0.7 and about the upcoming features in v0.8.

Also, I promised this release to be awesome.

But first, some important information for new people based on the feedback from previous post:

Why Dyon is NOT like other languages

Dyon does not have a garbage collector, and does not have ownership semantics.
It uses a lifetime checker, simpler, but less powerful than Rust.

foo(a: 'b, b) { // `a` outlives `b`
    ...
}

This means that Dyon has an extremely limited memory model compared to most languages.
The whole language semantics and syntax is designed to work around this limitation:

  • Atomic reference counting of arrays and objects
  • Copy-on-write for mutable aliased memory
  • Mathematical loops using index notation

The number one issue for some people responding to last blog post, was the lack of iterators.
Using iterators in Dyon is problematic because it leads to copy-on-write.

There is also another reason why iterators are not the focus in this language:

Mathematical loops works better with data-oriented patterns!

A consequence of the memory model is that object-oriented patterns does not work at all. For example, if you try to mutate an object by calling a method:

fn main() {
    list := [{name: "John"}]
    change_name(mut list[0], "Peter")
    println(list[0].name) // prints `John`.
}

fn change_name(mut person: {}, name: str) {
    person.name = clone(name)
}

This prints out “John” instead “Peter”. The reference counter of the item in the list increases, which leads to copy-on-write when changing it.

Therefore, you can not use object-oriented programming AT ALL in Dyon.
Instead, Dyon uses data-oriented patterns, which basically means:

Put stuff in flat arrays and iterate over them.

To make this convenient, Dyon uses current objects and mathematical loops:

fn main() {
    ~ list := [{name: "John"}] // Make `list` a current object.
    foo()
    println(list[0].name) // Prints `Peter`.
}

fn foo() {
    update_names()
}

fn update_names() ~ mut list: [] { // Bring back `list` into lexical scope
    for i { // Infer range from loop body.
        list[i].name = "Peter"
    }
}

When you are iterating over an array of arrays of arrays (3D), you can write:

for i, j, k {
    list[i][j][k] = ...
}

Instead of iterators, Dyon has many loops for common iterator functionality:

  • any, e.g. a := any i { list[i] > 2 }
  • all, e.g. a := all i { list[i] > 2 }
  • min, e.g. a := min i { list[i] }
  • max, e.g. a := max i { list[i] }
  • sift, creates a new list e.g. a := sift i { list[i] * 2 }
  • sum, e.g. a := sum i { list[i] }
  • prod, e.g. a := prod i { list[i] }

You can use continue and break with these loops to do filtering.

Once you have such loops, it makes sense to add secrets:

fn main() {
    list := [1, 2, 3]
    a := any i { list[i] == 2 }
    if a {
        println(why(a)) // prints `[1]`, because it is the indices of why `list[i] == 2`
    }
}

A secret is meta data for bool and f64 that returns the indices of the loops making up the answer.
It works with any combination of any, all, min and max loops.

Some people, who might be more familiar with mathematics, love this.

To the others who do not like it:

Using Dyon helped me understand mathematics better, so perhaps it could help you too?

Anyway, that is how the language works at the moment, so now you know there is for a good reason.

My experiences with Dyon so far

This was not supposed to be serious blog post, but for celebrating the release!
I wanted this to be a special moment, because I have worked so long and finally thinking “I have made it!”.
Therefore, I will share my thoughts with you about Dyon in a way that is more like a party speech:

When I released v0.7, I thought about Dyon as scripting language that was better in some ways,
than my experiences with other scripting languages.
Incomplete, full of bugs, but promising.

Now, I think it is simply the best scripting language I have ever used.

Do not misunderstand me, when I say “best scripting language” I mean just that.
I do not mean “best programming language”.
There are lots of stuff Dyon can not do, which Rust is a better language for.

However, when it comes to scripting, I mean, “scripting” as in scripting,
or how should I put it …

… if you imagine a landscape with elevation proportional to “scripting experience”, then I think Dyon is Mount Everest.

It is far from perfect, but Mount Everest is far from perfect either, it just …

… is a total mountain feeling to that scripting experience, if you understand what I mean?

Tomorrow some people will try it out and not like it,
because they have not grown into Dyon like I did,
but I do not think it matters.

I do not know when it will reach 1.0, but it feels like if there is a moment where Dyon went from being “almost something” to “something”, then I think it is this release.

Happy birthday Dyon! Let us celebrate!

One more thing …

Since the last blog post, I added some new features.

Dyon got closures:

fn main() {
    a := \(x) = x + 1
    println("0." + str(\a(7))) // prints `0.8`
}

You can use grab to capture a value from the closure environment:

fn main() {
    name := "Dyon"
    a := \(x) = (grab name) + " 0." + str(x)
    println(\a(8)) // prints `Dyon 0.8`
}

A grab expression is read-only, because it is a partial evaluation operator.
It computes a value, and injects it into the AST.
This means you can improve performance by doing expensive computations when creating the closure.

You can print out closures to see what they do:

fn main() {
    name := "Dyon"
    a := \(x) = (grab name) + " 0." + str(x)
    println(a) // prints `\(x: any) = "Dyon" + " 0." + str(x)`
}

Oh, we can improve the performance of this closure!

fn main() {
    name := "Dyon"
    a := \(x) = (grab name + " 0.") + str(x) // Put strings together when creating the closure.
    println(a) // prints `\(x: any) = "Dyon 0." + str(x)`
}

To grab values from higher closer environments, use grab 'n <expr>. For example, here is a function that takes two functors and joins them together to one functor:

fn fjoin(
    a: \(\(any) -> any, any) -> any,
    b: \(\(any) -> any, any) -> any
) -> \(\(any) -> any, any) -> any {
    return \(f, v) = {
        a := grab a
        \a(\(v) = {
            b := grab '2 b
            \b(grab f, v)
        }, v)
    }
}

Check out the functor example to see more.

Closures must use current objects to mutate the environment.

The benefit of this design:

  • closures do not require a lifetime
  • can be returned from functions
  • easy to refactor to a function or vice versa
Older Newer