Skip to content

Commit

Permalink
Improved lambda hotswapping when uniquely named
Browse files Browse the repository at this point in the history
  • Loading branch information
rune-scape committed Oct 16, 2024
1 parent 92e51fc commit 4cbbc62
Show file tree
Hide file tree
Showing 16 changed files with 380 additions and 174 deletions.
17 changes: 9 additions & 8 deletions modules/gdscript/gdscript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1505,14 +1505,15 @@ GDScript::UpdatableFuncPtr::~UpdatableFuncPtr() {
}

void GDScript::_recurse_replace_function_ptrs(const HashMap<GDScriptFunction *, GDScriptFunction *> &p_replacements) const {
MutexLock lock(func_ptrs_to_update_mutex);
for (UpdatableFuncPtr *updatable : func_ptrs_to_update) {
HashMap<GDScriptFunction *, GDScriptFunction *>::ConstIterator replacement = p_replacements.find(updatable->ptr);
if (replacement) {
updatable->ptr = replacement->value;
} else {
// Probably a lambda from another reload, ignore.
updatable->ptr = nullptr;
{
MutexLock lock(func_ptrs_to_update_mutex);
for (UpdatableFuncPtr *updatable : func_ptrs_to_update) {
HashMap<GDScriptFunction *, GDScriptFunction *>::ConstIterator replacement = p_replacements.find(updatable->ptr);
if (replacement) {
updatable->ptr = replacement->value;
} else {
updatable->ptr = nullptr;
}
}
}

Expand Down
182 changes: 96 additions & 86 deletions modules/gdscript/gdscript_compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

#include "gdscript_compiler.h"

#include "core/error/error_macros.h"
#include "gdscript.h"
#include "gdscript_byte_codegen.h"
#include "gdscript_cache.h"
Expand Down Expand Up @@ -2258,6 +2259,9 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_
if (p_func) {
if (p_func->identifier) {
func_name = p_func->identifier->name;
} else if (p_func->source_lambda && p_func->source_lambda->parent_variable) {
GDScriptParser::AssignableNode *parent_variable = p_func->source_lambda->parent_variable;
func_name = vformat("<anonymous lambda(%s)>", parent_variable->identifier->name);
} else {
func_name = "<anonymous lambda>";
}
Expand Down Expand Up @@ -3107,129 +3111,133 @@ void GDScriptCompiler::make_scripts(GDScript *p_script, const GDScriptParser::Cl
}
}

GDScriptCompiler::FunctionLambdaInfo GDScriptCompiler::_get_function_replacement_info(GDScriptFunction *p_func, int p_index, int p_depth, GDScriptFunction *p_parent_func) {
FunctionLambdaInfo info;
info.function = p_func;
info.parent = p_parent_func;
info.script = p_func->get_script();
info.name = p_func->get_name();
info.line = p_func->_initial_line;
info.index = p_index;
info.depth = p_depth;
info.capture_count = 0;
info.use_self = false;
info.arg_count = p_func->_argument_count;
info.default_arg_count = p_func->_default_arg_count;
info.sublambdas = _get_function_lambda_replacement_info(p_func, p_depth, p_parent_func);

ERR_FAIL_NULL_V(info.script, info);
GDScript::LambdaInfo *extra_info = info.script->lambda_info.getptr(p_func);
if (extra_info != nullptr) {
info.capture_count = extra_info->capture_count;
info.use_self = extra_info->use_self;
} else {
info.capture_count = 0;
void GDScriptCompiler::LambdaSourceInfoList::collect_function_lambda_replacement_info(GDScriptFunction *p_func) {
// Only collect the lambdas inside.
for (int i = 0; i < p_func->lambdas.size(); ++i) {
LambdaSourceInfoList::Element *E = infos.push_back({});
LambdaSourceInfo &info = E->get();
GDScriptFunction *lambda = p_func->lambdas[i];

info.function = lambda;
info.parent = p_func;
info.script = lambda->get_script();
info.name = lambda->get_name();
info.is_static = lambda->is_static();
info.use_self = false;
}

return info;
}
info.capture_count = 0;
info.arg_count = lambda->get_argument_count();
info.default_arg_count = lambda->get_default_argument_count();
info.sublambdas.collect_function_lambda_replacement_info(lambda);

if (info.script) {
GDScript::LambdaInfo *extra_info = info.script->lambda_info.getptr(lambda);
if (extra_info != nullptr) {
info.capture_count = extra_info->capture_count;
info.use_self = extra_info->use_self;
} else {
info.capture_count = 0;
info.use_self = false;
}
}

Vector<GDScriptCompiler::FunctionLambdaInfo> GDScriptCompiler::_get_function_lambda_replacement_info(GDScriptFunction *p_func, int p_depth, GDScriptFunction *p_parent_func) {
Vector<FunctionLambdaInfo> result;
// Only scrape the lambdas inside p_func.
for (int i = 0; i < p_func->lambdas.size(); ++i) {
result.push_back(_get_function_replacement_info(p_func->lambdas[i], i, p_depth + 1, p_func));
info.self_element = E;
infos_by_name[info.name].push_back(&info);
}
return result;
}

GDScriptCompiler::ScriptLambdaInfo GDScriptCompiler::_get_script_lambda_replacement_info(GDScript *p_script) {
ScriptLambdaInfo info;

void GDScriptCompiler::ScriptLambdaInfo::collect_script_lambda_replacement_info(GDScript *p_script) {
if (p_script->implicit_initializer) {
info.implicit_initializer_info = _get_function_lambda_replacement_info(p_script->implicit_initializer);
initializers.collect_function_lambda_replacement_info(p_script->implicit_initializer);
}
if (p_script->implicit_ready) {
info.implicit_ready_info = _get_function_lambda_replacement_info(p_script->implicit_ready);
initializers.collect_function_lambda_replacement_info(p_script->implicit_ready);
}
if (p_script->static_initializer) {
info.static_initializer_info = _get_function_lambda_replacement_info(p_script->static_initializer);
initializers.collect_function_lambda_replacement_info(p_script->static_initializer);
}

for (const KeyValue<StringName, GDScriptFunction *> &E : p_script->member_functions) {
info.member_function_infos.insert(E.key, _get_function_lambda_replacement_info(E.value));
member_functions[E.key].collect_function_lambda_replacement_info(E.value);
}

for (const KeyValue<StringName, Ref<GDScript>> &KV : p_script->get_subclasses()) {
info.subclass_info.insert(KV.key, _get_script_lambda_replacement_info(KV.value.ptr()));
subclasses[KV.key].collect_script_lambda_replacement_info(KV.value.ptr());
}

return info;
}

bool GDScriptCompiler::_do_function_infos_match(const FunctionLambdaInfo &p_old_info, const FunctionLambdaInfo *p_new_info) {
if (p_new_info == nullptr) {
bool GDScriptCompiler::LambdaSourceInfo::can_be_replaced_by(const LambdaSourceInfo &p_new_info) const {
if (p_new_info.capture_count != capture_count || p_new_info.use_self != use_self) {
return false;
}

if (p_new_info->capture_count != p_old_info.capture_count || p_new_info->use_self != p_old_info.use_self) {
if (p_new_info.script != script) {
return false;
}

int old_required_arg_count = p_old_info.arg_count - p_old_info.default_arg_count;
int new_required_arg_count = p_new_info->arg_count - p_new_info->default_arg_count;
if (new_required_arg_count > old_required_arg_count || p_new_info->arg_count < old_required_arg_count) {
int old_required_arg_count = arg_count - default_arg_count;
int new_required_arg_count = p_new_info.arg_count - p_new_info.default_arg_count;
if (new_required_arg_count > old_required_arg_count || p_new_info.arg_count < old_required_arg_count) {
return false;
}

return true;
}

void GDScriptCompiler::_get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const FunctionLambdaInfo &p_old_info, const FunctionLambdaInfo *p_new_info) {
ERR_FAIL_COND(r_replacements.has(p_old_info.function));
if (!_do_function_infos_match(p_old_info, p_new_info)) {
p_new_info = nullptr;
void GDScriptCompiler::_collect_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, LambdaSourceInfoList &p_old, LambdaSourceInfoList *p_new) {
if (p_new) {
for (KeyValue<StringName, Vector<LambdaSourceInfo *>> &old_named_infos : p_old.infos_by_name) {
if (old_named_infos.value.size() == 1) {
// Uniquely named lambda.
if (HashMap<StringName, Vector<LambdaSourceInfo *>>::Iterator new_named_infos_it = p_new->infos_by_name.find(old_named_infos.key)) {
if (new_named_infos_it->value.size() == 1) {
// Also uniquely named.
LambdaSourceInfo *&old_info = old_named_infos.value.write[0];
LambdaSourceInfo *&new_info = new_named_infos_it->value.write[0];
ERR_CONTINUE(old_info == nullptr);
ERR_CONTINUE(new_info == nullptr);
r_replacements[old_info->function] = new_info->function;
old_info->self_element->erase();
new_info->self_element->erase();
old_info = nullptr;
new_info = nullptr;
}
}
}
}
}

r_replacements.insert(p_old_info.function, p_new_info != nullptr ? p_new_info->function : nullptr);
_get_function_ptr_replacements(r_replacements, p_old_info.sublambdas, p_new_info != nullptr ? &p_new_info->sublambdas : nullptr);
}

void GDScriptCompiler::_get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const Vector<FunctionLambdaInfo> &p_old_infos, const Vector<FunctionLambdaInfo> *p_new_infos) {
for (int i = 0; i < p_old_infos.size(); ++i) {
const FunctionLambdaInfo &old_info = p_old_infos[i];
const FunctionLambdaInfo *new_info = nullptr;
if (p_new_infos != nullptr && p_new_infos->size() == p_old_infos.size()) {
LambdaSourceInfoList::Element *old_info_E = p_old.infos.front();
LambdaSourceInfoList::Element *new_info_E = p_new ? p_new->infos.front() : nullptr;
while (old_info_E) {
LambdaSourceInfo &old_info = old_info_E->get();
LambdaSourceInfo *new_info = nullptr;
if (p_new && p_new->infos.size() == p_old.infos.size()) {
// For now only attempt if the size is the same.
new_info = &p_new_infos->get(i);
new_info = &new_info_E->get();
}

if (new_info && old_info.can_be_replaced_by(*new_info)) {
r_replacements[old_info.function] = new_info->function;
}

_collect_function_ptr_replacements(r_replacements, old_info.sublambdas, new_info ? &new_info->sublambdas : nullptr);
old_info_E = old_info_E->next();
if (new_info_E) {
new_info_E = new_info_E->next();
}
_get_function_ptr_replacements(r_replacements, old_info, new_info);
}
}

void GDScriptCompiler::_get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const ScriptLambdaInfo &p_old_info, const ScriptLambdaInfo *p_new_info) {
_get_function_ptr_replacements(r_replacements, p_old_info.implicit_initializer_info, p_new_info != nullptr ? &p_new_info->implicit_initializer_info : nullptr);
_get_function_ptr_replacements(r_replacements, p_old_info.implicit_ready_info, p_new_info != nullptr ? &p_new_info->implicit_ready_info : nullptr);
_get_function_ptr_replacements(r_replacements, p_old_info.static_initializer_info, p_new_info != nullptr ? &p_new_info->static_initializer_info : nullptr);
void GDScriptCompiler::_collect_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, ScriptLambdaInfo &p_old_info, ScriptLambdaInfo *p_new_info) {
_collect_function_ptr_replacements(r_replacements, p_old_info.initializers, p_new_info ? &p_new_info->initializers : nullptr);

for (const KeyValue<StringName, Vector<FunctionLambdaInfo>> &old_kv : p_old_info.member_function_infos) {
_get_function_ptr_replacements(r_replacements, old_kv.value, p_new_info != nullptr ? p_new_info->member_function_infos.getptr(old_kv.key) : nullptr);
}
for (int i = 0; i < p_old_info.other_function_infos.size(); ++i) {
const FunctionLambdaInfo &old_other_info = p_old_info.other_function_infos[i];
const FunctionLambdaInfo *new_other_info = nullptr;
if (p_new_info != nullptr && p_new_info->other_function_infos.size() == p_old_info.other_function_infos.size()) {
// For now only attempt if the size is the same.
new_other_info = &p_new_info->other_function_infos[i];
}
// Needs to be called on all old lambdas, even if there's no replacement.
_get_function_ptr_replacements(r_replacements, old_other_info, new_other_info);
for (KeyValue<StringName, LambdaSourceInfoList> &old_kv : p_old_info.member_functions) {
_collect_function_ptr_replacements(r_replacements, old_kv.value, p_new_info ? p_new_info->member_functions.getptr(old_kv.key) : nullptr);
}
for (const KeyValue<StringName, ScriptLambdaInfo> &old_kv : p_old_info.subclass_info) {
const ScriptLambdaInfo &old_subinfo = old_kv.value;
const ScriptLambdaInfo *new_subinfo = p_new_info != nullptr ? p_new_info->subclass_info.getptr(old_kv.key) : nullptr;
_get_function_ptr_replacements(r_replacements, old_subinfo, new_subinfo);
for (KeyValue<StringName, ScriptLambdaInfo> &old_kv : p_old_info.subclasses) {
ScriptLambdaInfo &old_subinfo = old_kv.value;
ScriptLambdaInfo *new_subinfo = p_new_info ? p_new_info->subclasses.getptr(old_kv.key) : nullptr;
_collect_function_ptr_replacements(r_replacements, old_subinfo, new_subinfo);
}
}

Expand All @@ -3243,7 +3251,8 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri

source = p_script->get_path();

ScriptLambdaInfo old_lambda_info = _get_script_lambda_replacement_info(p_script);
ScriptLambdaInfo old_lambda_info;
old_lambda_info.collect_script_lambda_replacement_info(p_script);

// Create scripts for subclasses beforehand so they can be referenced
make_scripts(p_script, root, p_keep_state);
Expand All @@ -3260,10 +3269,11 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri
return err;
}

ScriptLambdaInfo new_lambda_info = _get_script_lambda_replacement_info(p_script);
ScriptLambdaInfo new_lambda_info;
new_lambda_info.collect_script_lambda_replacement_info(p_script);

HashMap<GDScriptFunction *, GDScriptFunction *> func_ptr_replacements;
_get_function_ptr_replacements(func_ptr_replacements, old_lambda_info, &new_lambda_info);
_collect_function_ptr_replacements(func_ptr_replacements, old_lambda_info, &new_lambda_info);
main_script->_recurse_replace_function_ptrs(func_ptr_replacements);

if (has_static_data && !root->annotated_static_unload) {
Expand Down
50 changes: 29 additions & 21 deletions modules/gdscript/gdscript_compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "gdscript_function.h"
#include "gdscript_parser.h"

#include "core/templates/hash_map.h"
#include "core/templates/hash_set.h"

class GDScriptCompiler {
Expand All @@ -44,34 +45,44 @@ class GDScriptCompiler {
HashSet<GDScript *> parsing_classes;
GDScript *main_script = nullptr;

struct FunctionLambdaInfo {
public:
struct LambdaSourceInfo;

struct LambdaSourceInfoList {
using Element = List<LambdaSourceInfo>::Element;
List<LambdaSourceInfo> infos;
HashMap<StringName, Vector<LambdaSourceInfo *>> infos_by_name;

void collect_function_lambda_replacement_info(GDScriptFunction *p_func);
};

struct LambdaSourceInfo {
GDScriptFunction *function = nullptr;
GDScriptFunction *parent = nullptr;
GDScript *script = nullptr;
StringName name;
int line = 0;
int index = 0;
int depth = 0;
//uint64_t code_hash;
//int code_size;
int capture_count = 0;
bool is_static = false;
bool use_self = false;
int capture_count = 0;
int arg_count = 0;
int default_arg_count = 0;
//Vector<GDScriptDataType> argument_types;
//GDScriptDataType return_type;
Vector<FunctionLambdaInfo> sublambdas;
LambdaSourceInfoList sublambdas;
LambdaSourceInfoList::Element *self_element;

bool can_be_replaced_by(const LambdaSourceInfo &p_new_info) const;
};

struct ScriptLambdaInfo {
Vector<FunctionLambdaInfo> implicit_initializer_info;
Vector<FunctionLambdaInfo> implicit_ready_info;
Vector<FunctionLambdaInfo> static_initializer_info;
HashMap<StringName, Vector<FunctionLambdaInfo>> member_function_infos;
Vector<FunctionLambdaInfo> other_function_infos;
HashMap<StringName, ScriptLambdaInfo> subclass_info;
LambdaSourceInfoList initializers;
HashMap<StringName, LambdaSourceInfoList> member_functions;
HashMap<StringName, ScriptLambdaInfo> subclasses;

void collect_script_lambda_replacement_info(GDScript *p_script);
};

private:
struct CodeGen {
GDScript *script = nullptr;
const GDScriptParser::ClassNode *class_node = nullptr;
Expand Down Expand Up @@ -161,13 +172,10 @@ class GDScriptCompiler {
Error _parse_setter_getter(GDScript *p_script, const GDScriptParser::ClassNode *p_class, const GDScriptParser::VariableNode *p_variable, bool p_is_setter);
Error _prepare_compilation(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
Error _compile_class(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state);
FunctionLambdaInfo _get_function_replacement_info(GDScriptFunction *p_func, int p_index = -1, int p_depth = 0, GDScriptFunction *p_parent_func = nullptr);
Vector<FunctionLambdaInfo> _get_function_lambda_replacement_info(GDScriptFunction *p_func, int p_depth = 0, GDScriptFunction *p_parent_func = nullptr);
ScriptLambdaInfo _get_script_lambda_replacement_info(GDScript *p_script);
bool _do_function_infos_match(const FunctionLambdaInfo &p_old_info, const FunctionLambdaInfo *p_new_info);
void _get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const FunctionLambdaInfo &p_old_info, const FunctionLambdaInfo *p_new_info);
void _get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const Vector<FunctionLambdaInfo> &p_old_infos, const Vector<FunctionLambdaInfo> *p_new_infos);
void _get_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, const ScriptLambdaInfo &p_old_info, const ScriptLambdaInfo *p_new_info);

static void _collect_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, LambdaSourceInfoList &p_old, LambdaSourceInfoList *p_new);
static void _collect_function_ptr_replacements(HashMap<GDScriptFunction *, GDScriptFunction *> &r_replacements, ScriptLambdaInfo &p_old_info, ScriptLambdaInfo *p_new_info);

int err_line = 0;
int err_column = 0;
StringName source;
Expand Down
1 change: 1 addition & 0 deletions modules/gdscript/gdscript_function.h
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,7 @@ class GDScriptFunction {
_FORCE_INLINE_ bool is_static() const { return _static; }
_FORCE_INLINE_ MethodInfo get_method_info() const { return method_info; }
_FORCE_INLINE_ int get_argument_count() const { return _argument_count; }
_FORCE_INLINE_ int get_default_argument_count() const { return _default_arg_count; }
_FORCE_INLINE_ Variant get_rpc_config() const { return rpc_config; }
_FORCE_INLINE_ int get_max_stack_size() const { return _stack_size; }

Expand Down
Loading

0 comments on commit 4cbbc62

Please sign in to comment.