Shards: Coding Simplicity and Power

How Shards Transforms Game Development by Streamlining Execution, Enhancing Concurrency, and Turbocharging Performance

Photo of a wide digital realm with vibrant landscapes, characters, and structures stretching across the horizon, symbolizing the vast potential of game development.

In the world of game development, complexity is often a significant challenge. Developers face intricate execution models, complex concurrency approaches, and limited tools for important tasks such as profiling and optimization. The stakes are high: a steep learning curve, slow development pace, and a constant struggle to fine-tune the end product.

Introducing Shards: not just a programming language, but a game-changer. Shards makes things simpler, providing a smooth and easy experience without compromising on power or capability. It's an all-in-one solution, designed to speed up your game development process while getting rid of unnecessary complexities.

Shards is more than just a new syntax or set of commands - it's a cohesive ecosystem. With a custom-engineered virtual machine at its core, Shards is optimized for efficient memory management and fast execution. But that's not all. Shards also introduces an innovative execution model that simplifies concurrency, enabling you to focus on what really matters: creating amazing games.

The Architecture and Innovation Behind Shards

Vector of an expansive, ultramodern city skyline at dusk, with neon-lit buildings and hovering digital interfaces that represent breakthrough programming concepts. Groups of diverse game developers, both men and women, collaborate in various spaces, fueled by the promise of this technological utopia.

Introduction to the Core Trifecta: Shards, Wires, and Meshes

In the complex world of game development, efficiency and simplicity are often at odds with each other. However, Shards overcomes this challenge with its unique architecture, which includes Shards, Wires, and Meshes.

The Importance of Modular Components

Traditional game development environments often impose a steep learning curve and a complex execution model. In contrast, Shards employs modular components to allow for a more granular approach to problem-solving. Developers can single out specific logical units, understand them in isolation, and then see how they interact, thus reducing both the mental overhead and debugging time.

["Hello " @name] | String.Join | Log >= message
; or without syntax sugar:
Const(["Hello " @name]) | String.Join | Log | Set(message)

The Shard: Dissecting the Basic Unit

A Shard is what one might refer to as the "atomic" unit in this architecture. It’s akin to a piece of Lego that can be easily manipulated, combined, and recombined.

Flexibility in Execution

Each Shard has one input and one output, providing a linear flow, thereby making it easier to predict program behavior. Shards also support parameters, adding a layer of customization and dynamic behavior that can adapt to the varying requirements of game development.

Const(["Hello " @name]) | String.Join | Log | Set(message)
Const("!") | AppendTo(message)
Get(message) | Log("new message")
; or with syntax sugar:
; ["Hello " @name] | String.Join | Log >= message
; "!" | AppendTo(message)
; message | Log("new message")

Output:
[info] [root] Hello Astarion
[info] [root] new message: Hello Astarion!

Type-Safe Composition

When Shards are composed into Wires, the system validates types and connection points to ensure that the resulting Wire is coherent and valid. This automatic validation can prevent numerous runtime issues, significantly reducing debugging time. As a bonus, this composition step also performs extensive optimization on the flow, improving performance just in time before a wire is run.

{ ; notice how a shards table can map to JSON 1:1
  "products": [
    {
      "product_id": "1",
      "name": "Wireless Mouse",
      "price": 25.99,
      "category": "Electronics"
    },
    {
      "product_id": "2",
      "name": "Mechanical Keyboard",
      "price": 89.99,
      "category": "Electronics"
    },
    {
      "product_id": "3",
      "name": "Gaming Chair",
      "price": 199.99,
      "category": "Furniture"
    },
    {
      "product_id": "4",
      "name": "Coffee Mug",
      "price": 9.99,
      "category": "Home & Kitchen"
    },
  ]
} | ToJson ; will output a JSON string
Http.Post("http://127.0.0.1/echo") | FromJson ; type is Any now as we cannot know
ExpectTable | Take("products") | ExpectSeq 
Map({ExpectTable | Take("price") | ExpectFloat})
Lowest ; at this point we know for sure it's a float!
Assert.Is(9.99)

`{ ... }`: This is a Shards table, compatible to a JSON object. It holds a list of products with various attributes like `product_id`, `name`, `price`, and `category`.

`| ToJson`: This operation converts the Shards table to a JSON string.

`Http.Post("http://127.0.0.1/echo")`: Sends the JSON data to a local server, echoing it back.

`| FromJson`: Converts the echoed JSON string back to a Shards data structure. The type is set to `Any` because the operation can't know the schema in advance.

`ExpectTable | Take("products")`: Asserts that the data should be a table and then takes the "products" field.

`| ExpectSeq`: Ensures the "products" field is a sequence (array).

`| Map({ExpectTable | Take("price") | ExpectFloat})`: Maps over the sequence, extracting and asserting the "price" field to be a float for each product.

`Lowest`: The unary function that finds the lowest price among the products.

`| Assert.Is(9.99)`: Asserts that the lowest price is 9.99, which should match the price of the "Coffee Mug".

Wires: Where Shards Come to Life

