Compare commits

...

2 Commits

9 changed files with 613 additions and 513 deletions

View File

@ -27,7 +27,7 @@ macro(compile_options target_name)
sanitizers(${target_name}) sanitizers(${target_name})
endmacro() endmacro()
project(blt-gp VERSION 0.3.8) project(blt-gp VERSION 0.3.9)
include(CTest) include(CTest)
@ -107,17 +107,17 @@ endif ()
if (${BUILD_GP_TESTS}) if (${BUILD_GP_TESTS})
blt_add_project(blt-stack tests/old/stack_tests.cpp test) # blt_add_project(blt-stack tests/old/stack_tests.cpp test)
blt_add_project(blt-eval tests/old/evaluation_tests.cpp test) # blt_add_project(blt-eval tests/old/evaluation_tests.cpp test)
blt_add_project(blt-order tests/old/order_tests.cpp test) # blt_add_project(blt-order tests/old/order_tests.cpp test)
#blt_add_project(blt-destructor tests/destructor_test.cpp test) #blt_add_project(blt-destructor tests/destructor_test.cpp test)
blt_add_project(blt-gp1 tests/old/gp_test_1.cpp test) # blt_add_project(blt-gp1 tests/old/gp_test_1.cpp test)
blt_add_project(blt-gp2 tests/old/gp_test_2.cpp test) # blt_add_project(blt-gp2 tests/old/gp_test_2.cpp test)
blt_add_project(blt-gp3 tests/old/gp_test_3.cpp test) # blt_add_project(blt-gp3 tests/old/gp_test_3.cpp test)
blt_add_project(blt-gp4 tests/old/gp_test_4.cpp test) # blt_add_project(blt-gp4 tests/old/gp_test_4.cpp test)
blt_add_project(blt-gp5 tests/old/gp_test_5.cpp test) # blt_add_project(blt-gp5 tests/old/gp_test_5.cpp test)
blt_add_project(blt-gp6 tests/old/gp_test_6.cpp test) # blt_add_project(blt-gp6 tests/old/gp_test_6.cpp test)
blt_add_project(blt-gp7 tests/old/gp_test_7.cpp test) # blt_add_project(blt-gp7 tests/old/gp_test_7.cpp test)
blt_add_project(blt-symbolic-regression tests/symbolic_regression_test.cpp test) blt_add_project(blt-symbolic-regression tests/symbolic_regression_test.cpp test)

View File

