# Getting Started

A blackboard is a data structure that provides a simple way to share information between different parts of a program. The blackboard is a common design pattern in artificial intelligence programming.

This blackboard uses source generators to generate typed code for your custom blackboard, based on very simple definitions in your code.

Each data value is stored as a `Signal`. This is a very small (typed) class that holds a specific value. The signal also contains events that are called when the value changes.

{% hint style="warning" %}
This package requires the use of Assembly Definitions. Make sure you understand them and know how to prevent circular dependencies before buying.
{% endhint %}

## Getting Started

It is advised to read through this readme in order to understand the inner workings of this system. In the examples folder there are multiple example/demo's set up. Please make sure to select the "Blackboard" GameObject in each scene and view the associated scripts. Comments and explanations are within the code.

## Read More

{% content-ref url="/pages/t0fdptUiN4PcjYPmto9S" %}
[Blackboard](/concepts/blackboard)
{% endcontent-ref %}

{% content-ref url="/pages/YtdMUnqk4ymsVVAeLVvp" %}
[Signals](/concepts/signals)
{% endcontent-ref %}

{% content-ref url="/pages/goWrMJiJ2tef0kRH6ikF" %}
[Saving & Loading](/concepts/saving_loading)
{% endcontent-ref %}

{% content-ref url="/pages/U5MbAIalE2QJFanwWktP" %}
[Validations](/concepts/validations)
{% endcontent-ref %}

{% content-ref url="/pages/MZM2M48LXLMx35YHKsgZ" %}
[Source Generators](/concepts/source_generators)
{% endcontent-ref %}

{% content-ref url="/pages/Gp94okz7mMqbu0P6PvJ7" %}
[Examples](/examples)
{% endcontent-ref %}

{% content-ref url="/pages/c2Xw9x1Y0EAeNasGnFnf" %}
[FAQ](/faq)
{% endcontent-ref %}


# How it works

In the Blackboard framework, we make extensive use of modern C# features such as partial classes, the Roslyn compiler and its analyzers, and source generators to simplify development and automate code generation. Below, we'll break down these concepts and how they work together to enhance your workflow.

