Files
test/source/blender/functions/FN_multi_function_procedure.hh
Campbell Barton e955c94ed3 License Headers: Set copyright to "Blender Authors", add AUTHORS
Listing the "Blender Foundation" as copyright holder implied the Blender
Foundation holds copyright to files which may include work from many
developers.

While keeping copyright on headers makes sense for isolated libraries,
Blender's own code may be refactored or moved between files in a way
that makes the per file copyright holders less meaningful.

Copyright references to the "Blender Foundation" have been replaced with
"Blender Authors", with the exception of `./extern/` since these this
contains libraries which are more isolated, any changed to license
headers there can be handled on a case-by-case basis.

Some directories in `./intern/` have also been excluded:

- `./intern/cycles/` it's own `AUTHORS` file is planned.
- `./intern/opensubdiv/`.

An "AUTHORS" file has been added, using the chromium projects authors
file as a template.

Design task: #110784

Ref !110783.
2023-08-16 00:20:26 +10:00

545 lines
12 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup fn
*/
#include "FN_multi_function.hh"
namespace blender::fn::multi_function {
class Variable;
class Instruction;
class CallInstruction;
class BranchInstruction;
class DestructInstruction;
class DummyInstruction;
class ReturnInstruction;
class Procedure;
/** Every instruction has exactly one of these types. */
enum class InstructionType {
Call,
Branch,
Destruct,
Dummy,
Return,
};
/**
* An #InstructionCursor points to a position in a multi-function procedure, where an instruction
* can be inserted.
*/
class InstructionCursor {
public:
enum Type {
None,
Entry,
Call,
Destruct,
Branch,
Dummy,
};
private:
Type type_ = None;
Instruction *instruction_ = nullptr;
/* Only used when it is a branch instruction. */
bool branch_output_ = false;
public:
InstructionCursor() = default;
InstructionCursor(CallInstruction &instruction);
InstructionCursor(DestructInstruction &instruction);
InstructionCursor(BranchInstruction &instruction, bool branch_output);
InstructionCursor(DummyInstruction &instruction);
static InstructionCursor ForEntry();
Instruction *next(Procedure &procedure) const;
void set_next(Procedure &procedure, Instruction *new_instruction) const;
Instruction *instruction() const;
Type type() const;
friend bool operator==(const InstructionCursor &a, const InstructionCursor &b)
{
return a.type_ == b.type_ && a.instruction_ == b.instruction_ &&
a.branch_output_ == b.branch_output_;
}
friend bool operator!=(const InstructionCursor &a, const InstructionCursor &b)
{
return !(a == b);
}
};
/**
* A variable is similar to a virtual register in other libraries. During evaluation, every is
* either uninitialized or contains a value for every index (remember, a multi-function procedure
* is always evaluated for many indices at the same time).
*/
class Variable : NonCopyable, NonMovable {
private:
DataType data_type_;
Vector<Instruction *> users_;
std::string name_;
int index_in_graph_;
friend Procedure;
friend CallInstruction;
friend BranchInstruction;
friend DestructInstruction;
public:
DataType data_type() const;
Span<Instruction *> users();
StringRefNull name() const;
void set_name(std::string name);
int index_in_procedure() const;
};
/** Base class for all instruction types. */
class Instruction : NonCopyable, NonMovable {
protected:
InstructionType type_;
Vector<InstructionCursor> prev_;
friend Procedure;
friend CallInstruction;
friend BranchInstruction;
friend DestructInstruction;
friend DummyInstruction;
friend ReturnInstruction;
public:
InstructionType type() const;
/**
* Other instructions that come before this instruction. There can be multiple previous
* instructions when branching is used in the procedure.
*/
Span<InstructionCursor> prev() const;
};
/**
* References a multi-function that is evaluated when the instruction is executed. It also
* references the variables whose data will be passed into the multi-function.
*/
class CallInstruction : public Instruction {
private:
const MultiFunction *fn_ = nullptr;
Instruction *next_ = nullptr;
MutableSpan<Variable *> params_;
friend Procedure;
public:
const MultiFunction &fn() const;
Instruction *next();
const Instruction *next() const;
void set_next(Instruction *instruction);
void set_param_variable(int param_index, Variable *variable);
void set_params(Span<Variable *> variables);
Span<Variable *> params();
Span<const Variable *> params() const;
};
/**
* What makes a branch instruction special is that it has two successor instructions. One that will
* be used when a condition variable was true, and one otherwise.
*/
class BranchInstruction : public Instruction {
private:
Variable *condition_ = nullptr;
Instruction *branch_true_ = nullptr;
Instruction *branch_false_ = nullptr;
friend Procedure;
public:
Variable *condition();
const Variable *condition() const;
void set_condition(Variable *variable);
Instruction *branch_true();
const Instruction *branch_true() const;
void set_branch_true(Instruction *instruction);
Instruction *branch_false();
const Instruction *branch_false() const;
void set_branch_false(Instruction *instruction);
};
/**
* A destruct instruction destructs a single variable. So the variable value will be uninitialized
* after this instruction. All variables that are not output variables of the procedure, have to be
* destructed before the procedure ends. Destructing early is generally a good thing, because it
* might help with memory buffer reuse, which decreases memory-usage and increases performance.
*/
class DestructInstruction : public Instruction {
private:
Variable *variable_ = nullptr;
Instruction *next_ = nullptr;
friend Procedure;
public:
Variable *variable();
const Variable *variable() const;
void set_variable(Variable *variable);
Instruction *next();
const Instruction *next() const;
void set_next(Instruction *instruction);
};
/**
* This instruction does nothing, it just exists to building a procedure simpler in some cases.
*/
class DummyInstruction : public Instruction {
private:
Instruction *next_ = nullptr;
friend Procedure;
public:
Instruction *next();
const Instruction *next() const;
void set_next(Instruction *instruction);
};
/**
* This instruction ends the procedure.
*/
class ReturnInstruction : public Instruction {
};
/**
* Inputs and outputs of the entire procedure network.
*/
struct Parameter {
ParamType::InterfaceType type;
Variable *variable;
};
struct ConstParameter {
ParamType::InterfaceType type;
const Variable *variable;
};
/**
* A multi-function procedure allows composing multi-functions in arbitrary ways. It consists of
* variables and instructions that operate on those variables. Branching and looping within the
* procedure is supported as well.
*
* Typically, a #Procedure should be constructed using a #ProcedureBuilder, which has many more
* utility methods for common use cases.
*/
class Procedure : NonCopyable, NonMovable {
private:
LinearAllocator<> allocator_;
Vector<CallInstruction *> call_instructions_;
Vector<BranchInstruction *> branch_instructions_;
Vector<DestructInstruction *> destruct_instructions_;
Vector<DummyInstruction *> dummy_instructions_;
Vector<ReturnInstruction *> return_instructions_;
Vector<Variable *> variables_;
Vector<Parameter> params_;
Vector<destruct_ptr<MultiFunction>> owned_functions_;
Instruction *entry_ = nullptr;
friend class ProcedureDotExport;
public:
Procedure() = default;
~Procedure();
Variable &new_variable(DataType data_type, std::string name = "");
CallInstruction &new_call_instruction(const MultiFunction &fn);
BranchInstruction &new_branch_instruction();
DestructInstruction &new_destruct_instruction();
DummyInstruction &new_dummy_instruction();
ReturnInstruction &new_return_instruction();
void add_parameter(ParamType::InterfaceType interface_type, Variable &variable);
Span<ConstParameter> params() const;
template<typename T, typename... Args> const MultiFunction &construct_function(Args &&...args);
Instruction *entry();
const Instruction *entry() const;
void set_entry(Instruction &entry);
Span<Variable *> variables();
Span<const Variable *> variables() const;
std::string to_dot() const;
bool validate() const;
private:
bool validate_all_instruction_pointers_set() const;
bool validate_all_params_provided() const;
bool validate_same_variables_in_one_call() const;
bool validate_parameters() const;
bool validate_initialization() const;
struct InitState {
bool can_be_initialized = false;
bool can_be_uninitialized = false;
};
InitState find_initialization_state_before_instruction(const Instruction &target_instruction,
const Variable &variable) const;
};
/* -------------------------------------------------------------------- */
/** \name #InstructionCursor Inline Methods
* \{ */
inline InstructionCursor::InstructionCursor(CallInstruction &instruction)
: type_(Call), instruction_(&instruction)
{
}
inline InstructionCursor::InstructionCursor(DestructInstruction &instruction)
: type_(Destruct), instruction_(&instruction)
{
}
inline InstructionCursor::InstructionCursor(BranchInstruction &instruction, bool branch_output)
: type_(Branch), instruction_(&instruction), branch_output_(branch_output)
{
}
inline InstructionCursor::InstructionCursor(DummyInstruction &instruction)
: type_(Dummy), instruction_(&instruction)
{
}
inline InstructionCursor InstructionCursor::ForEntry()
{
InstructionCursor cursor;
cursor.type_ = Type::Entry;
return cursor;
}
inline Instruction *InstructionCursor::instruction() const
{
/* This isn't really const correct unfortunately, because to make it correct we'll need a const
* version of #InstructionCursor. */
return instruction_;
}
inline InstructionCursor::Type InstructionCursor::type() const
{
return type_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #Variable Inline Methods
* \{ */
inline DataType Variable::data_type() const
{
return data_type_;
}
inline Span<Instruction *> Variable::users()
{
return users_;
}
inline StringRefNull Variable::name() const
{
return name_;
}
inline int Variable::index_in_procedure() const
{
return index_in_graph_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #Instruction Inline Methods
* \{ */
inline InstructionType Instruction::type() const
{
return type_;
}
inline Span<InstructionCursor> Instruction::prev() const
{
return prev_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #CallInstruction Inline Methods
* \{ */
inline const MultiFunction &CallInstruction::fn() const
{
return *fn_;
}
inline Instruction *CallInstruction::next()
{
return next_;
}
inline const Instruction *CallInstruction::next() const
{
return next_;
}
inline Span<Variable *> CallInstruction::params()
{
return params_;
}
inline Span<const Variable *> CallInstruction::params() const
{
return params_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #BranchInstruction Inline Methods
* \{ */
inline Variable *BranchInstruction::condition()
{
return condition_;
}
inline const Variable *BranchInstruction::condition() const
{
return condition_;
}
inline Instruction *BranchInstruction::branch_true()
{
return branch_true_;
}
inline const Instruction *BranchInstruction::branch_true() const
{
return branch_true_;
}
inline Instruction *BranchInstruction::branch_false()
{
return branch_false_;
}
inline const Instruction *BranchInstruction::branch_false() const
{
return branch_false_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #DestructInstruction Inline Methods
* \{ */
inline Variable *DestructInstruction::variable()
{
return variable_;
}
inline const Variable *DestructInstruction::variable() const
{
return variable_;
}
inline Instruction *DestructInstruction::next()
{
return next_;
}
inline const Instruction *DestructInstruction::next() const
{
return next_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #DummyInstruction Inline Methods
* \{ */
inline Instruction *DummyInstruction::next()
{
return next_;
}
inline const Instruction *DummyInstruction::next() const
{
return next_;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name #Procedure Inline Methods
* \{ */
inline Span<ConstParameter> Procedure::params() const
{
static_assert(sizeof(Parameter) == sizeof(ConstParameter));
return params_.as_span().cast<ConstParameter>();
}
inline Instruction *Procedure::entry()
{
return entry_;
}
inline const Instruction *Procedure::entry() const
{
return entry_;
}
inline Span<Variable *> Procedure::variables()
{
return variables_;
}
inline Span<const Variable *> Procedure::variables() const
{
return variables_;
}
template<typename T, typename... Args>
inline const MultiFunction &Procedure::construct_function(Args &&...args)
{
destruct_ptr<T> fn = allocator_.construct<T>(std::forward<Args>(args)...);
const MultiFunction &fn_ref = *fn;
owned_functions_.append(std::move(fn));
return fn_ref;
}
/** \} */
} // namespace blender::fn::multi_function