@ -22,53 +22,13 @@
#include <blt/std/types.h> #include <blt/std/types.h>
#include <blt/gp/typesystem.h> #include <blt/gp/typesystem.h>
#include <blt/gp/stack.h> #include <blt/gp/stack.h>
#include <blt/gp/util/meta.h>
#include <functional> #include <functional>
#include <type_traits> #include <type_traits>
#include <optional> #include <optional>
namespace blt::gp namespace blt::gp
{ {
namespace detail
{
template<typename T>
using remove_cv_ref = std::remove_cv_t<std::remove_reference_t<T>>;
template<typename...>
struct first_arg;
template<typename First, typename... Args>
struct first_arg<First, Args...>
{
using type = First;
};
template<>
struct first_arg<>
{
using type = void;
};
template<bool b, typename... types>
struct is_same;
template<typename... types>
struct is_same<true, types...> : public std::false_type
{
};
template<typename... types>
struct is_same<false, types...> : public std::is_same<types...>
{
};
template<typename... types>
constexpr bool is_same_v = is_same<sizeof...(types) == 0, types...>::value;
struct empty_t
{
};
}
template<typename Return, typename... Args> template<typename Return, typename... Args>
struct call_with struct call_with
@ -102,7 +62,7 @@ namespace blt::gp
} }
template<typename context = void, typename... NoCtxArgs> template<typename context = void, typename... NoCtxArgs>
void call_destructors_without_first(stack_allocator& read_allocator) void call_destructors_without_first(stack_allocator& read_allocator) const
{ {
if constexpr (sizeof...(NoCtxArgs) > 0) if constexpr (sizeof...(NoCtxArgs) > 0)
{ {
@ -111,9 +71,9 @@ namespace blt::gp
} }
template<typename Func, typename... ExtraArgs> template<typename Func, typename... ExtraArgs>
Return operator()(bool has_context, Func&& func, stack_allocator& read_allocator, ExtraArgs&& ... args) Return operator()(const bool has_context, Func&& func, stack_allocator& read_allocator, ExtraArgs&& ... args)
{ {
constexpr auto seq = std::make_integer_sequence<blt::u64, sizeof...(Args)>(); constexpr auto seq = std::make_integer_sequence<u64, sizeof...(Args)>();
#if BLT_DEBUG_LEVEL > 0 #if BLT_DEBUG_LEVEL > 0
try try
{ {
@ -213,17 +173,22 @@ namespace blt::gp
return func; return func;
} }
inline auto set_ephemeral() auto set_ephemeral()
{ {
is_ephemeral_ = true; is_ephemeral_ = true;
return *this; return *this;
} }
inline bool is_ephemeral() bool is_ephemeral() const
{ {
return is_ephemeral_; return is_ephemeral_;
} }
bool return_has_drop() const
{
return detail::has_func_drop_v<Return>;
}
operator_id id = -1; operator_id id = -1;
private: private:
function_t func; function_t func;

View File

@ -57,6 +57,29 @@
namespace blt::gp namespace blt::gp
{ {
struct operator_special_flags
{
explicit operator_special_flags(const bool is_ephemeral = false, const bool has_drop = false): m_ephemeral(is_ephemeral), m_drop(has_drop)
{
}
[[nodiscard]] bool is_ephemeral() const
{
return m_ephemeral;
}
[[nodiscard]] bool has_drop() const
{
return m_drop;
}
private:
bool m_ephemeral : 1;
bool m_drop : 1;
};
static_assert(sizeof(operator_special_flags) == 1, "Size of operator flags struct is expected to be 1 byte!");
struct argc_t struct argc_t
{ {
blt::u32 argc = 0; blt::u32 argc = 0;
@ -90,11 +113,12 @@ namespace blt::gp
struct program_operator_storage_t struct program_operator_storage_t
{ {
// indexed from return TYPE ID, returns index of operator // indexed from return TYPE ID, returns index of operator
blt::expanding_buffer<tracked_vector<operator_id>> terminals; expanding_buffer<tracked_vector<operator_id>> terminals;
blt::expanding_buffer<tracked_vector<operator_id>> non_terminals; expanding_buffer<tracked_vector<operator_id>> non_terminals;
blt::expanding_buffer<tracked_vector<std::pair<operator_id, blt::size_t>>> operators_ordered_terminals; expanding_buffer<tracked_vector<std::pair<operator_id, size_t>>> operators_ordered_terminals;
// indexed from OPERATOR ID (operator number) // indexed from OPERATOR ID (operator number) to a bitfield of flags
blt::hashset_t<operator_id> ephemeral_leaf_operators; hashmap_t<operator_id, operator_special_flags> operator_flags;
tracked_vector<operator_info_t> operators; tracked_vector<operator_info_t> operators;
tracked_vector<operator_metadata_t> operator_metadata; tracked_vector<operator_metadata_t> operator_metadata;
tracked_vector<detail::print_func_t> print_funcs; tracked_vector<detail::print_func_t> print_funcs;
@ -128,33 +152,9 @@ namespace blt::gp
largest_returns)), ...); largest_returns)), ...);
// largest = largest * largest_argc; // largest = largest * largest_argc;
blt::size_t largest = largest_args * largest_argc * largest_returns * largest_argc; size_t largest = largest_args * largest_argc * largest_returns * largest_argc;
storage.eval_func = [&operators..., largest](const tree_t& tree, void* context) -> evaluation_context& { storage.eval_func = tree_t::make_execution_lambda<Context>(largest, operators...);
const auto& ops = tree.get_operations();
const auto& vals = tree.get_values();
static thread_local evaluation_context results{};
results.values.reset();
results.values.reserve(largest);
blt::size_t total_so_far = 0;
blt::size_t op_pos = 0;
for (const auto& operation : iterate(ops).rev())
{
op_pos++;
if (operation.is_value())
{
total_so_far += operation.type_size();
results.values.copy_from(vals.from(total_so_far), operation.type_size());
continue;
}
call_jmp_table(operation.id(), context, results.values, results.values, operators...);
}
return results;
};
blt::hashset_t<type_id> has_terminals; blt::hashset_t<type_id> has_terminals;
@ -277,7 +277,7 @@ namespace blt::gp
out << "[Printing Value on '" << (op.get_name() ? *op.get_name() : "") << "' Not Supported!]"; out << "[Printing Value on '" << (op.get_name() ? *op.get_name() : "") << "' Not Supported!]";
} }
}); });
storage.destroy_funcs.push_back([](detail::destroy_t type, stack_allocator& alloc) storage.destroy_funcs.push_back([](const detail::destroy_t type, stack_allocator& alloc)
{ {
switch (type) switch (type)
{ {
@ -293,8 +293,7 @@ namespace blt::gp
} }
}); });
storage.names.push_back(op.get_name()); storage.names.push_back(op.get_name());
if (op.is_ephemeral()) storage.operator_flags.emplace(operator_id, operator_special_flags{op.is_ephemeral(), op.return_has_drop()});
storage.ephemeral_leaf_operators.insert(operator_id);
return meta; return meta;
} }
@ -307,48 +306,7 @@ namespace blt::gp
} }
} }
template <typename Operator> private:
static void execute(void* context, stack_allocator& write_stack, stack_allocator& read_stack, Operator& operation)
{
if constexpr (std::is_same_v<detail::remove_cv_ref<typename Operator::First_Arg>, Context>)
{
write_stack.push(operation(context, read_stack));
}
else
{
write_stack.push(operation(read_stack));
}
}
template <blt::size_t id, typename Operator>
static bool call(blt::size_t op, void* context, stack_allocator& write_stack, stack_allocator& read_stack, Operator& operation)
{
if (id == op)
{
execute(context, write_stack, read_stack, operation);
return false;
}
return true;
}
template <typename... Operators, size_t... operator_ids>
static void call_jmp_table_internal(size_t op, void* context, stack_allocator& write_stack, stack_allocator& read_stack,
std::integer_sequence<size_t, operator_ids...>, Operators&... operators)
{
if (op >= sizeof...(operator_ids))
{
BLT_UNREACHABLE;
}
(call<operator_ids>(op, context, write_stack, read_stack, operators) && ...);
}
template <typename... Operators>
static void call_jmp_table(size_t op, void* context, stack_allocator& write_stack, stack_allocator& read_stack,
Operators&... operators)
{
call_jmp_table_internal(op, context, write_stack, read_stack, std::index_sequence_for<Operators...>(), operators...);
}
program_operator_storage_t storage; program_operator_storage_t storage;
}; };
@ -756,9 +714,14 @@ namespace blt::gp
return current_stats; return current_stats;
} }
[[nodiscard]] bool is_operator_ephemeral(operator_id id) [[nodiscard]] bool is_operator_ephemeral(const operator_id id) const
{ {
return storage.ephemeral_leaf_operators.contains(static_cast<blt::size_t>(id)); return storage.operator_flags.find(static_cast<size_t>(id))->second.is_ephemeral();
}
[[nodiscard]] bool operator_has_drop(const operator_id id) const
{
return storage.operator_flags.find(static_cast<size_t>(id))->second.has_drop();
} }
void set_operations(program_operator_storage_t op) void set_operations(program_operator_storage_t op)