![How it works](https://3907927415-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9IiEBezOtBtfBlKn7fXC%2Fuploads%2Fgit-blob-8ee36380fb2f5eaf121d16ae1bc752130ee512c0%2Fhow-it-works.png?alt=media)

## 1. Partial Classes

Partial classes allow you to split the definition of a class across multiple files. This is particularly useful for large classes or for situations where parts of the class need to be automatically generated, as seen in our framework.

In the image, the green and blue boxes are part of the same class, but they are declared in different sections of the code:

* Green Box: This is where you manually define certain properties or fields like private int health = 100;.
* Blue Box: This part of the class is automatically generated by a source generator (explained later), which wraps the field into a signal-based structure for better data management.

Partial classes allow both the manually written and automatically generated code to coexist and work seamlessly.

```csharp
public partial class ExampleBlackboard : BlackboardBehaviour
{
    private int health = 100;
}

public partial class ExampleBlackboard : IExampleBlackboard
{
    [SerializeField]
    private Signals.Health healthSignal;

    public int Health
    {
        get => this.healthSignal.Value;
        set => this.healthSignal.Value = value;
    }

    public Signals.IHealth HealthSignal => this.healthSignal;
}
```

The above code represents the two different parts of the same class, with one part written manually and the other generated automatically.

## 2. Compiler

The C# compiler is responsible for transforming your C# code into machine-readable code. When you compile your code, the compiler reads both manually written code (green box) and automatically generated code (blue box), combining them into a single class at runtime.

In our framework, this means that the compiler:

* Reads your custom code from files like ExampleBlackboard.
* Incorporates the generated code from partial classes created by the source generator.
* Produces the final binary that is executed at runtime.

## 3. Roslyn Analyzers

Roslyn Analyzers are tools that run during compilation and can inspect, diagnose, and provide warnings or suggestions about your code. They can be thought of as static analysis tools that help improve code quality by catching errors, enforcing coding styles, or suggesting performance improvements.

In the Blackboard framework, Roslyn Analyzers play a role in ensuring that your custom blackboard classes conform to the expected patterns and conventions. For example, if a user fails to set up a signal property correctly, an analyzer could provide a compile-time warning with a suggestion to fix the issue.

## 4. Source Generators

Source generators are a powerful feature of the C# compiler that allows additional source code to be automatically generated at compile time. In the context of the Blackboard framework, source generators take care of the repetitive and boilerplate code necessary for signal management.

In the example provided in the image, the blue box is generated by a source generator. Instead of manually creating the signal wrappers for fields like health, the source generator creates this code automatically based on some input rules or configurations.

For example, the code generator could inspect the field `private int health = 100;` and generate the corresponding signal code like so:

```csharp
[SerializeField]
private Signals.Health healthSignal;

public int Health
{
    get => this.healthSignal.Value;
    set => this.healthSignal.Value = value;
}

public Signals.IHealth HealthSignal => this.healthSignal;
```

This greatly simplifies development by reducing boilerplate code and ensuring consistency across your project.

## 5. How It All Fits Together

* You define a partial class like ExampleBlackboard, with some fields and properties.
* The compiler reads your partial class and looks for additional code generated by source generators.
* The source generator creates code that integrates your fields with the signal system.
* During compilation, the Roslyn Analyzers ensure that your code follows the framework’s best practices, issuing warnings or suggestions if needed.
* Finally, the compiler merges your manually written and automatically generated code into a complete class that can be used in your project.

By understanding these components, you can see how the Blackboard framework automates signal management and keeps your code clean and maintainable, while allowing flexibility with manual additions through partial classes.


# FAQ

<details>

<summary>Where can I buy the pro version?</summary>

Thank you for concidering to buy the pro version! It can be bought from the [Unity Asset Store](https://u3d.as/3igL)

</details>

<details>

<summary>What is included in the free version?</summary>

The free version includes regular signals and computed signals of the following types: `int`, `float`, `string` and `bool`.

All other types, lists signals, nested signals, and interfaces are not supported in the free version.

</details>

<details>

<summary>Where can I get lite support?</summary>

You can join us on [Discord](https://discord.gg/dCPnHaYNrm) and use the `#blackboard-lite` channel!

</details>

<details>

<summary>Where can I get pro support?</summary>

You can join us on [Discord](https://discord.gg/dCPnHaYNrm)!

If you PM `@CrashKonijn` with your invoice number you'll get access to the `#blackboard-pro` channel!

</details>

<details>

<summary>How do I run the generators?</summary>

The Unity compiler runs them automatically!

</details>

<details>

<summary>No code is generated for my blackboard</summary>

Unity scopes source generators to all code within the same Assembly Definition. Please make sure the Blackboard.Generators.dll is in the same Assembly Definition as your blackboards.

</details>

<details>

<summary>How do I see the generated code?</summary>

To view the other partial/generated code you can do the following:

In Rider: ctrl + click on the classname In Visual Studio: Place your cursor on the classname and press F12. The declarations window should show and you can select the generated partial. The file is \_\<lite|pro>\_generated.cs.

<img src="https://3907927415-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9IiEBezOtBtfBlKn7fXC%2Fuploads%2Fgit-blob-d1f0810886f513610197c5b8dd3f957c7adc24cd%2Fblackboard_navigate_rider.gif?alt=media" alt="Example 03 Events editor" data-size="original">

Other editor's (including VS Code) are not officialy supported by \[Unity]\(<https://docs.unity3d.com/Manual/roslyn-analyzers.html>) and can't show the code in the editor!

You can still see the generated code by adding the `[DebugBlackboard]` attribute to your blackboard class. Two buttons should now appear in the (unity) inspector, that will allow you to Debug.Log or copy the source.

</details>

<details>

<summary>My editor doesn't update correctly</summary>

Sometimes the editor loses it's binding, re-selecting the GameObject should fix this.

</details>

<details>

<summary>Updating a (nested) value in a list doesn't call a change event on the list.</summary>

Unfortunaly the system can't detect nested changes. You should listen for changes to signals on the nested object yourself.

</details>


# Release Notes

## 1.1.0 (BREAKING)

* BREAKING: Namespace moved from `Blackboard.X` to `RabbitBlackboard.X`.
* Feature: Pro now also generates a value enum. This can be used to retrieve signals based on this enum value.
* Feature: Generated blackboard interfaces now extend their parent interfaces as well
* Fixed: Blackboard interface not having setters
* Fixed: Generator breaking when other sources also used `Blackboard` namespace.
* Fixed: Class without namespace breaking generator

## 1.0.12

* Feature: Added support to overwrite ToData & FromData methods
* Feature: Added support to overwrite ToJson & FromJson methods

## 1.0.11

* Fix: GenerationState being generated for incorrect assemblies
* Fix: Improved generation time

## 1.0.10

* Feature: Added support for referenced blackboards.
* Feature: Added blackboard state editor window. Gives you insights into logs and errors of the generator.
* Fix: Improved handling of errors by the generator.
* Fix: Global namespace breaking generator

## 1.0.9

* Fix: Better support for generic types used in blackboard.
* Feature: Added support for scriptable object blackboards.
* Feature: Added support for class blackboards.

## 1.0.8

* Feature: Added support for dictionaries!
* Feature: Added Dictionary example
* Fix: Removed warnings for public methods on blackboards
* Fix: Added warning when using non-signal variable as an input for a computed signal.
* Fix: Using non-signal variables in a computed method won't break the generated code anymore.
* Fix: List not throwing event when setting through index (`listSignal.Value[0] = "something"`)
* Fix: FromData and ToData not always rendering correctly with nested sub types.
* Fix: Editor computed signals not rendering in correct height, allowing for overlap.
* Docs: Added more info about list and dictionary signals.

## 1.0.7

* Added the `[DebugBlackboard]` attribute that allows you so see the generated code in the inspector.
* Added object list example
* Improved nested object example
* Fixed bug where validations weren't always applied from the inspector
* Fixed bug with objects in lists not rendering signals
* Introduced `IAllAreSignals` and `ISomeAreSignals` to add clarity. `IHasSignals` is now obsolete.

## 1.0.6

* Fixed bug when building
* Improved examples
* Improved docs

## 1.0.5

* Fixed bug

## 1.0.4

* Improved editor
* Added support for nested types
* Greatly improved API
* Added more examples
* Improved docs

## 1.0.3

* Improved editor
* Improved source generation, increasing future extandability
* Added validation attributes

## 1.0.2

* Improved handling of lists
* Improved interface generation
* System will now give feedback about possible mistakes
* Added more examples

## 1.0.1

* Added better examples
* Improved editor
* Better abstractions

## 1.0.0

* Initial release


# Concepts


# Blackboard

## Overview

The Blackboard system is a flexible and powerful framework designed for Unity, enabling developers to create and manage game data dynamically. It leverages the concept of a "blackboard" as a central data repository where different components of a game can read and write data, facilitating communication and data sharing among disparate systems without direct references.

## Key Concepts

### BlackboardBehaviour

`BlackboardBehaviour` is the base class for any blackboard. It allows the creation of custom blackboards by extending this class. Custom blackboards can contain any number of data fields, known as signals, which are automatically managed by the system.

### Signals

Signals are the primary means of communication within the blackboard system. They represent data points on the blackboard, such as health, ammo, or player state. Signals are dynamically generated and managed, allowing for a flexible and scalable approach to data management.

### Debugging

The `DebugBlackboard` attribute can be applied to a `BlackboardBehaviour` class to enable viewing the generated source for the blackboard in the Unity inspector. This is particularly useful for debugging and understanding the auto-generated aspects of your blackboard.

### Creating a Simple Blackboard

Below is an example of how to create a simple blackboard with a single signal representing health.

```csharp
using CrashKonijn.Blackboard.Contracts;

namespace CrashKonijn.Blackboard.Blackboards.Examples
{
    public partial class ExampleBlackboard : BlackboardBehaviour
    {
        private int health = 100;
    }
}
```

A source generator will generate the following code:

{% hint style="info" %}
The source generators are automatically run by the Unity compiler!
{% endhint %}

```csharp
public partial class ExampleBlackboard : IExampleBlackboard {
    // Prive serialized reference to the signal
    [SerializeField]
    private Signals.Health healthSignal;
    // Wrapper around the signal value that allows you to acces the health value as blackboard.Health;
    public int Health { get => this.healthSignal.Value; set { this.healthSignal.Value = value; } }
    // The reference to the signal
    public Signals.IHealth HealthSignal => this.healthSignal;
    
    public static class Signals {
        // The generated signal
        [Serializable]
        public class Health : FieldSignal<int>, IHealth {
            public Health(int value) : base(value, "Health", "healthSignal") { }
        }
        // An interface matching the signal
        public interface IHealth : IFieldSignal<int> { }
    }
}

// An interface mathing the entire blackboard is also generated!
public interface IExampleBlackboard
{
    public int Health { get; }
    public BasicBlackboard.Signals.IHealth HealthSignal { get; }
}
```

### Interacting with the Blackboard

Components can interact with the blackboard by referencing the BlackboardBehaviour and using the generated signals. Here's an example of a component that modifies the health signal on a blackboard:

```csharp
public class EventsHealthBehaviour : MonoBehaviour
{
    public EventsBlackboard Blackboard { get; set; }

    public void OnEnable()
    {
        this.Blackboard.HealthSignal.OnValueChanged.AddListener(this.OnHealthChanged);
    }
    
    public void OnDisable()
    {
        this.Blackboard.HealthSignal.OnValueChanged.RemoveListener(this.OnHealthChanged);
    }

    public void DoDamage()
    {
        this.Blackboard.Health -= 10;
    }
    
    public void Heal()
    {
        this.Blackboard.HealthSignal.Value += 10;
    }

    public void OnHealthChanged(int health)
    {
        Debug.Log($"Health changed: {health}");
    }
}
```

### Viewing Generated Code

The source generators automatically run by Unity's compilation process add signals and other necessary code to the blackboard. To view the generated code:

* **In Rider**: Ctrl + click on the classname.
* **In Visual Studio**: Place your cursor on the classname and press F12. The declarations window should show, and you can select the generated partial. The file is named `<FileName>_<lite|pro>_generated.cs`.
* **Other editors**: Unity doesn't support any other editor officially and as such are incapable of viewing the generated code added by source generators. By adding the `[DebugBlackboard]` attribute to your blackboard the inspector will show two buttons which will allow you to Debug.Log the generated code or to copy it.

### Getting values using enums

In the pro version each value is also generated as an enum. All values of this enum reference a signal. This could be helpfull when building a value selector in an editor window for example.

This is what is being generated:

```csharp
public partial class ComputedBlackboard {
    // the rest

    public ISignal GetSignal(Value value)
    {
        switch (value)
        {
           case Value.Health: return this.HealthSignal;
           case Value.LowHealth: return this.LowHealthSignal;
           case Value.IsAlive: return this.IsAliveSignal;
           case Value.IsLowHealth: return this.IsLowHealthSignal;
        }

        return null;
    }

    public enum Value {
      Health,
      LowHealth,
      IsAlive,
      IsLowHealth,
    }
}
```

Since this is untyped, the `GetSignal` method returns `ISignal`. To use the signal cast it to any of the expected values, for example:

```csharp
if (signal is IReadWriteSignal<string> stringSignal) {
  stringSignal.Value = "something"
}

if (signal is IReadSignal<int> intSignal) {
   Debug.Log(intSignal.Value)
}
```

## IAllAreSignals and ISomeAreSignals Interfaces

In the Blackboard framework, signals are a fundamental concept used to represent data that can change over time, triggering updates or actions when these changes occur. The IAllAreSignals and ISomeAreSignals interfaces play a crucial role in defining how signals are treated within classes that represent data in the Blackboard.

Using these interfaces allows you to add signals to nested classes in a blackboard, or to add signals to any other class!

### IAllAreSignals Interface

The IAllAreSignals interface is used to mark a class as a container where all valid fields are considered signals. This means that every field in a class implementing this interface is automatically treated as a signal without the need for explicit marking. This interface is particularly useful when you want to ensure that all properties of an object are reactive and can trigger updates or actions upon changes.

#### Example Usage

```csharp
[Serializable]
public partial class Gun : IAllAreSignals
{
    private GunType _type;
}
```

In the example above, the `Gun` class is marked with the `IAllAreSignals` interface, indicating that all of its fields, such as `_type`, are treated as signals within the Blackboard system.

### ISomeAreSignals Interface

Contrary to `IAllAreSignals`, the `ISomeAreSignals` interface is used when only some fields within a class should be treated as signals. This interface requires explicit marking of fields that should be considered signals, offering more granular control over which properties are reactive.

#### Example Usage

```csharp
[Serializable]
public partial class Bullets : ISomeAreSignals
{
    // This won't be generated as a signal
    // The serializefield makes sure it's saved in the editor
    [SerializeField]
    private int clipSize = 5;
    
    // You manually have to mark the field as a signal
    [Signal]
    [Blackboard.Contracts.Min(0)]
    private int inGun = 5;
    
    // This marks it as a computed signal
    [Signal]
    private static bool _isEmpty(Signals.InGun inGun) => inGun.Value <= 0;

    // Methods won't collide with generated code
    public void Shoot()
    {
        // Make sure you use the property instead of the field
        // Updating the field won't do anything
        this.InGun--;
    }

    public void Reload()
    {
        this.InGun = this.clipSize;
    }
}
```

In the Bullets class example, the `ISomeAreSignals` interface is implemented, and only the `inGun` field is explicitly marked as a signal using the `[Signal]` attribute. This allows for specific fields to be reactive, while others, like `clipSize`, remain as regular fields.


# Signals

Signals are a core concept in the Blackboard framework, enabling dynamic data flow and interaction within your application. They are used to represent data that can change over time and trigger updates or actions when these changes occur. This document provides an overview of how signals work within the Blackboard framework, particularly focusing on computed signals as illustrated in the ComputedBlackboard class.

## What is a Signal?

A signal is an encapsulation of a value that can change over time. In the Blackboard framework, signals are used to represent data points, such as health levels or status indicators, that other parts of the application need to monitor or react to. Signals can be simple, holding a single value, or computed, deriving their value from other signals.

## Simple Signals

Simple signals hold a direct value, like an integer or a boolean. They are straightforward and notify listeners when their value changes. An example of a simple signal could be a health signal representing a character's health points in a game.

A field must adhere to the following conditions to be generated as a signal:

1. It must be private
2. The name must be written as `camelCase` or `_camelCase`

## Computed Signals

Computed signals derive their value from one or more other signals. They automatically update when any of the dependent signals change, ensuring that the computed value is always up to date. Computed signals are powerful tools for creating reactive systems where changes propagate through the application as data changes.

For a computed signal to be generated a method must adhere to the following rules:

1. It must be private
2. It must be static
3. The name must be written as `camelCase` or `_camelCase`

### Example: Health Status Signals

Consider a scenario where we have two signals: Health and LowHealth. The Health signal represents the current health points of a character, while LowHealth is a threshold value below which the character is considered to be in low health.

The `ComputedBlackboard` class demonstrates how computed signals can be implemented:

`_isAlive`: This computed signal returns `true` if the Health value is greater than 0, indicating the character is alive. `_isLowHealth`: This computed signal returns `true` if the Health value is less than or equal to the LowHealth value, indicating the character is in low health.

```csharp
private static bool _isAlive(Signals.Health health) => health.Value > 0;
private static bool _isLowHealth(Signals.Health health, Signals.LowHealth lowHealth) => health.Value <= lowHealth.Value;
```

These computed signals enable the application to react dynamically to changes in the character's health, updating game logic, UI, or triggering events based on the character's health status.

## List Signals

A list signal is defined similarly to simple signals but is specifically designed to hold a list of items. It can be used to represent any collection of data that needs to be monitored for additions, removals, or changes.

### Creating a List Signal

To create a list signal, you define a private field in your blackboard that holds a collection, and the Blackboard system's source generators automatically generate the necessary code to treat it as a signal. This auto-generated code includes methods for adding, removing, and iterating over items in the list, as well as notifying listeners of changes.

#### Example:

```csharp
[Serializable]
public partial class InventoryBlackboard : BlackboardBehaviour
{
    private List<Item> _items = new List<Item>();
}
```

In this example, `_items` is of type List<>, indicating that it should be treated as a list signal. The Blackboard system will generate code that allows other parts of the game to interact with the `_items` list in a reactive manner.

### Interacting with List Signals

Components can interact with list signals by subscribing to change notifications or by using the generated methods to modify the list. This allows for a decoupled architecture where components can react to changes in the list without directly managing the list itself.

#### Example:

```csharp
public class InventoryManager : MonoBehaviour
{
    public InventoryBlackboard Blackboard { get; set; }

    public void AddItem(Item item)
    {
        this.Blackboard.Items.Add(item);
    }

    public void RemoveItem(Item item)
    {
        this.Blackboard.Items.Remove(item);
    }
}
```

In this example, `InventoryManager` interacts with the Items list signal on the InventoryBlackboard to add and remove items.

## Dictionary Signals

Dictionary signals are similar to list signals but are designed to hold key-value pairs. They are useful for representing data that requires a mapping between keys and values, such as a collection of settings or lookup tables.

### Creating a Dictionary Signal

To create a dictionary signal, you define a private field in your blackboard that holds a dictionary, and the Blackboard system's source generators automatically generate the necessary code to treat it as a signal. All the standard dictionary operations, such as adding, removing, and updating key-value pairs, are supported through the generated code.

#### Example:

```csharp
[Serializable]
public partial class ExampleBlackboard : BlackboardBehaviour
{
    private Dictionary<string, int> _playerScores_ = new Dictionary<string, int>();
}
```

## Usage in behaviours

Signals are typically used within components that need to react to data changes. The `ComputedHealthBehaviour` class demonstrates how a component can interact with signals, adjusting the character's health and responding to changes:

`DoDamage`: Decreases the health signal by 10. `Heal`: Increases the health signal by 10. These methods modify the underlying signals, which in turn can trigger updates in computed signals or other parts of the application that are listening for changes.

```csharp
// Simplified example
public class ComputedHealthBehaviour : MonoBehaviour
{
    public ComputedBlackboard Blackboard { get; set; }

    public void DoDamage()
    {
        this.Blackboard.Health -= 10;
    }
    
    public void Heal()
    {
        this.Blackboard.Health += 10;
    }
}
```

## Signal Events

Signals in the CrashKonijn Blackboard system are designed to notify interested parties when the value of a signal changes. Each signal has two primary events associated with it, designed to cater to different needs for observing changes.

### OnChanged Event

The OnChanged event is a basic notification that the signal's value has changed. It does not provide the new value of the signal. This event is useful when you only need to know that a change occurred but do not need to know the specifics of the change.

Example usage:

```csharp
signal.OnChanged += () => Debug.Log("Signal value changed.");
```

### OnValueChanged Event

The OnValueChanged event is more detailed, providing the new value of the signal as a parameter to the event handlers. This is useful when the new value is needed to perform further operations or calculations.

Example usage:

```csharp
signal.OnValueChanged += (newValue) => Debug.Log($"New signal value: {newValue}");
```

### Determining Value Changes

The system determines that a signal's value has changed through the HasChanged method. This method compares the current value of the signal with the new value being set. If the method determines that the value has indeed changed (i.e., the new value is different from the current value), it triggers the change events (OnChanged and OnValueChanged).

Example implementation:

```csharp
public T Value {
    get => this.value;
    set { this.setValue(value) };
}

protected void SetValue(T value)
{
    if (this.HasChanged(value))
    {
        this.value = value;
        this.Changed(value);
    }
}
```

### Limitations with Nested Types

It's important to note that changes to nested types within a signal's value do not automatically trigger change events. The HasChanged method typically compares the top-level object references or primitive values. If a nested property or field within an object changes, but the top-level reference remains the same, the system does not recognize this as a change.

For example, if a signal holds an object of a class Person with a property Name, changing the Name property of the existing object does not trigger the signal's change events. To ensure changes are detected, a new instance of the object with the updated values must be set on the signal, or custom logic must be implemented to manually trigger the change events when nested properties are modified.

This behavior ensures performance optimization by avoiding deep checks on complex objects but requires careful consideration when working with nested types to ensure changes are properly detected and communicated.

## Signal interfaces

In the Blackboard system, each signal class can have a corresponding interface generated automatically. This feature is particularly useful in the Pro version, where it enables a more flexible and type-safe way of interacting with signals. The generated interface allows for easy mocking in tests and adherence to the SOLID principles, especially the Dependency Inversion Principle.

### How It Works

When a new signal class is defined, the source generator checks if the class is part of the Pro version. If so, it generates a matching interface for that signal. This interface is named after the signal class but prefixed with an I. For example, if the signal class is named Health, the generated interface will be named IHealth.

### Code Example

Consider the following signal class definition:

```csharp
[Serializable]
public class Health : FieldSignal<int>
{
    public Health(int value) : base(value, "Health", "healthSignal") { }
}
```

For the above Health signal, the source generator will produce an interface like this:

```csharp
public interface IHealth : IFieldSignal<int> { }
```

### Using the Generated Interface

Once the interface is generated, it can be used to interact with the signal in a type-safe manner. Here's how you can use the IHealth interface:

```csharp
// Assuming ExampleBlackboard is a class that contains the Health signal
var blackboard = this.GetComponent<ExampleBlackboard>();

// Accessing the Health value directly
blackboard.Health = 100;
Debug.Log(blackboard.Health);

// Using the generated interface to interact with the Health signal
IHealth healthSignal = blackboard.HealthSignal;
healthSignal.Value = 100;
Debug.Log(healthSignal.Value);
```

### Advantages of Using Interfaces for Signals

Type Safety: By defining an interface for signals, such as IHealth, you ensure that any method accepting this interface as a parameter can only be passed objects that adhere to the specified contract. This reduces runtime errors and improves code reliability.

1. **Decoupling**: Interfaces allow for a decoupling between the signal's implementation and its usage. You can change the underlying implementation of the Health signal without affecting the methods that use it, as long as the interface remains consistent.
2. **Testability**: With interfaces, it becomes easier to mock signals in unit tests. You can create mock objects that implement IHealth to test how your methods react to different signal states without needing to instantiate the actual signal classes.

#### Example Usage

Consider a method within your Unity project that modifies the health of a character based on certain game events:

```csharp
public void ApplyDamage(IHealth healthSignal, int damageAmount)
{
    healthSignal.Value -= damageAmount;
    Debug.Log($"New Health: {healthSignal.Value}");
}
```

This method accepts an IHealth interface as a parameter, making it agnostic to the specific implementation of the health signal. You can pass any object that implements IHealth, allowing for a flexible and modular approach to signal management.

To use this method with a signal from your blackboard, you would retrieve the signal reference and pass it as follows:

```csharp
var blackboard = this.GetComponent<ExampleBlackboard>();
var healthSignal = blackboard.HealthSignal;

ApplyDamage(healthSignal, 20);
```

In this scenario, ApplyDamage can interact with the health signal through the IHealth interface, adjusting its value and logging the new health. This approach leverages the benefits of interfaces for signals, promoting a clean, maintainable, and testable codebase.


# Saving & Loading

The Blackboard system provides a flexible way to save and load data, making it easier to persist state across sessions or to initialize your system with predefined data. This functionality is encapsulated in two pairs of methods: `ToJson/FromJson` and `ToData/FromData`. A `[BlackboardClass]Data` object is generated with your blackboard that can be used to easily save and load your blackboard!

## Data Methods

The ToData method converts the Blackboard's state into a data class instance. This data class is automatically generated and named after the Blackboard with the suffix Data. This method is useful for scenarios where you prefer working with strongly-typed data structures over JSON strings.

## Example

Given the following blackboard:

```csharp
public partial class ExampleBlackboard : IBlackboard
{
    private int _health = 100;
    private int _ammo = 50;
    private int _lives = 3;
}
```

The source generator will generate the following code:

```csharp
public partial class ExampleBlackboard : IExampleBlackboard
{
    public ExampleBlackboardData ToData()
    {
        return new ExampleBlackboardData
        {
            Health = this.HealthSignal.Value,
            Ammo = this.AmmoSignal.Value,
            Lives = this.LivesSignal.Value,
        };
    }

    public void FromData(ExampleBlackboardData data)
    {
        this.HealthSignal.Value = data.Health;
        this.AmmoSignal.Value = data.Ammo;
        this.LivesSignal.Value = data.Lives;
    }
}

public class ExampleBlackboardData
{
    public int Health;
    public int Ammo;
    public int Lives;
}
```

### Saving to Data

```csharp
public void SaveData()
{
    var blackboardData = this.Blackboard.ToData();
    // Use 'blackboardData' as needed
}
```

### Loading from Data

To load Blackboard data from a data class instance, use the FromData method. This method updates the Blackboard's state to reflect the data in the provided instance.

```csharp
public void LoadData(YourDataClass blackboardData)
{
    this.Blackboard.FromData(blackboardData);
}
```

### DontSaveAttribute

This attribute can be used to prevent a value from being saved when calling `ToData()`. This can be useful for values that are only temporary or are calculated.

```csharp
using Blackboard.Contracts.Attributes;

public partial class ExampleBlackboard : BlackboardBehaviour {
    [DontSave] private int _health;
}
```

## JSON Methods

### Saving to JSON

To save the current state of the Blackboard to a JSON string, use the ToJson method. This method serializes the Blackboard's data into a JSON format string, which can then be saved to a file, PlayerPrefs, or any other storage mechanism your application uses.

```csharp
public void SaveJson()
{
    string json = this.Blackboard.ToJson();
    // Save 'json' to your storage
}
```

### Loading from JSON

To load Blackboard data from a JSON string, use the FromJson method. This method deserializes the provided JSON string, updating the Blackboard's state with the data.

```csharp
public void LoadJson(string json)
{
    if (string.IsNullOrEmpty(json))
    {
        Debug.Log("Can't load empty json");
        return;
    }

    this.Blackboard.FromJson(json);
}
```

## Note

Please be aware that these implementations are simple and might not cover all use cases, such as handling GameObjects or interfaces. You may need to extend or modify the provided methods to suit your specific needs.


# Validations

The framework allows for the creation of complex blackboard systems in Unity, facilitating communication between different parts of your game or application. One of the powerful features of this framework is the ability to validate signals using `IValidation` attributes. These attributes ensure that the data within your signals meets certain criteria, enhancing the robustness and reliability of your blackboard system.

`IValidation` attributes are used to enforce data integrity and constraints on the signals within a blackboard. By applying these attributes, developers can define rules that signal values must adhere to, such as minimum and maximum values, required string patterns, or custom validation logic.

## Using validation attributes on Signals

To use IValidation attributes on signals, you first need to define a signal within a class that extends BlackboardBehaviour. Then, you can apply one or more IValidation attributes to this signal to enforce validation rules.

Here's an example of a blackboard with a signal that uses a built-in validation attribute:

```csharp
using CrashKonijn.Blackboard.Contracts;
using UnityEngine;

namespace CrashKonijn.Blackboard.Blackboards.Examples
{
    public class HealthBlackboard : BlackboardBehaviour
    {
        [MinMax(0, 100)]
        public int health;
    }
}
```

## Creating Custom Validation Attributes

To create a custom validation attribute, you need to extend the `ValidateAttributeBase` class and override its validation method. This method should return true if the value passes validation and false otherwise.

Here's an example of a custom validation attribute that ensures a string value is always capitalized:

```csharp
public class CapitalizeAttribute : ValidateAttributeBase
{
    public override T Validate<T>(T value)
    {
        if (value is string stringValue)
        {
            return (T) (object) stringValue.ToUpper();
        }

        return value;
    }
}
```

You can then apply this custom validation attribute to a signal in a BlackboardBehaviour:

```csharp
namespace CrashKonijn.Blackboard.Blackboards.Examples
{
    public class PlayerNameBlackboard : BlackboardBehaviour
    {
        [Capitalize]
        public string playerName;
    }
}
```

## Existing validations

The following validations are provided by the package:

### NormalizeAttribute

Normalizes a numeric value relative to a base value, optionally limiting the result between 0 and 1.

#### Parameters

* `baseValue` (float): The value that should represent 1f in the normalization process. Default is 1f.
* `limit` (bool): Determines whether to clamp the normalized value between 0 and 1. Default is false.

#### Usage

```csharp
[Normalize(100f, true)]
private float speed;
```

### EaseInOutCurveAttribute

Transforms a value based on an ease-in-ease-out animation curve, allowing for smooth transitions between two values over time.

#### Parameters

* `timeStart` (float): The start time for the ease curve.
* `valueStart` (float): The start value for the ease curve.
* `timeEnd` (float): The end time for the ease curve.
* `valueEnd` (float): The end value for the ease curve.
* `exponent` (float): The exponent applied to the curve's output value. Default is 1f.

#### Usage

```csharp
[EaseInOutCurve(0f, 0f, 1f, 100f, 2f)]
private float animationProgress;
```

### MinMaxAttribute

Ensures a numeric value stays within a specified minimum and maximum range.

#### Parameters

* `min` (float/int): The minimum allowable value.
* `max` (float/int): The maximum allowable value.

#### Usage

```csharp
[MinMax(0, 100)]
private int health;
```

### MinAttribute

Ensures a numeric value does not fall below a specified minimum.

#### Parameters

* `min` (float/int): The minimum allowable value.

#### Usage

```csharp
[Min(0)]
private float minimumDistance;
```

#### MaxAttribute

Ensures a numeric value does not exceed a specified maximum.

#### Parameters

* `max` (float/int): The maximum allowable value.

#### Usage

```csharp
[Max(100)]
private float maximumDistance;
```


# Source Generators

Source generators are a feature of the Roslyn compiler platform, which is used by C#. They allow developers to generate additional source code during the compilation process. This can be useful for a variety of tasks, such as automatically generating boilerplate code, ensuring compile-time checks on certain conditions, or even creating domain-specific languages within C#.

Here's a basic overview of where source generators fit in the build process:

1. **Initialization**: When the compilation process starts, the Roslyn compiler initializes all registered source generators. This is where a source generator can set up any initial state or register callbacks for later stages. For example, in the BlackboardSourceGenerator, the Initialize method registers a syntax receiver to collect certain syntax nodes for later processing.
2. **Generation**: After the initial parsing and before the actual compilation of user code into IL (Intermediate Language), the compiler invokes the Execute method of each source generator. At this point, the generator can analyze the code, and based on its logic, generate additional C# source files. These generated files are then compiled along with the user's original source files. In the BlackboardSourceGenerator, the Generate method analyzes collected syntax nodes and generates source files based on them.
3. **Compilation**: The generated source files are compiled along with the original source files into the final assembly. Errors or warnings from the source generator can be reported during this phase and will appear alongside other compilation messages.
4. **Output**: The output of the compilation process includes the generated source files (which can sometimes be viewed in the IDE for debugging purposes), the compiled assembly, and any diagnostics (errors or warnings) produced during compilation.

## Assembly Definitions

Source generators are used to generate the code for the blackboard. This allows you to define the data in a simple way, and have the code generated for you. Source generators in Unity check if they apply to any class "within their reach". Allowing the `Blackboard.Generators.dll` access to the complete project will slow down compilation. To mitigate this the source generation dll is placed into a specific assembly definition. This way the source generation only applies to the classes that are within the assembly definition.

Therefore it is important to place the `Blackboard.Generators.dll` in the same assembly definition as the classes that use the blackboard, or place any new blackboard classes in the existing assembly definition.


# Examples


# 01 Basic

This is a basic example showing how to create a simple blackboard.

These files can be found in the /Examples/\[example] and /Blackboards/Examples folders.

![Example 01 Basics editor](https://3907927415-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9IiEBezOtBtfBlKn7fXC%2Fuploads%2Fgit-blob-f01d3b901147f7a880345925b7fe39ba2b01bd9d%2Fblackboard_examples_01.gif?alt=media)

## BasicBlackboard.cs

```csharp
﻿using CrashKonijn.RabbitBlackboard.Contracts;

namespace CrashKonijn.BlackboardPro.Blackboards.Examples
{
    // This attribute will enable viewing the generated source for the blackboard in the inspector.
    // Only enable this if you can't use Rider or Visual Studio to view the generated source.
    [DebugBlackboard]
    public partial class BasicBlackboard : BlackboardBehaviour
    {
        // This will generate a HealthSignal containing an int value on this same class.
        // Even though you can't see it in this file, it has been added in another 'partial'.
        // The source generators are automatically run by Unity's compilation process!

        // To view the other partial/generated code you can do the following:
        // In Rider: ctrl + click on the classname
        // In Visual Studio: Place your cursor on the classname and press F12. The declarations window should show and you can select the generated partial. The file is <FileName>_<lite|pro>_generated.cs.
        private int health = 100;
    }
}

```

## BasicHealthBehaviour.cs

```csharp
﻿using CrashKonijn.RabbitBlackboard.Contracts;
using UnityEngine;
using BasicBlackboard = CrashKonijn.BlackboardPro.Blackboards.Examples.BasicBlackboard;

namespace CrashKonijn.BlackboardPro.Examples._01_basic
{
    [RequireComponent(typeof(BasicBlackboard))]
    public class BasicHealthBehaviour : MonoBehaviour
    {
        /* Simple hack to show a button in the editor */
        [Header("Removes 10 health.")]
        [Button(nameof(DoDamage))]
        public string doDamageButton;

        /* Simple hack to show a button in the editor */
        [Header("Adds 10 health")]
        [Button(nameof(Heal))]
        public string healButton;

        private BasicBlackboard blackboard;

        public BasicBlackboard Blackboard
        {
            get
            {
                if (this.blackboard == null)
                {
                    this.blackboard = this.GetComponent<BasicBlackboard>();
                }

                return this.blackboard;
            }
        }

        public void DoDamage()
        {
            this.Blackboard.Health -= 10;
        }

        public void Heal()
        {
            this.Blackboard.Health += 10;
        }
    }
}

```


# 02 Computed

This example shows you how to create computed signals.

These files can be found in the /Examples/\[example] and /Blackboards/Examples folders.

![Example 02 Computed editor](https://3907927415-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9IiEBezOtBtfBlKn7fXC%2Fuploads%2Fgit-blob-55c4ecb230a3888ccedf299a90ed3e676c4d8420%2Fblackboard_examples_02.gif?alt=media)

## ComputedBlackboard.cs

```csharp
﻿using CrashKonijn.RabbitBlackboard.Contracts;

namespace CrashKonijn.BlackboardPro.Blackboards.Examples
{
    public partial class ComputedBlackboard : BlackboardBehaviour
    {
        private int health = 100;
        private int lowHealth = 40;

        // Computed signals automatically update when their dependencies change
        private static bool _isAlive(Signals.Health health) => health.Value > 0;

        // Computed signals can depend on multiple signals
        private static bool _isLowHealth(Signals.Health health, Signals.LowHealth lowHealth) => health.Value <= lowHealth.Value;
    }
}

```

## ComputedHealthBehaviour.cs

```csharp
﻿using CrashKonijn.RabbitBlackboard.Contracts;
using UnityEngine;
using ComputedBlackboard = CrashKonijn.BlackboardPro.Blackboards.Examples.ComputedBlackboard;

namespace CrashKonijn.BlackboardPro.Examples._02_computed
{
    [RequireComponent(typeof(ComputedBlackboard))]
    public class ComputedHealthBehaviour : MonoBehaviour
    {
        /* Simple hack to show a button in the editor */
        [Header("Removes 10 health.")]
        [Button(nameof(DoDamage))]
        public string doDamageButton;

        /* Simple hack to show a button in the editor */
        [Header("Adds 10 health")]
        [Button(nameof(Heal))]
        public string healButton;

        private ComputedBlackboard blackboard;

        public ComputedBlackboard Blackboard
        {
            get
            {
                if (this.blackboard == null)
                {
                    this.blackboard = this.GetComponent<ComputedBlackboard>();
                }

                return this.blackboard;
            }
        }

        public void DoDamage()
        {
            this.Blackboard.Health -= 10;
        }

        public void Heal()
        {
            this.Blackboard.Health += 10;
        }
    }
}

```


# 03 Events

This example shows you how to use events on signals.

These files can be found in the /Examples/\[example] and /Blackboards/Examples folders.

![Example 03 Events editor](https://3907927415-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9IiEBezOtBtfBlKn7fXC%2Fuploads%2Fgit-blob-aca844952991ee004f5b95ac2facadf45f18c313%2Fblackboard_examples_03.gif?alt=media)

## EventsBlackboard.cs

```csharp
using CrashKonijn.RabbitBlackboard.Contracts;

namespace CrashKonijn.BlackboardPro.Blackboards.Examples
{
    public partial class EventsBlackboard : BlackboardBehaviour
    {
        private int health = 100;

        private static bool _isAlive(Signals.Health health) => health.Value > 0;
    }
}

```

## EventsHealthBehaviour.cs

```csharp
﻿using CrashKonijn.RabbitBlackboard.Contracts;
using UnityEngine;
using EventsBlackboard = CrashKonijn.BlackboardPro.Blackboards.Examples.EventsBlackboard;

namespace CrashKonijn.BlackboardPro.Examples._03_events
{
    [RequireComponent(typeof(EventsBlackboard))]
    public class EventsHealthBehaviour : MonoBehaviour
    {
        /* Simple hack to show a button in the editor */
        [Header("Removes 10 health.")]
        [Button(nameof(DoDamage))]
        public string doDamageButton;

        /* Simple hack to show a button in the editor */
        [Header("Adds 10 health")]
        [Button(nameof(Heal))]
        public string healButton;

        private EventsBlackboard blackboard;

        public EventsBlackboard Blackboard
        {
            get
            {
                if (this.blackboard == null)
                {
                    this.blackboard = this.GetComponent<EventsBlackboard>();
                }

                return this.blackboard;
            }
        }

        public void OnEnable()
        {
            this.Blackboard.IsAliveSignal.OnValueChanged.AddListener(this.OnIsAliveChanged);
        }

        public void OnDisable()
        {
            this.Blackboard.IsAliveSignal.OnValueChanged.RemoveListener(this.OnIsAliveChanged);
        }

        public void DoDamage()
        {
            this.Blackboard.Health -= 10;
        }

        public void Heal()
        {
            this.Blackboard.Health += 10;
        }

        // This is set through the editor. Click on the 'Events' button in the blackboard editor to view it.
        public void OnHealthChanged(int health)
        {
            Debug.Log($"Health changed: {health}");
        }

        // This event is set through code. It is only bound to the blackboard during awake, so please enter play mode to see it in action.
        // Even though the IsAlive signal is re-calculated any time the Health signal changes, it's change event is only triggered when the value actually changes.
        public void OnIsAliveChanged(bool isAlive)
        {
            Debug.Log($"IsAlive changed: {isAlive}");
        }
    }
}

```


# 04 Saving & Loading

This example shows you how to save and load data in a blackboard.

These files can be found in the /Examples/\[example] and /Blackboards/Examples folders.

![Example 04 Saving editor](https://3907927415-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9IiEBezOtBtfBlKn7fXC%2Fuploads%2Fgit-blob-dceae6211412b327c59612525f321546044f76e7%2Fblackboard_examples_04.gif?alt=media)

## SavingBlackboard.cs

```csharp
﻿using CrashKonijn.RabbitBlackboard.Contracts;

namespace CrashKonijn.BlackboardPro.Blackboards.Examples
{
    public partial class SavingBlackboard : BlackboardBehaviour
    {
        // By default, all signals are saved and loaded.
        private int health = 100;

        // Sometimes you don't want values to be saved/loaded, you can use the [DoNotSave] attribute to prevent this.
        [DontSave]
        private int lowHealth = 40;

        // Computed props are never saved, they are recalculated on load.
        private static bool isLowHealth(Signals.Health health, Signals.LowHealth lowHealth) => health.Value <= lowHealth.Value;
    }
}

```

## SavingBehaviour.cs

```csharp
﻿using CrashKonijn.BlackboardPro.Blackboards.Examples;
using CrashKonijn.RabbitBlackboard.Contracts;
using UnityEngine;

namespace CrashKonijn.BlackboardPro.Examples._04_saving
{
    [RequireComponent(typeof(SavingBlackboard))]
    public class SavingBehaviour : MonoBehaviour
    {
        [TextArea]
        [SerializeField]
        private string json;

        /* Simple hack to show a button in the editor */
        [Header("Save the data to json")]
        [Button(nameof(SaveJson))]
        public string saveJsonButton;

        /* Simple hack to show a button in the editor */
        [Header("Load the data from json")]
        [Button(nameof(LoadJson))]
        public string loadJsonButton;

        [SerializeField]
        private SavingBlackboardData blackboardData;

        /* Simple hack to show a button in the editor */
        [Header("Save the data as data")]
        [Button(nameof(SaveData))]
        public string saveDataButton;

        /* Simple hack to show a button in the editor */
        [Header("Load the data from data")]
        [Button(nameof(LoadData))]
        public string loadDataButton;

        private SavingBlackboard blackboard;

        public SavingBlackboard Blackboard
        {
            get
            {
                if (this.blackboard == null)
                {
                    this.blackboard = this.GetComponent<SavingBlackboard>();
                }

                return this.blackboard;
            }
        }

        // This package provides a ToJson and FromJson method to save and load the blackboard.
        // Please be aware that this is a simple implementation and might not work for all cases such as GameObjects or interfaces.
        public void SaveJson()
        {
            this.json = this.Blackboard.ToJson();
        }

        public void LoadJson()
        {
            if (string.IsNullOrEmpty(this.json))
            {
                Debug.Log("Can't load empty json");
                return;
            }

            this.Blackboard.FromJson(this.json);
        }

        // This package provides a ToData and FromData method to save and load the blackboard.
        // The package will create a data class within the same namespace for you that you can use to save and load the blackboard.
        // The name of the data class is the name of the blackboard with the suffix Data.
        public void SaveData()
        {
            this.blackboardData = this.Blackboard.ToData();
        }

        public void LoadData()
        {
            this.Blackboard.FromData(this.blackboardData);
        }
    }
}

```


# 05 Lists

This example shows you how to use lists in a blackboard.

These files can be found in the /Examples/\[example] and /Blackboards/Examples folders.

![Example 05 Lists editor](https://3907927415-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9IiEBezOtBtfBlKn7fXC%2Fuploads%2Fgit-blob-f6554c71575bbbb2183ae3ce5a686e57833b0ed4%2Fblackboard_examples_05.gif?alt=media)

## ListBlackboard.cs

```csharp
using System.Collections.Generic;
using System.Linq;
using CrashKonijn.RabbitBlackboard.Contracts;

namespace CrashKonijn.BlackboardPro.Blackboards.Examples
{
    public partial class ListBlackboard : BlackboardBehaviour
    {
        private int startHealth = 100;

        // Lists can be of any type
        private List<int> changes;

        // This is a computed property, showing health being equal to startHealth plus the sum of all changes.
        private static int health(Signals.StartHealth startHealth, Signals.Changes changes) => startHealth.Value + changes.Value.Sum(x => x);
    }
}

```

## ListsHealthBehaviour.cs

```csharp
﻿using CrashKonijn.RabbitBlackboard.Contracts;
using UnityEngine;
using ListBlackboard = CrashKonijn.BlackboardPro.Blackboards.Examples.ListBlackboard;

namespace CrashKonijn.BlackboardPro.Examples._05_lists
{
    [RequireComponent(typeof(ListBlackboard))]
    public class ListsHealthBehaviour : MonoBehaviour
    {
        /* Simple hack to show a button in the editor */
        [Header("Removes 10 health.")]
        [Button(nameof(DoDamage))]
        public string doDamageButton;

        /* Simple hack to show a button in the editor */
        [Header("Adds 10 health")]
        [Button(nameof(Heal))]
        public string healButton;

        /* Simple hack to show a button in the editor */
        [Header("Clears the changes list")]
        [Button(nameof(Clear))]
        public string clearButton;

        private ListBlackboard blackboard;

        public ListBlackboard Blackboard
        {
            get
            {
                if (this.blackboard == null)
                {
                    this.blackboard = this.GetComponent<ListBlackboard>();
                }

                return this.blackboard;
            }
        }

        public void DoDamage()
        {
            this.Blackboard.Changes.Add(-10);
        }

        public void Heal()
        {
            this.Blackboard.Changes.Add(10);
        }

        public void Clear()
        {
            this.Blackboard.Changes.Clear();
        }
    }
}

```


# 06 Validations

This example shows you how to use validations in a signal.

These files can be found in the /Examples/\[example] and /Blackboards/Examples folders.

![Example 06 Validations editor](https://3907927415-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9IiEBezOtBtfBlKn7fXC%2Fuploads%2Fgit-blob-c64c9c0db7f5f829f16d95b500b1bc29229d8966%2Fblackboard_examples_06.gif?alt=media)

## ValidationsBlackboard.cs

```csharp
﻿using CrashKonijn.BlackboardPro.Blackboards.Examples.Attributes;
using CrashKonijn.RabbitBlackboard.Contracts;

namespace CrashKonijn.BlackboardPro.Blackboards.Examples
{
    public partial class ValidationsBlackboard : BlackboardBehaviour
    {
        // This value can never be less than 0
        // Note: that this is not the same attribute as the one Unity provides.
        [Min(0)]
        private int _bullets;

        // This is a custom validation that will capitalize the string
        // It can be viewed in ./Attributes/CapitalizeAttribute.cs
        [Capitalize]
        private string _name;

        // Validations can be stacked!
        // This value can never be less than 0 or greater than 150
        [Min(0)]
        [Max(150)]
        private int _health;

        // This is a computed signal that is normalized health between 0 and 1.
        // Even if health becomes higher than 100, this value will never be higher than 1.
        [Normalize(100f, true)]
        private static float _normalizedHealth(Signals.Health health) => health.Value;

        // This is a computed signal that is a curve between 0 and 1.
        [EaseInOutCurve(0f, 1f, 1f, 0f, 2f)]
        [MinMax(0f, 1f)]
        private static float _shouldHeal(Signals.NormalizedHealth health) => health.Value;
    }
}

```

## ValidationsHealthBehaviour.cs

```csharp
﻿using CrashKonijn.BlackboardPro.Blackboards.Examples;
using CrashKonijn.RabbitBlackboard.Contracts;
using UnityEngine;

namespace CrashKonijn.BlackboardPro.Examples._06_validations
{
    [RequireComponent(typeof(ValidationsBlackboard))]
    public class ValidationsHealthBehaviour : MonoBehaviour
    {
        /* Simple hack to show a button in the editor */
        [Header("Removes 10 health.")]
        [Button(nameof(DoDamage))]
        public string doDamageButton;

        /* Simple hack to show a button in the editor */
        [Header("Adds 10 health")]
        [Button(nameof(Heal))]
        public string healButton;

        /* Simple hack to show a button in the editor */
        [Header("Add 5 bullets.")]
        [Button(nameof(AddBullets))]
        public string addBulletsButton;

        /* Simple hack to show a button in the editor */
        [Header("Removed 5 bullets")]
        [Button(nameof(RemoveBullets))]
        public string removeBulletsButton;

        private ValidationsBlackboard blackboard;

        public ValidationsBlackboard Blackboard
        {
            get
            {
                if (this.blackboard == null)
                {
                    this.blackboard = this.GetComponent<ValidationsBlackboard>();
                }

                return this.blackboard;
            }
        }

        public void DoDamage()
        {
            this.Blackboard.Health -= 10;
        }

        public void Heal()
        {
            this.Blackboard.Health += 10;
        }

        public void AddBullets()
        {
            this.Blackboard.Bullets += 5;
        }

        public void RemoveBullets()
        {
            this.Blackboard.Bullets -= 5;
        }
    }
}

```


# 07 Nested Objects

This example shows you how to use nested objects in a blackboard.

These files can be found in the /Examples/\[example] and /Blackboards/Examples folders.

![Example 07 Nested Objects editor](https://3907927415-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9IiEBezOtBtfBlKn7fXC%2Fuploads%2Fgit-blob-3a2fcbfe2bd532c0711be41d1c80d6d413711059%2Fblackboard_examples_07.gif?alt=media)

## NestedObjectBlackboard.cs

```csharp
﻿using System;
using CrashKonijn.RabbitBlackboard.Contracts;
using UnityEngine;

namespace CrashKonijn.BlackboardPro.Blackboards.Examples
{
    public enum GunType
    {
        Pistol,
        Rifle,
        Shotgun,
    }

    // Make sure the class is partial and uses [Serializable] attribute
    // The IAllAreSignals interface is used to mark the class as a signal container
    // With IAllAreSignals all valid fields are signals
    [Serializable]
    public partial class Gun : IAllAreSignals
    {
        private GunType _type;
    }

    // Make sure the class is partial and uses [Serializable] attribute
    // The ISomeAreSignals interface is used to mark the class as a signal container
    // With ISomeAreSignals you must manually mark fields as signals
    [Serializable]
    public partial class Bullets : ISomeAreSignals
    {
        // This won't be generated as a signal
        // The serializefield makes sure it's saved in the editor
        [SerializeField]
        private int clipSize = 5;

        // You manually have to mark the field as a signal
        [Signal]
        [RabbitBlackboard.Contracts.Min(0)]
        private int inGun = 5;

        // This marks it as a computed signal
        [Signal]
        private static bool _isEmpty(Signals.InGun inGun) => inGun.Value <= 0;

        // Methods won't collide with generated code
        public void Shoot()
        {
            // Make sure you use the property instead of the field
            // Updating the field won't do anything
            this.InGun--;
        }

        public void Reload()
        {
            this.InGun = this.clipSize;
        }
    }

    public partial class NestedObjectBlackboard : BlackboardBehaviour
    {
        // This is a nested object.
        private Bullets _bullets;

        // This is a nested object
        private Gun _gun;
    }
}

```

## NestedObjectBehaviour.cs

```csharp
﻿using CrashKonijn.BlackboardPro.Blackboards.Examples;
using CrashKonijn.RabbitBlackboard.Contracts;
using UnityEngine;

namespace CrashKonijn.BlackboardPro.Examples._07_nested_objects
{
    [RequireComponent(typeof(NestedObjectBlackboard))]
    public class NestedObjectBehaviour : MonoBehaviour
    {
        /* Simple hack to show a button in the editor */
        [Header("Shoots a bullet.")]
        [Button(nameof(Shoot))]
        public string shootButton;

        /* Simple hack to show a button in the editor */
        [Header("Reloads the gun")]
        [Button(nameof(Reload))]
        public string reloadButton;

        private NestedObjectBlackboard blackboard;

        public NestedObjectBlackboard Blackboard
        {
            get
            {
                if (this.blackboard == null)
                {
                    this.blackboard = this.GetComponent<NestedObjectBlackboard>();
                }

                return this.blackboard;
            }
        }

        private void OnEnable()
        {
            this.Blackboard.Bullets.InGunSignal.OnValueChanged.AddListener(this.OnBulletsChange);
        }

        private void OnDisable()
        {
            this.Blackboard.Bullets.InGunSignal.OnValueChanged.RemoveListener(this.OnBulletsChange);
        }

        private void OnBulletsChange(int bullets)
        {
            Debug.Log($"Bullets changed: {bullets}");
        }

        public void Shoot()
        {
            this.Blackboard.Bullets.Shoot();
        }

        public void Reload()
        {
            this.Blackboard.Bullets.Reload();
        }
    }
}

```


# 08 Unity Attributes

This example shows you how to use unity attributes in a blackboard.

These files can be found in the /Examples/\[example] and /Blackboards/Examples folders.

## UnityAttributeBlackboard.cs

```csharp
﻿using CrashKonijn.RabbitBlackboard.Contracts;
using UnityEngine;

namespace CrashKonijn.BlackboardPro.Blackboards.Examples
{
    public partial class UnityAttributeBlackboard : BlackboardBehaviour
    {
        // You can use unity attributes to show extra information in the editor
        [Header("Basic Info")]
        private string playerName;

        // The blackboard is rendered in the following order:
        // 1. Fields
        // 2. Lists
        // 3. Computed properties
        // This means that this header and field be lower in the blackboard
        [Header("Arrays/Lists")]
        private int[] someArray;

        [Space]
        private string spacedValue;

        [HideInInspector]
        private float hiddenValue;

        // These attributes won't work
        [Tooltip("The player's health points.")]
        [Range(0, 100)]
        private int health;
    }
}

```


# 09 Object Lists

This example shows you how to use object lists in a blackboard.

These files can be found in the /Examples/\[example] and /Blackboards/Examples folders.

![Example 09 Object Lists editor](https://3907927415-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9IiEBezOtBtfBlKn7fXC%2Fuploads%2Fgit-blob-337694beb9ef3c308f1625944beb09a390af1845%2Fblackboard_examples_09.gif?alt=media)

## ObjectListBlackboard.cs

```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using CrashKonijn.RabbitBlackboard.Contracts;

namespace CrashKonijn.BlackboardPro.Blackboards.Examples
{
    [Serializable]
    public class SerializedChange
    {
        public int amount;
    }

    [Serializable]
    public partial class SignalChange : ISomeAreSignals
    {
        [Signal]
        private int amount;

        [Signal]
        private string name;
    }

    public partial class ObjectListBlackboard : BlackboardBehaviour
    {
        private int startHealth = 100;

        // Lists can be of any type
        private List<SerializedChange> serializedChanges;
        private List<SignalChange> signalChanges;

        // This is a computed property, showing health being equal to startHealth plus the sum of all changes.
        private static int serializedHealth(Signals.StartHealth startHealth, Signals.SerializedChanges changes) => startHealth.Value + changes.Value.Sum(x => x.amount);

        // WARNING: even though the type here has signals on its own. Change events are triggered by the VALUE of the signal.
        // This means that even when changing the amount of a SignalChange, the containing list won't trigger an event.
        private static int signalHealth(Signals.StartHealth startHealth, Signals.SignalChanges changes) => startHealth.Value + changes.Value.Sum(x => x.Amount);
    }
}

```

## ObjectListsHealthBehaviour.cs

```csharp
﻿using CrashKonijn.BlackboardPro.Blackboards.Examples;
using CrashKonijn.RabbitBlackboard.Contracts;
using UnityEngine;
using ObjectListBlackboard = CrashKonijn.BlackboardPro.Blackboards.Examples.ObjectListBlackboard;
using SignalChange = CrashKonijn.BlackboardPro.Blackboards.Examples.SignalChange;

namespace CrashKonijn.BlackboardPro.Examples._09_object_lists
{
    [RequireComponent(typeof(ObjectListBlackboard))]
    public class ObjectListsHealthBehaviour : MonoBehaviour
    {
        /* Simple hack to show a button in the editor */
        [Header("Removes 10 health.")]
        [Button(nameof(DoDamage))]
        public string doDamageButton;

        /* Simple hack to show a button in the editor */
        [Header("Adds 10 health")]
        [Button(nameof(Heal))]
        public string healButton;

        /* Simple hack to show a button in the editor */
        [Header("Clears the changes list")]
        [Button(nameof(Clear))]
        public string clearButton;

        private ObjectListBlackboard blackboard;

        public ObjectListBlackboard Blackboard
        {
            get
            {
                if (this.blackboard == null)
                {
                    this.blackboard = this.GetComponent<ObjectListBlackboard>();
                }

                return this.blackboard;
            }
        }

        public void DoDamage()
        {
            this.Blackboard.SignalChanges.Add(new SignalChange
            {
                Amount = -10,
            });
            this.Blackboard.SerializedChanges.Add(new SerializedChange
            {
                amount = -10,
            });
        }

        public void Heal()
        {
            this.Blackboard.SignalChanges.Add(new SignalChange
            {
                Amount = 10,
            });
            this.Blackboard.SerializedChanges.Add(new SerializedChange
            {
                amount = 10,
            });
        }

        public void Clear()
        {
            this.Blackboard.SignalChanges.Clear();
        }
    }
}

```


# 10 Dictionaries

This example shows you how to use dictionaries in a blackboard.

These files can be found in the /Examples/\[example] and /Blackboards/Examples folders.

![Example 10 Dictionaries editor](https://3907927415-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F9IiEBezOtBtfBlKn7fXC%2Fuploads%2Fgit-blob-fc489e4b473510868cbcc2417e0e27a5d51115f2%2Fblackboard_examples_10.gif?alt=media)

## DictionaryBlackboard.cs

```csharp
﻿using System;
using System.Collections.Generic;
using System.Linq;
using CrashKonijn.RabbitBlackboard.Contracts;

namespace CrashKonijn.BlackboardPro.Blackboards.Examples
{
    [Serializable]
    public class DictionaryObj
    {
        private string name;
    }

    [Serializable]
    public partial class DictSignalObj : IAllAreSignals
    {
        
    }
    
    public partial class DictionaryBlackboard : BlackboardBehaviour
    {
        // Unity doesn't support serializing dictionaries!
        // Therefore, you won't be able to see its values in the inspector.
        // Changing and updating it's values trough code will work!
        private Dictionary<string, int> scores = new Dictionary<string, int>();
        private Dictionary<string, DictionaryObj> scoresNested = new Dictionary<string, DictionaryObj>();
        private Dictionary<string, DictSignalObj> scoresNestedSignalObj = new Dictionary<string, DictSignalObj>();
        
        // This computed method will show the values in the editor as a computed signal value.
        // Don't use this in production code, it's only for demo purposes.
        private static string _scoresLog(Signals.Scores scores) => string.Join(",\n", scores.Value.Select(x => $"{x.Key}: {x.Value}"));
    }
}
```

## DictionariesBehaviour.cs

```csharp
﻿using System.Linq;
using CrashKonijn.BlackboardPro.Blackboards.Examples;
using CrashKonijn.RabbitBlackboard.Contracts;
using UnityEngine;

namespace CrashKonijn.BlackboardPro.Examples._10_dictionaries
{
    [RequireComponent(typeof(DictionaryBlackboard))]
    public class DictionariesBehaviour : MonoBehaviour
    {
        private int _index;

        /* Simple hack to show a button in the editor */
        [Header("Adds an item to the dictionaries")]
        [Button(nameof(Add))]
        public string addButton;

        /* Simple hack to show a button in the editor */
        [Header("Removes the last item from the dictionaries")]
        [Button(nameof(Remove))]
        public string removeButton;

        /* Simple hack to show a button in the editor */
        [Header("Removes all items from the dictionaries")]
        [Button(nameof(Clear))]
        public string clearButton;

        private DictionaryBlackboard blackboard;

        public DictionaryBlackboard Blackboard
        {
            get
            {
                if (this.blackboard == null)
                {
                    this.blackboard = this.GetComponent<DictionaryBlackboard>();
                }

                return this.blackboard;
            }
        }

        public void Add()
        {
            this._index++;

            this.Blackboard.Scores.Add($"test{this._index}", this._index);
            this.Blackboard.ScoresNested.Add($"test{this._index}", new DictionaryObj());
            this.Blackboard.ScoresNestedSignalObj.Add($"test{this._index}", new DictSignalObj());
        }

        public void Remove()
        {
            if (this.Blackboard.Scores.Any())
                this.Blackboard.Scores.Remove(this.Blackboard.Scores.Keys.Last());

            if (this.Blackboard.ScoresNested.Any())
                this.Blackboard.ScoresNested.Remove(this.Blackboard.ScoresNested.Keys.Last());

            if (this.Blackboard.ScoresNestedSignalObj.Any())
                this.Blackboard.ScoresNestedSignalObj.Remove(this.Blackboard.ScoresNestedSignalObj.Keys.Last());
        }

        public void Clear()
        {
            this._index = 0;

            this.Blackboard.Scores.Clear();
            this.Blackboard.ScoresNested.Clear();
            this.Blackboard.ScoresNestedSignalObj.Clear();
        }
    }
}

```


# 11 Scriptable Object Blackboard

This example shows you how to use a scriptable object Blackboard.

These files can be found in the /Examples/\[example] and /Blackboards/Examples folders.

## ScriptableObjectBlackboard.cs

```csharp
﻿using CrashKonijn.RabbitBlackboard.Contracts;
using UnityEngine;

namespace CrashKonijn.BlackboardPro.Blackboards.Examples
{
    [CreateAssetMenu(menuName = "Blackboard Pro/Examples/ScriptableObjectBlackboard")]
    public partial class ScriptableObjectBlackboard : BlackboardScriptable
    {
        private int startPlayerHealth = 100;
        private static int lowHealth(Signals.StartPlayerHealth startPlayerHealth) => (int) (startPlayerHealth.Value * 0.4f);
    }
}

```

## ScriptableBehaviour.cs

```csharp
﻿using System;
using CrashKonijn.BlackboardPro.Blackboards.Examples;
using UnityEngine;

namespace CrashKonijn.BlackboardPro.Examples._11_scriptable_object
{
    public class ScriptableBehaviour : MonoBehaviour
    {
        [Header("Make sure to view the ScriptableBlackboard\nin the example folder!\n\nThis behaviour binds events on play")]
        [SerializeField]
        private ScriptableObjectBlackboard blackboard;
        
        // You can still register events to the signals!
        private void OnEnable()
        {
            blackboard.LowHealthSignal.OnValueChanged.AddListener(OnLowHealthChanged);
            blackboard.StartPlayerHealthSignal.OnValueChanged.AddListener(OnStartPlayerHealthChanged);
        }

        private void OnDisable()
        {
            blackboard.LowHealthSignal.OnValueChanged.RemoveListener(OnLowHealthChanged);
            blackboard.StartPlayerHealthSignal.OnValueChanged.RemoveListener(OnStartPlayerHealthChanged);
        }
        
        private void OnLowHealthChanged(int value)
        {
            Debug.Log($"Low health changed to {value}");
        }

        private void OnStartPlayerHealthChanged(int value)
        {
            Debug.Log($"Start player health changed to {value}");
        }
    }
}
```


# 12 Class Blackboard

This example shows you how to use a class Blackboard.

These files can be found in the /Examples/\[example] and /Blackboards/Examples folders.

## ClassBlackboard.cs

```csharp
﻿using System;
using CrashKonijn.RabbitBlackboard.Contracts;

namespace CrashKonijn.BlackboardPro.Blackboards.Examples
{
    // Make sure the class is serializable so it can show in the editor
    [Serializable]
    public partial class ClassBlackboard : IBlackboard
    {
        private int health = 50;
        private static bool isLowHealth(Signals.Health health) => health.Value < 50;
    }
}

```

## ClassBehaviour.cs

```csharp
﻿using CrashKonijn.BlackboardPro.Blackboards.Examples;
using CrashKonijn.RabbitBlackboard.Contracts;
using UnityEngine;

namespace CrashKonijn.BlackboardPro.Examples._12_blackboard_class
{
    public class ClassBehaviour : MonoBehaviour
    {
        // You can manage your own instance of classes that implement IBlackboard.
        [SerializeField]
        private ClassBlackboard blackboard = new();

        /* Simple hack to show a button in the editor */
        [Header("Removes 10 health.")]
        [Button(nameof(DoDamage))]
        public string doDamageButton;

        /* Simple hack to show a button in the editor */
        [Header("Adds 10 health")]
        [Button(nameof(Heal))]
        public string healButton;

        private void OnEnable()
        {
            this.blackboard.IsLowHealthSignal.OnValueChanged.AddListener(this.OnLowHealthChanged);
            this.blackboard.HealthSignal.OnValueChanged.AddListener(this.OnStartPlayerHealthChanged);
        }

        private void OnDisable()
        {
            this.blackboard.IsLowHealthSignal.OnValueChanged.RemoveListener(this.OnLowHealthChanged);
            this.blackboard.HealthSignal.OnValueChanged.RemoveListener(this.OnStartPlayerHealthChanged);
        }

        private void OnLowHealthChanged(bool value)
        {
            Debug.Log($"Is low health changed to {value}");
        }

        private void OnStartPlayerHealthChanged(int value)
        {
            Debug.Log($"Start player health changed to {value}");
        }

        public void Heal()
        {
            this.blackboard.Health += 10;
        }

        public void DoDamage()
        {
            this.blackboard.Health -= 10;
        }
    }
}

```


# 13 Blackboard References

This example shows you how to reference another blackboard from a blackboard.

These files can be found in the /Examples/\[example] and /Blackboards/Examples folders.

## SourceBlackboard.cs

```csharp
﻿using CrashKonijn.RabbitBlackboard.Contracts;

namespace CrashKonijn.BlackboardPro.Blackboards.Examples
{
    public partial class SourceBlackboard : BlackboardBehaviour
    {
        private int lowHealth = 50;
    }
}

```

## ReferenceBlackboard.cs

```csharp
﻿using CrashKonijn.RabbitBlackboard.Contracts;

namespace CrashKonijn.BlackboardPro.Blackboards.Examples
{
    public partial class ReferenceBlackboard : BlackboardBehaviour
    {
        private SourceBlackboard _sourceBlackboard;

        private int health;

        private static bool isLowHealth(SourceBlackboard.Signals.LowHealth lowHealth, Signals.Health health) => health.Value < lowHealth.Value;
    }
}

```

## DoubleReferenceBlackboard.cs

```csharp
﻿using CrashKonijn.RabbitBlackboard.Contracts;

namespace CrashKonijn.BlackboardPro.Blackboards.Examples
{
    public partial class DoubleReferenceBlackboard : BlackboardBehaviour
    {
        private ReferenceBlackboard referenceBlackboard;

        private static bool isAlive(ReferenceBlackboard.Signals.Health health) => health.Value > 0;
        private static bool isHighHealth(ReferenceBlackboard.Signals.Health health, SourceBlackboard.Signals.LowHealth lowHealth) => health.Value > lowHealth.Value;
    }
}

```