Wires are essentially directed graphs of Shards, enabling more complex behaviors by orchestrating simpler elements.

Persistent State Management

What sets a Wire apart is its persistent state. In typical game development, managing state can become a quagmire, leading to bugs that are hard to trace. Shards avoids this by providing stateful Wires, which manage their own internal state efficiently.

; A non-looped wire
@wire(accumulate {
  = amount
  Once({
    0 >= value ; >= is Set, mutable set
  })
  value | Math.Add(amount) > value ; > is Update, which mutates a variable
  value ; output the updated value
})

2 | Do(accumulate) ; 2
1 | Do(accumulate) ; 3
3 | Do(accumulate) ; 6

The Looping Mechanism

Wires can be either looped or non-looped. A non-looped wire acts as a smart function, maintaining its state. Looping wires function as tasks or agents that maintain their state across cycles, making them suitable for long-lived processes such as AI behavior, physics simulations, or player interactions.

; a looped wire
@wire(compound {
  Msg("Looping!")
  Once({
    0 >= compound-value
  })
  
  2 | Math.Add(compound-value) | Do(accumulate)
  1 | Do(accumulate)
  3 | Do(accumulate)
  Log > compound-value
} Looped: true)

; Looping!
; 6
; Looping!
; 18
; Looping!
; 42
; Looping!
; 90
; Looping!
; 186
; Looping!
; 378

Colorful wires 🌈

In the insightful piece, "What Color is Your Function?" by Bob Nystrom, the concept of function coloration in asynchronous programming is explained. Nystrom discusses the challenges posed by "coloring" functions, which refers to differentiating between synchronous and asynchronous functions. This differentiation is important as it impacts how functions are called and managed in a codebase.

In the innovative realm of Shards, the color of a function, referred to as the 'wire,' can be determined in advance. This foresight helps users understand the complexity of the graph and easily identify suspension points in their code. This brings a level of predictability and manageability that is often lacking in traditional asynchronous paradigms.

Meshes: Orchestrating Concurrency

In a world where multi-threading can make or break a game, Shards introduces Meshes, the conductors that synchronize and schedule Wires.

Thread Affinity

Meshes typically have an affinity with physical CPU threads, which makes the system highly aware of the underlying hardware. This awareness enables Shards to make intelligent decisions regarding the scheduling of Wires, resulting in a more responsive and efficient game.

@mesh(worker) ; simply declare a mesh
@schedule(worker compound) ; schedule a wire on the mesh
@run(worker FPS: 60) ; run the mesh (blocking)

Stackful Coroutines

While typical game engines struggle with balancing threads and asynchronous tasks, Wires can act as stackful coroutines. This means they can pause their execution and yield control back to the Mesh, enabling more complex and non-blocking workflows without the headaches of traditional multi-threading problems.

@wire(idle {
  When(Inputs.IsKeyDown("w") SwitchTo(move))
  ; ... do something while idle
} Looped: true)

@wire(move {
  WhenNot(Inputs.IsKeyDown("w") SwitchTo(idle))
	; ... do something while moving
} Looped: true)

SwitchTo(idle) ; start with idle state

Automatic Memory Management: A Leap Forward

Shards introduce a remarkable level of memory efficiency by automatically reusing memory between iterations and intelligently freeing it when Wires terminate. This is a significant advancement as it eliminates the need for manual memory management, which is often a cause of memory leaks and game crashes.

@wire(process-assets-table {
  Sequence(assets-collection)

  "hello" >> assets-collection ; notice that copies are optimized
  "world" >> assets-collection ; when written again this value will
	; be copied in pre-allocated memory!
  ; allocations happens during warmup and first iteration
  
  assets-collection
} Pure: true)

In this process-assets-table wire, we first declare a variable called assets-collection, which by default will be of type Any. This wire serves as a stateful container, retaining memory between different invocations, thus being both resource-efficient and speedy.

The wire starts its execution with the Sequence shard, which automatically clears the assets-collection while preserving its capacity. This is a crucial memory-optimization feature, reducing the need for frequent memory allocations and deallocations.

Next, we add the strings "hello" and "world" to assets-collection. This demonstrates how you can manipulate and use the collection within the wire.

Finally, the assets-collection itself is output from the wire. Thanks to the stateful design of Shards wires, the memory allocated for assets-collection is retained for future iterations, providing memory efficiency out-of-the-box.

The Pure: true annotation signifies that this wire is free from side-effects, meaning it will behave consistently each time it's executed.

With this setup, the memory used by assets-collection is smartly managed. It gets reused in subsequent wire invocations and is freed only when the wire is no longer needed.

Virtual Machine: An Innovative Execution Model

Shards operate on a unique virtual machine (VM) that does not target machine code. Instead, it uses an array of function pointers to achieve its goals. This model is derived from the threaded code technique, which is often seen in some Forth interpreters.

The Benefit of Simplicity

While JIT compilers are recognized for their performance, they also bring complexity. Shards bypass this by using a simple yet efficient execution model. This is especially beneficial for game development since JIT compilation is not permitted on game consoles, and iOS imposes strict restrictions on it. The Shards VM exemplifies how Shards combines simplicity with power, creating an ecosystem that is both easy to understand and reliable in execution.