View File

@ -19,6 +19,7 @@
#ifndef BLT_GP_TREE_H #ifndef BLT_GP_TREE_H
#define BLT_GP_TREE_H #define BLT_GP_TREE_H
#include <blt/gp/util/meta.h>
#include <blt/gp/typesystem.h> #include <blt/gp/typesystem.h>
#include <blt/gp/stack.h> #include <blt/gp/stack.h>
#include <blt/gp/fwdecl.h> #include <blt/gp/fwdecl.h>
@ -31,16 +32,17 @@
namespace blt::gp namespace blt::gp
{ {
struct op_container_t struct op_container_t
{ {
op_container_t(const size_t type_size, const operator_id id, const bool is_value): op_container_t(const size_t type_size, const operator_id id, const bool is_value):
m_type_size(type_size), m_id(id), m_is_value(is_value), m_has_drop(false) m_type_size(type_size), m_id(id), m_is_value(is_value), m_has_drop(false)
{} {
}
op_container_t(const size_t type_size, const operator_id id, const bool is_value, const bool has_drop): op_container_t(const size_t type_size, const operator_id id, const bool is_value, const bool has_drop):
m_type_size(type_size), m_id(id), m_is_value(is_value), m_has_drop(has_drop) m_type_size(type_size), m_id(id), m_is_value(is_value), m_has_drop(has_drop)
{} {
}
[[nodiscard]] auto type_size() const [[nodiscard]] auto type_size() const
{ {
@ -61,6 +63,7 @@ namespace blt::gp
{ {
return m_has_drop; return m_has_drop;
} }
private: private:
size_t m_type_size; size_t m_type_size;
operator_id m_id; operator_id m_id;
@ -81,7 +84,10 @@ namespace blt::gp
public: public:
explicit tree_t(gp_program& program); explicit tree_t(gp_program& program);
tree_t(const tree_t& copy) = default; tree_t(const tree_t& copy): func(copy.func)
{
copy_fast(copy);
}
tree_t& operator=(const tree_t& copy) tree_t& operator=(const tree_t& copy)
{ {
@ -94,14 +100,13 @@ namespace blt::gp
/** /**
* This function copies the data from the provided tree, will attempt to reserve and copy in one step. * This function copies the data from the provided tree, will attempt to reserve and copy in one step.
* will avoid reallocation if enough space is already present. * will avoid reallocation if enough space is already present.
*
* This function is meant to copy into and replaces data inside the tree.
*/ */
void copy_fast(const tree_t& copy) void copy_fast(const tree_t& copy)
{ {
if (this == &copy) if (this == &copy)
return; return;
values.reserve(copy.values.stored());
values.reset();
values.insert(copy.values);
operations.reserve(copy.operations.size()); operations.reserve(copy.operations.size());
@ -119,7 +124,6 @@ namespace blt::gp
*op_it = *copy_it; *op_it = *copy_it;
if (copy_it->has_drop()) if (copy_it->has_drop())
{ {
} }
++copy_it; ++copy_it;
} }
@ -128,12 +132,15 @@ namespace blt::gp
{ {
if (op_it->has_drop()) if (op_it->has_drop())
{ {
} }
} }
operations.erase(op_it_cpy, operations.end()); operations.erase(op_it_cpy, operations.end());
for (; copy_it != copy.operations.end(); ++copy_it) for (; copy_it != copy.operations.end(); ++copy_it)
operations.emplace_back(*copy_it); operations.emplace_back(*copy_it);
values.reserve(copy.values.stored());
values.reset();
values.insert(copy.values);
} }
tree_t(tree_t&& move) = default; tree_t(tree_t&& move) = default;
@ -144,11 +151,22 @@ namespace blt::gp
struct child_t struct child_t
{ {
blt::ptrdiff_t start; ptrdiff_t start;
// one past the end // one past the end
blt::ptrdiff_t end; ptrdiff_t end;
}; };
void insert_operator(const op_container_t& container)
{
operations.emplace_back(container);
}
template <typename... Args>
void emplace_operator(Args&&... args)
{
operations.emplace_back(std::forward<Args>(args)...);
}
[[nodiscard]] inline tracked_vector<op_container_t>& get_operations() [[nodiscard]] inline tracked_vector<op_container_t>& get_operations()
{ {
return operations; return operations;
@ -255,7 +273,77 @@ namespace blt::gp
return total_value_bytes(operations.begin(), operations.end()); return total_value_bytes(operations.begin(), operations.end());
} }
template <typename Context, typename... Operators>
static auto make_execution_lambda(size_t call_reserve_size, Operators&... operators)
{
return [call_reserve_size, &operators...](const tree_t& tree, void* context) -> evaluation_context& {
const auto& ops = tree.operations;
const auto& vals = tree.values;
thread_local evaluation_context results{};
results.values.reset();
results.values.reserve(call_reserve_size);
size_t total_so_far = 0;
for (const auto& operation : iterate(ops).rev())
{
if (operation.is_value())
{
total_so_far += operation.type_size();
results.values.copy_from(vals.from(total_so_far), operation.type_size());
continue;
}
call_jmp_table<Context>(operation.id(), context, results.values, results.values, operators...);
}
return results;
};
}
private: private:
template <typename Context, typename Operator>
static void execute(void* context, stack_allocator& write_stack, stack_allocator& read_stack, Operator& operation)
{
if constexpr (std::is_same_v<detail::remove_cv_ref<typename Operator::First_Arg>, Context>)
{
write_stack.push(operation(context, read_stack));
}
else
{
write_stack.push(operation(read_stack));
}
}
template <typename Context, size_t id, typename Operator>
static bool call(const size_t op, void* context, stack_allocator& write_stack, stack_allocator& read_stack, Operator& operation)
{
if (id == op)
{
execute<Context>(context, write_stack, read_stack, operation);
return false;
}
return true;
}
template <typename Context, typename... Operators, size_t... operator_ids>
static void call_jmp_table_internal(size_t op, void* context, stack_allocator& write_stack, stack_allocator& read_stack,
std::integer_sequence<size_t, operator_ids...>, Operators&... operators)
{
if (op >= sizeof...(operator_ids))
{
BLT_UNREACHABLE;
}
(call<Context, operator_ids>(op, context, write_stack, read_stack, operators) && ...);
}
template <typename Context, typename... Operators>
static void call_jmp_table(size_t op, void* context, stack_allocator& write_stack, stack_allocator& read_stack,
Operators&... operators)
{
call_jmp_table_internal<Context>(op, context, write_stack, read_stack, std::index_sequence_for<Operators...>(), operators...);
}
tracked_vector<op_container_t> operations; tracked_vector<op_container_t> operations;
stack_allocator values; stack_allocator values;
detail::eval_func_t* func; detail::eval_func_t* func;
@ -285,10 +373,12 @@ namespace blt::gp
individual_t() = delete; individual_t() = delete;
explicit individual_t(tree_t&& tree): tree(std::move(tree)) explicit individual_t(tree_t&& tree): tree(std::move(tree))
{} {
}
explicit individual_t(const tree_t& tree): tree(tree) explicit individual_t(const tree_t& tree): tree(tree)
{} {
}
individual_t(const individual_t&) = default; individual_t(const individual_t&) = default;
@ -306,7 +396,8 @@ namespace blt::gp
{ {
public: public:
population_tree_iterator(tracked_vector<individual_t>& ind, blt::size_t pos): ind(ind), pos(pos) population_tree_iterator(tracked_vector<individual_t>& ind, blt::size_t pos): ind(ind), pos(pos)
{} {
}
auto begin() auto begin()
{ {

View File

@ -30,14 +30,14 @@
namespace blt::gp namespace blt::gp
{ {
struct operator_id : integer_type<blt::u64> struct operator_id : integer_type<u64>
{ {
using integer_type<blt::u64>::integer_type; using integer_type::integer_type;
}; };
struct type_id : integer_type<blt::u64> struct type_id : integer_type<u64>
{ {
using integer_type<blt::u64>::integer_type; using integer_type::integer_type;
}; };
class type class type
@ -48,31 +48,38 @@ namespace blt::gp
template <typename T> template <typename T>
static type make_type(const type_id id) static type make_type(const type_id id)
{ {
return type(stack_allocator::aligned_size<T>(), id, blt::type_string<T>()); return type(stack_allocator::aligned_size<T>(), id, blt::type_string<T>(), detail::has_func_drop<T>::value);
} }
[[nodiscard]] inline blt::size_t size() const [[nodiscard]] size_t size() const
{ {
return size_; return size_;
} }
[[nodiscard]] inline type_id id() const [[nodiscard]] type_id id() const
{ {
return id_; return id_;
} }
[[nodiscard]] inline std::string_view name() const [[nodiscard]] std::string_view name() const
{ {
return name_; return name_;
} }
[[nodiscard]] bool has_drop() const
{
return has_drop_;
}
private: private:
type(size_t size, type_id id, std::string_view name): size_(size), id_(id), name_(name) type(const size_t size, const type_id id, const std::string_view name, const bool has_drop): size_(size), id_(id), name_(name),
{} has_drop_(has_drop)
{
}
blt::size_t size_{}; size_t size_{};
type_id id_{}; type_id id_{};
std::string name_{}; std::string name_{};
bool has_drop_ = false;
}; };
/** /**
@ -85,7 +92,7 @@ namespace blt::gp
type_provider() = default; type_provider() = default;
template <typename T> template <typename T>
inline void register_type() void register_type()
{ {
if (has_type<T>()) if (has_type<T>())
return; return;
@ -95,17 +102,18 @@ namespace blt::gp
} }
template <typename T> template <typename T>
inline type get_type() type get_type()
{ {
return types[blt::type_string_raw<T>()]; return types[blt::type_string_raw<T>()];
} }
template <typename T> template <typename T>
inline bool has_type(){ bool has_type()
{
return types.find(blt::type_string_raw<T>()) != types.end(); return types.find(blt::type_string_raw<T>()) != types.end();
} }
inline type get_type(type_id id) type get_type(type_id id)
{ {
return types_from_id[id]; return types_from_id[id];
} }
@ -115,7 +123,7 @@ namespace blt::gp
* @param engine * @param engine
* @return * @return
*/ */
inline type select_type(std::mt19937_64& engine) type select_type(std::mt19937_64& engine)
{ {
std::uniform_int_distribution dist(0ul, types.size() - 1); std::uniform_int_distribution dist(0ul, types.size() - 1);
auto offset = dist(engine); auto offset = dist(engine);
@ -126,8 +134,8 @@ namespace blt::gp
} }
private: private:
blt::hashmap_t<std::string, type> types; hashmap_t<std::string, type> types;
blt::expanding_buffer<type> types_from_id; expanding_buffer<type> types_from_id;
}; };
} }

View File

@ -0,0 +1,66 @@
#pragma once
/*
* Copyright (C) 2024 Brett Terpstra
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef BLT_GP_UTIL_META_H
#define BLT_GP_UTIL_META_H
#include <type_traits>
namespace blt::gp::detail
{
template <typename T>
using remove_cv_ref = std::remove_cv_t<std::remove_reference_t<T>>;
template <typename...>
struct first_arg;
template <typename First, typename... Args>
struct first_arg<First, Args...>
{
using type = First;
};
template <>
struct first_arg<>
{
using type = void;
};
template <bool b, typename... types>
struct is_same;
template <typename... types>
struct is_same<true, types...> : std::false_type
{
};
template <typename... types>
struct is_same<false, types...> : std::is_same<types...>
{
};
template <typename... types>
constexpr bool is_same_v = is_same<sizeof...(types) == 0, types...>::value;
struct empty_t
{
};
}
#endif //BLT_GP_UTIL_META_H

View File

@ -61,7 +61,7 @@ namespace blt::gp
auto& info = args.program.get_operator_info(top.id); auto& info = args.program.get_operator_info(top.id);
tree.get_operations().emplace_back( tree.emplace_operator(
args.program.get_typesystem().get_type(info.return_type).size(), args.program.get_typesystem().get_type(info.return_type).size(),
top.id, top.id,
args.program.is_operator_ephemeral(top.id)); args.program.is_operator_ephemeral(top.id));

View File

@ -308,6 +308,13 @@ namespace blt::gp
auto* f = &program.get_eval_func(); auto* f = &program.get_eval_func();
if (f != func) if (f != func)
func = f; func = f;
for (const auto& op : operations)
{
if (op.has_drop())
{
}
}
operations.clear(); operations.clear();
values.reset(); values.reset();
} }

View File

@ -189,22 +189,22 @@ inline void there(blt::size_t)
int main() int main()
{ {
blt::gp::thread_manager_t threads{ // blt::gp::thread_manager_t threads{
std::thread::hardware_concurrency(), blt::gp::task_builder_t<test>::make_callable( // std::thread::hardware_concurrency(), blt::gp::task_builder_t<test>::make_callable(
blt::gp::task_t{test::hello, hello}, // blt::gp::task_t{test::hello, hello},
blt::gp::task_t{test::there, there} // blt::gp::task_t{test::there, there}
) // )
}; // };
threads.add_task(test::hello); // threads.add_task(test::hello);
threads.add_task(test::hello); // threads.add_task(test::hello);
threads.add_task(test::hello); // threads.add_task(test::hello);
threads.add_task(test::there); // threads.add_task(test::there);
while (threads.has_tasks_left()) // while (threads.has_tasks_left())
threads.execute(); // threads.execute();
// for (int i = 0; i < 1; i++) for (int i = 0; i < 1; i++)
// do_run(); do_run();
return 0; return 0;
} }