Skip to content

Commit

Permalink
Merge pull request #20 from limbonaut/blackboard-improvements
Browse files Browse the repository at this point in the history
Rework `Blackboard` API and introduce `BlackboardPlan` resource and editor.

- `BBVariable` object holds the value of a blackboard variable and its metadata (not exposed to the API).
- `BlackboardPlan` resource stores and manages a collection of variables, and is used to construct new `Blackboard` instances.
  - Each `BehaviorTree` resource has its own `BlackboardPlan` resource that acts as a blueprint.
  - `BTPlayer` also has its own `BlackboardPlan` which extends the behavior tree `BlackboardPlan` resource, i.e. variables from BehaviorTree are overridden in the BTPlayer node.
- Editor for the `BlackboardPlan` resources
  - Accessed with "Manage" button in the inspector.
  - Rename, reposition, and change type and hint of the blackboard variables.
  - Edit default variable values directly in the inspector.
- Define variables in the `BehaviorTree` blackboard plan and override those variables in the `BTPlayer`'s plan.
- Fully compatible with both module and GDExtension builds!
  • Loading branch information
limbonaut authored Jan 26, 2024
2 parents 33b455f + f912f0a commit 662738e
Show file tree
Hide file tree
Showing 51 changed files with 1,905 additions and 172 deletions.
153 changes: 153 additions & 0 deletions blackboard/bb_variable.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/**
* bb_variable.cpp
* =============================================================================
* Copyright 2021-2024 Serhii Snitsaruk
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
* =============================================================================
*/

#include "bb_variable.h"

#include "../util/limbo_compat.h"

void BBVariable::unref() {
if (data && data->refcount.unref()) {
memdelete(data);
}
data = nullptr;
}

// void BBVariable::init_ref() {
// if (data) {
// unref();
// }
// data = memnew(Data);
// data->refcount.init();
// }

void BBVariable::set_value(const Variant &p_value) {
data->value = p_value;
}

Variant BBVariable::get_value() const {
return data->value;
}

void BBVariable::set_type(Variant::Type p_type) {
data->type = p_type;
data->value = VARIANT_DEFAULT(p_type);
}

Variant::Type BBVariable::get_type() const {
return data->type;
}

void BBVariable::set_hint(PropertyHint p_hint) {
data->hint = p_hint;
}

PropertyHint BBVariable::get_hint() const {
return data->hint;
}

void BBVariable::set_hint_string(const String &p_hint_string) {
data->hint_string = p_hint_string;
}

String BBVariable::get_hint_string() const {
return data->hint_string;
}

BBVariable BBVariable::duplicate() const {
BBVariable var;
var.data->hint = data->hint;
var.data->hint_string = data->hint_string;
var.data->type = data->type;
var.data->value = data->value;
return var;
}

bool BBVariable::is_same_prop_info(const BBVariable &p_other) const {
if (data->type != p_other.data->type) {
return false;
}
if (data->hint != p_other.data->hint) {
return false;
}
if (data->hint_string != p_other.data->hint_string) {
return false;
}
return true;
}

void BBVariable::copy_prop_info(const BBVariable &p_other) {
data->type = p_other.data->type;
data->hint = p_other.data->hint;
data->hint_string = p_other.data->hint_string;
}

bool BBVariable::operator==(const BBVariable &p_var) const {
if (data == p_var.data) {
return true;
}

if (!data || !p_var.data) {
return false;
}

if (data->type != p_var.data->type) {
return false;
}

if (data->hint != p_var.data->hint) {
return false;
}

if (data->value != p_var.data->value) {
return false;
}

if (data->hint_string != p_var.data->hint_string) {
return false;
}

return true;
}

bool BBVariable::operator!=(const BBVariable &p_var) const {
return !(*this == p_var);
}

void BBVariable::operator=(const BBVariable &p_var) {
if (this == &p_var) {
return;
}

unref();

if (p_var.data && p_var.data->refcount.ref()) {
data = p_var.data;
}
}

BBVariable::BBVariable(const BBVariable &p_var) {
if (p_var.data && p_var.data->refcount.ref()) {
data = p_var.data;
}
}

BBVariable::BBVariable(Variant::Type p_type, PropertyHint p_hint, const String &p_hint_string) {
data = memnew(Data);
data->refcount.init();

set_type(p_type);
data->hint = p_hint;
data->hint_string = p_hint_string;
}