The Language Backbone: C++ and Rust

Leveraging the computational strengths of C++ and the flourishing Rust ecosystem, Shards provides a robust and efficient foundation for game development. These languages have been chosen because they align with the core philosophy of Shards: power without unnecessary complexity. In a deliberate move, Shards has avoided using Garbage Collected (GC) languages, which often struggle to meet the real-time demands of apps and games due to their unpredictable pause times. Additionally, Shards utilizes stackful coroutines, a mechanism that is notoriously cumbersome under GC because every stack requires a thorough examination to manage memory. This choice emphasizes the focus on real-time performance and minimal overhead, enabling developers to fully harness the potential of Shards in creating immersive and responsive gaming experiences.

// A Rust shard
#[derive(shards::shard)]
#[shard_info("AddStuff", "Adds some optional value to the input")]
struct AddStuffShard {
  #[shard_required]
  required: ExposedTypes,
  #[shard_param("OtherValue", "The value to add", [common_type::none, common_type::int, common_type::int_var])]
  other_value: ParamVar,
}

impl Default for AddStuffShard {
  fn default() -> Self {
    Self {
      required: ExposedTypes::new(),
      other_value: ParamVar::new(0.into()),
    }
  }
}

#[shards::shard_impl]
impl Shard for AddStuffShard {
  fn input_types(&mut self) -> &Types {
    &INT_TYPES
  }

  fn output_types(&mut self) -> &Types {
    &INT_TYPES
  }

  fn warmup(&mut self, ctx: &Context) -> Result<(), &str> {
    self.warmup_helper(ctx)?;
    Ok(())
  }

  fn cleanup(&mut self) -> Result<(), &str> {
    self.cleanup_helper()?;
    Ok(())
  }

  fn compose(&mut self, data: &InstanceData) -> Result<Type, &str> {
    self.compose_helper(data)?;
    Ok(self.output_types()[0])
  }

  fn activate(&mut self, _context: &Context, input: &Var) -> Result<Var, &str> {
    let other = self.other_value.get();
    Ok(if other.is_none() {
      *input
    } else {
      let a: SHInt = input.try_into()?;
      let b: SHInt = other.try_into()?;
      (a + b).into()
    })
  }
}
// A C++ shard
struct AddStuff {
  static SHTypesInfo inputTypes() { return shards::CoreInfo::IntType; }
  static SHTypesInfo outputTypes() { return shards::CoreInfo::IntType; }
  static SHOptionalString help() { return SHCCSTR("Adds some optional value to the input"); }

  PARAM_PARAMVAR(_otherValue, "OtherValue", "The value to add", {shards::CoreInfo::NoneType, shards::CoreInfo::IntType, shards::CoreInfo::IntVarType});
  PARAM_IMPL(PARAM_IMPL_FOR(_otherValue));

  AddStuff() { _otherValue = Var(0); }

  void warmup(SHContext *context) { PARAM_WARMUP(context); }
  void cleanup() { PARAM_CLEANUP(); }

  PARAM_REQUIRED_VARIABLES();
  SHTypeInfo compose(SHInstanceData &data) {
    PARAM_COMPOSE_REQUIRED_VARIABLES(data);
    return outputTypes().elements[0];
  }

  SHVar activate(SHContext *shContext, const SHVar &input) {
    Var &other = (Var &)_otherValue.get();
    SHInt result = input.payload.intValue;
    if (!other.isNone())
      result += (SHInt)other;
    return Var(result);
  }
};
; Shards usage
1 | AddStuff(2) | Log = v ; 3
1 | AddStuff(v) | Log ; 4
2 | AddStuff | Log ; 2
Illustration of a vast, intricate labyrinth set in a digital space, filled with icons of coding languages, glitches, and technical challenges. Diverse game developers, of different genders and descents, try to find their way, expressing determination amidst complexity.

In the landscape of software development, Shards is carving out a niche where code is not just written but also visually mapped, reflecting an intuitive understanding that resonates with both human programmers and language models like GPT-4.

This trinity of form – textual, visual, and AI-compatible – is at the heart of Shards, establishing it as a tool that's as much about visual clarity as it is about computational depth. With plans to develop a visual programming interface, Shards is stepping into a future where the barriers between idea and execution are increasingly transparent.

In this future, AI doesn't just assist; it collaborates, making the language both a canvas for creativity and a framework for function. I welcome everyone interested in the crossroads of AI and visual programming to follow my updates on X/Twitter(https://twitter.com/voidtarget). There, we can engage in discussions about how Shards is shaping the way we think about and practice software development, making it more inclusive, intuitive, and interconnected.

Stay tuned for an upcoming series of in-depth articles where we'll explore:

  • Case Studies: Real-world applications of Shards in game development.
  • Developer Insights: First-hand experiences and testimonials.
  • Benchmarks: Performance metrics that showcase the efficiency of Shards.
  • Comparative Analysis: How Shards stacks up against traditional game development environments.
0 | Log("Exiting with error code") | Exit