Skip to content

Frequently Asked Questions (FAQ)

Sven Nilsen edited this page Aug 30, 2019 · 13 revisions

If your question is not here, please open up an issue.

Is Piston cross platform?

Yes. Piston uses backend-agnostic design and supports multiple cross platform APIs:

Is the goal of Piston to develop a game engine similar to Unity or UnrealEngine?

Unity and UnrealEngine are products sold against profit, which is different than what Piston does.

Piston libraries are free, but might be part of a commercial project through various companies. You are free to earn money by using Piston in your products, as long as you include the license. Most code is licensed under MIT, but we move what we can to dual licensing between MIT and Apache 2. This is a permissive license combination that is popular in the Rust community.

Piston is about minimizing developer costs. The major motivation to develop the ecosystem and reduce costs is to keep things modular, such that pieces of code can be reused across projects.

Why does the Piston project work on all these different ideas?

To evaluate design of libraries, we need to test them in various applications. This is the safest route to get valid feedback. Often we pick something that the author is interested in, because enjoying to work on something is important.

Can I use Piston libraries without using a Piston window?

Yes. All the libraries are designed to be used with a minimal abstraction.

You need to implement the trait that is required when calling a function. For input events in a game loop, this trait is GenericEvent. Look at the source code in the core and in the window back-ends for how to implement it yourself.

Rust allows you to:

  • Extend an existing struct or enum with new methods by adding a new trait
  • Implement an existing trait for a new enum or struct

Since you can not do both, there is no other way than to wrap existing types into new one and then implement the traits. Piston does this for you in the window and graphics back-ends, but you can write your own if you want to.

Why do big projects have so many dependencies?

Software is often more complex than it looks like on the surface. This is because the code must describe all the small details that the computer does.

When programmers work on a project, they need to find ways to organize the code. The sheer amount of it requires engineering discipline, and this requirement grows with size of a project.

Many projects prefer to share crates instead of rewriting similar functionality multiple times. When some code is reusable across projects, it gets extracted and put in its own crate. This crate is published on crates.io with a version number that tells Cargo how to use it.

Piston has many crates, and this makes it sometimes hard to find exactly what you need. However, there is no alternative to this type of organization, so we learn to live with it. There is no other way than to help each other out when we get stuck.

What are "current objects"?

The Piston-Current library is a tool to help productivity. It associates a value per type in a shadow stack that can be accessed in the same thread. It is used for prototyping and not recommended to use in general library design.

Dyon has built-in support for current objects, but using names instead of types. This makes them similar to globals, but more powerful.

How fast is Piston-Graphics?

The following steps can reduce CPU usage significantly:

  1. Change event loop settings (e.g. enable lazy mode for GUI)
  2. Set triangulation resolution of ellipses to a lower setting
  3. Use "invalidate" regions which are only rendered when e.g. a widget changes

Piston-Graphics is designed for making workarounds possible, e.g. write custom graphics backends that overrides default behavior.

For most simple games and interactive applications, Piston-Graphics should be fast enough.

Piston-Graphics uses an immediate design because this is the most flexible way to program 2D. By default, it converts any shape into triangles which are sent to the back-end in chunks.

The graphics back-end then might choose to buffer chunks to reduce draw calls. This speeds up rendering approximately 6x.

The problem is when switching back and forth between single colored graphics and textured graphics. Depending on the back-end, this might lead to reduction in performance. To optimize, you should look up which methods you need on the Graphics trait and reuse the inner closure call.

If the geometry does not change frequently and you need to process a lot of data, then you should create static buffers in the underlying graphics API, for example Gfx or Glium. This will keep the geometry in the GPU memory with shorter distance to where it is used.

Piston does not provide libraries for this because Gfx already is back-end agnostic and has these features.

My application crashes when running in VirtualBox on Linux, how do I fix it?

VirtualBox only supports older OpenGL drivers, which conflicts with the default version V3.2 that many window-backends uses in Piston. To fix this error, call .opengl(OpenGL::V3_1) or experiment with older versions when building the window from WindowSettings.