BBVariable::~BBVariable() {
unref();
}
77 changes: 77 additions & 0 deletions blackboard/bb_variable.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* bb_variable.h
* =============================================================================
* Copyright 2021-2024 Serhii Snitsaruk
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
* =============================================================================
*/

#ifndef BB_VARIABLE_H
#define BB_VARIABLE_H

#ifdef LIMBOAI_MODULE
#include "core/object/object.h"
#include "core/templates/safe_refcount.h"
#include "core/variant/variant.h"
#endif // LIMBOAI_MODULE

#ifdef LIMBOAI_GDEXTENSION
#include "godot_cpp/core/defs.hpp"
#include "godot_cpp/templates/safe_refcount.hpp"
#include "godot_cpp/variant/variant.hpp"
using namespace godot;
#endif // LIMBOAI_GDEXTENSION

class BBVariable {
private:
struct Data {
SafeRefCount refcount;
Variant value;
Variant::Type type = Variant::NIL;
PropertyHint hint = PropertyHint::PROPERTY_HINT_NONE;
String hint_string;
// bool bound = false;
// uint64_t bound_object = 0;
// StringName bound_property;
};

Data *data = nullptr;
void unref();
// void init_ref();

public:
void set_value(const Variant &p_value);
Variant get_value() const;

void set_type(Variant::Type p_type);
Variant::Type get_type() const;

void set_hint(PropertyHint p_hint);
PropertyHint get_hint() const;

void set_hint_string(const String &p_hint_string);
String get_hint_string() const;

BBVariable duplicate() const;

bool is_same_prop_info(const BBVariable &p_other) const;
void copy_prop_info(const BBVariable &p_other);

// bool is_bound() { return bound; }

// void bind(Node *p_root, NodePath p_path);
// void unbind();

bool operator==(const BBVariable &p_var) const;
bool operator!=(const BBVariable &p_var) const;
void operator=(const BBVariable &p_var);

BBVariable(const BBVariable &p_var);
BBVariable(Variant::Type p_type = Variant::Type::NIL, PropertyHint p_hint = PROPERTY_HINT_NONE, const String &p_hint_string = "");
~BBVariable();
};

#endif // BB_VARIABLE_H
62 changes: 35 additions & 27 deletions blackboard/blackboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,63 +22,71 @@
#include <godot_cpp/classes/ref.hpp>
#include <godot_cpp/classes/ref_counted.hpp>
#include <godot_cpp/core/object.hpp>
#include <godot_cpp/variant/dictionary.hpp>
using namespace godot;
#endif