Is Piston used in production?

Yes, but still at very early stage.

The founder of the Piston project, Sven Nilsen, uses it in his own company Cutout Pro.

How do I know upgrades will not break my code?

The first non-zero number in the semver version that Cargo uses tells whether a breaking change happened.

The Piston project uses a tool, Eco, which fetches Cargo.toml files directly from the web and then runs an analysis. It outputs a file with recommended updates. When this is done, the whole ecosystem should be updated and consistent.

One problem some people have is that finding the right version that works with another can be a mentally hard problem. To solve this you can use Eco, and make a list of links to Cargo.toml files. You do not have to list all dependencies. Eco will then help you to stay updated.

What is the idiomatic way of organizing code?

The default pattern for Piston is the model/view/controller pattern:

  • A model is simply some data structure, database etc.
  • A view is how something is rendered
  • A controller stores the state, transforms input events into other events or actions, or deals with application logic in general.

Currently, there is no traits defined for this abstraction, it is simply a way of splitting up code into reusable parts.

Two simple examples of this pattern is timer_controller and button_controller. More libraries will come, but you can take a look here for an updated overview.

This code is reusable for any model or view, but it is also more work to set up. You might want to use a tailored API for your use cases, e.g. Conrod for UI. This is why Piston uses a modular design with a small core, so the code that is reused for particular projects, e.g. editors, can be worked on in parallel with other projects.

What we mean by "idiomatic" code is that this pattern does work and has a predictable maintenance cost for large projects. It is not meant to mean that you can't do any better or that everyone should program this way. The great thing about Rust is that it allows a wide style of designs, e.g. functional APIs, stream processing etc. You should not think that one particular flavor is superior to another, but learn different techniques and figure out the trade-offs. You know best what works for you! 😄

What is the idiomatic way of handling events?

Piston uses event traits with the input::GenericEvent trait. This is to reduce breaking APIs and let controllers work with custom defined event types.

Generic controller libraries depend on the core input library directly, to avoid breaking changes when the window and event_loop core libraries change.

If you are writing a generic controller library, do the following steps:

  1. Add pistoncore-input to Cargo.toml
  2. Add extern crate input; at the top of your library.
  3. Add use input::GenericEvent; to import the generic event trait.
  4. Use a signature fn event<E: GenericEvent>(&mut self, e: &E, ...)
  5. Import the event traits that are needed, e.g. use input::UpdateEvent;
  6. Use the style if let Some(args) = e.update_args() { ... } or e.update(|args| { ... });

Do not handle the enum variant directly, but use event traits. This allows your code to be upgraded more easily.

In generic application code, you do the same steps but with the piston crate. The input crate is reexported under piston::input. Write use piston::input::GenericEvent; instead of use input::GenericEvent;.

If you are only creating a demo or an example, you can use the piston_window crate. This library reexports all event traits directly under piston_window, so you write use piston_window::*;.

What is the idiomatic way of drawing 2D for generic backends?

use graphics::{Context, Graphics};
use graphics::character::CharacterCache;

impl MyView {
    pub fn draw<G: Graphics>(&self, c: &Context, g: &mut G) { ... }
}

You can also write this:

use graphics::{Context, Graphics};
use graphics::character::CharacterCache;

impl MyView {
    pub fn draw(&self, c: &Context, g: &mut impl Graphics) { ... }
}

When you use a character glyph cache:

use graphics::{Context, Graphics};
use graphics::character::CharacterCache;

impl MyView {
    pub fn draw<C, G>(&self, glyphs: &mut C, c: &Context, g: &mut G)
        where C: CharacterCache, G: Graphics<Texture = C::Texture>
    { ... }
}

This makes sure that the graphics backend accepts the texture type used by the character glyph cache.

Should I use the "piston_window" crate in libraries?

No. The "piston_window" crate is intended for application level code only.

For libraries, use the "piston" crate and "piston2d-graphics" to write generic code. This will make the code more reusable across projects.