Ref<Blackboard> Blackboard::top() const {
Ref<Blackboard> bb(this);
while (bb->get_parent_scope().is_valid()) {
bb = bb->get_parent_scope();
while (bb->get_parent().is_valid()) {
bb = bb->get_parent();
}
return bb;
}

Variant Blackboard::get_var(const Variant &p_key, const Variant &p_default) const {
if (data.has(p_key)) {
return data.get(p_key, Variant());
Variant Blackboard::get_var(const String &p_name, const Variant &p_default) const {
if (data.has(p_name)) {
return data.get(p_name).get_value();
} else if (parent.is_valid()) {
return parent->get_var(p_key, p_default);
return parent->get_var(p_name, p_default);
} else {
return p_default;
}
}

void Blackboard::set_var(const Variant &p_key, const Variant &p_value) {
data[p_key] = p_value;
void Blackboard::set_var(const String &p_name, const Variant &p_value) {
if (data.has(p_name)) {
// Not checking type - allowing duck-typing.
data[p_name].set_value(p_value);
} else {
BBVariable var(p_value.get_type());
var.set_value(p_value);
data.insert(p_name, var);
}
}

bool Blackboard::has_var(const String &p_name) const {
return data.has(p_name) || (parent.is_valid() && parent->has_var(p_name));
}

bool Blackboard::has_var(const Variant &p_key) const {
return data.has(p_key) || (parent.is_valid() && parent->has_var(p_key));
void Blackboard::erase_var(const String &p_name) {
data.erase(p_name);
}

void Blackboard::erase_var(const Variant &p_key) {
data.erase(p_key);
void Blackboard::add_var(const String &p_name, const BBVariable &p_var) {
ERR_FAIL_COND(data.has(p_name));
data.insert(p_name, p_var);
}

void Blackboard::prefetch_nodepath_vars(Node *p_node) {
ERR_FAIL_COND(p_node == nullptr);
Array keys = data.keys();
Array values = data.values();
for (int i = 0; i < keys.size(); i++) {
if (values[i].get_type() == Variant::NODE_PATH) {
Node *fetched_node = p_node->get_node_or_null(values[i]);
for (const KeyValue<String, BBVariable> &kv : data) {
BBVariable var = kv.value;
if (var.get_value().get_type() == Variant::NODE_PATH) {
Node *fetched_node = p_node->get_node_or_null(var.get_value());
if (fetched_node != nullptr) {
data[keys[i]] = fetched_node;
var.set_value(fetched_node);
}
}
}
}

void Blackboard::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_data"), &Blackboard::get_data);
ClassDB::bind_method(D_METHOD("set_data", "p_data"), &Blackboard::set_data);
ClassDB::bind_method(D_METHOD("get_var", "p_key", "p_default"), &Blackboard::get_var, Variant());
ClassDB::bind_method(D_METHOD("set_var", "p_key", "p_value"), &Blackboard::set_var);
ClassDB::bind_method(D_METHOD("has_var", "p_key"), &Blackboard::has_var);
ClassDB::bind_method(D_METHOD("set_parent_scope", "p_blackboard"), &Blackboard::set_parent_scope);
ClassDB::bind_method(D_METHOD("get_parent_scope"), &Blackboard::get_parent_scope);
ClassDB::bind_method(D_METHOD("erase_var", "p_key"), &Blackboard::erase_var);
ClassDB::bind_method(D_METHOD("get_var", "p_name", "p_default"), &Blackboard::get_var, Variant());
ClassDB::bind_method(D_METHOD("set_var", "p_name", "p_value"), &Blackboard::set_var);
ClassDB::bind_method(D_METHOD("has_var", "p_name"), &Blackboard::has_var);
ClassDB::bind_method(D_METHOD("set_parent_scope", "p_blackboard"), &Blackboard::set_parent);
ClassDB::bind_method(D_METHOD("get_parent_scope"), &Blackboard::get_parent);
ClassDB::bind_method(D_METHOD("erase_var", "p_name"), &Blackboard::erase_var);
ClassDB::bind_method(D_METHOD("prefetch_nodepath_vars", "p_node"), &Blackboard::prefetch_nodepath_vars);
ClassDB::bind_method(D_METHOD("top"), &Blackboard::top);
}
28 changes: 15 additions & 13 deletions blackboard/blackboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
#ifndef BLACKBOARD_H
#define BLACKBOARD_H

#include "bb_variable.h"

#ifdef LIMBOAI_MODULE
#include "core/object/object.h"
#include "core/object/ref_counted.h"
#include "core/variant/dictionary.h"
#include "core/variant/variant.h"
#include "scene/main/node.h"
#endif // LIMBOAI_MODULE
Expand All @@ -25,35 +26,36 @@
#include <godot_cpp/classes/ref.hpp>
#include <godot_cpp/classes/ref_counted.hpp>
#include <godot_cpp/core/object.hpp>
#include <godot_cpp/variant/dictionary.hpp>
#include <godot_cpp/templates/hash_map.hpp>
using namespace godot;
#endif // LIMBOAI_GDEXTENSION

class Blackboard : public RefCounted {
GDCLASS(Blackboard, RefCounted);

private:
Dictionary data;
HashMap<String, BBVariable> data;
Ref<Blackboard> parent;

protected:
static void _bind_methods();

public:
void set_data(const Dictionary &p_value) { data = p_value; }
Dictionary get_data() const { return data; }

void set_parent_scope(const Ref<Blackboard> &p_blackboard) { parent = p_blackboard; }
Ref<Blackboard> get_parent_scope() const { return parent; }
void set_parent(const Ref<Blackboard> &p_blackboard) { parent = p_blackboard; }
Ref<Blackboard> get_parent() const { return parent; }

Ref<Blackboard> top() const;

Variant get_var(const Variant &p_key, const Variant &p_default) const;
void set_var(const Variant &p_key, const Variant &p_value);
bool has_var(const Variant &p_key) const;
void erase_var(const Variant &p_key);
Variant get_var(const String &p_name, const Variant &p_default) const;
void set_var(const String &p_name, const Variant &p_value);
bool has_var(const String &p_name) const;
void erase_var(const String &p_name);

void add_var(const String &p_name, const BBVariable &p_var);

void prefetch_nodepath_vars(Node *p_node);

// TODO: Add serialization API.
};

#endif // BLACKBOARD_H
#endif // BLACKBOARD_H
Loading

0 comments on commit 662738e

Please sign in to comment.