Skip to content

Commit

Permalink
Add observer functions
Browse files Browse the repository at this point in the history
 - When there is a setter and an observer, value is not changed
   before calling the observer
 - Partially fixes godotengine#6491
  • Loading branch information
creikey committed Oct 15, 2019
1 parent 1fed266 commit 6f5b4d1
Show file tree
Hide file tree
Showing 9 changed files with 81 additions and 6 deletions.
1 change: 1 addition & 0 deletions core/class_db.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ class ClassDB {
int index;
StringName setter;
StringName getter;
StringName observer;
MethodBind *_setptr;
MethodBind *_getptr;
Variant::Type type;
Expand Down
18 changes: 17 additions & 1 deletion modules/gdscript/gdscript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -951,13 +951,28 @@ bool GDScriptInstance::set(const StringName &p_name, const Variant &p_value) {
Variant::CallError err;
call(E->get().setter, &val, 1, err);
if (err.error == Variant::CallError::CALL_OK) {
return true; //function exists, call was successful
if (E->get().observer) {
// call observer without changing data beforehand
call(E->get().observer, NULL, 0, err);
if (err.error != Variant::CallError::CALL_OK) {
return false; // error in calling observer function
}
}
return true; // function exists, call was successful
}

} else {
if (!E->get().data_type.is_type(p_value)) {
return false; // Type mismatch
}
members.write[E->get().index] = p_value;
if (E->get().observer) {
Variant::CallError err;
call(E->get().observer, NULL, 0, err);
if (err.error == Variant::CallError::CALL_OK) {
return true;
}
}
}
return true;
}
Expand Down Expand Up @@ -1790,6 +1805,7 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
"func",
"preload",
"setget",
"observer",
"signal",
"tool",
"yield",
Expand Down
1 change: 1 addition & 0 deletions modules/gdscript/gdscript.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class GDScript : public Script {
int index;
StringName setter;
StringName getter;
StringName observer;
MultiplayerAPI::RPCMode rpc_mode;
GDScriptDataType data_type;
};
Expand Down
1 change: 1 addition & 0 deletions modules/gdscript/gdscript_compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1895,6 +1895,7 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar
minfo.index = p_script->member_indices.size();
minfo.setter = p_class->variables[i].setter;
minfo.getter = p_class->variables[i].getter;
minfo.observer = p_class->variables[i].observer;
minfo.rpc_mode = p_class->variables[i].rpc_mode;
minfo.data_type = _gdtype_from_datatype(p_class->variables[i].data_type);

Expand Down
2 changes: 1 addition & 1 deletion modules/gdscript/gdscript_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2181,7 +2181,7 @@ static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p

static const char *_keywords[] = {
"and", "in", "not", "or", "false", "PI", "TAU", "INF", "NAN", "self", "true", "as", "assert",
"breakpoint", "class", "extends", "is", "func", "preload", "setget", "signal", "tool", "yield",
"breakpoint", "class", "extends", "is", "func", "preload", "setget", "observer", "signal", "tool", "yield",
"const", "enum", "export", "onready", "static", "var", "break", "continue", "if", "elif",
"else", "for", "pass", "return", "match", "while", "remote", "sync", "master", "puppet", "slave",
"remotesync", "mastersync", "puppetsync",
Expand Down
59 changes: 55 additions & 4 deletions modules/gdscript/gdscript_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4904,6 +4904,18 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
}
}

if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_OBSERVER) {
// observer before setget
tokenizer->advance();

if (!tokenizer->is_token_literal()) {
_set_error("Expected an identifier for the observer function after \"observer\".");
}

member.observer = tokenizer->get_token_literal();
tokenizer->advance();
}

if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_SETGET) {

tokenizer->advance();
Expand All @@ -4930,6 +4942,23 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
member.getter = tokenizer->get_token_literal();
tokenizer->advance();
}

if (tokenizer->get_token() == GDScriptTokenizer::TK_PR_OBSERVER) {
// check if observer already set
if (!(member.observer == StringName())) {
_set_error("Cannot set observer twice");
return;
}

// observer after setget section
tokenizer->advance();

if (!tokenizer->is_token_literal()) {
_set_error("Expected an identifier for the observer function after \"observer\".");
}
member.observer = tokenizer->get_token_literal();
tokenizer->advance();
}
}

p_class->variables.push_back(member);
Expand Down Expand Up @@ -7690,12 +7719,25 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
}
}

// Setter and getter
if (v.setter == StringName() && v.getter == StringName()) continue;
// Setter, getter and observer
if (v.setter == StringName() && v.getter == StringName() && v.observer == StringName()) continue;

bool found_getter = false;
bool found_setter = false;
bool found_observer = false;
for (int j = 0; j < p_class->functions.size(); j++) {
if (v.observer == p_class->functions[j]->name) {
found_observer = true;
FunctionNode *observer = p_class->functions[j];

if (observer->get_required_argument_count() != 0) {
_set_error("The observer function needs to receive exactly 0 arguments. See \"" + observer->name +
"()\" definition at line " + itos(observer->line) + ".",
v.line);
return;
}
continue;
}
if (v.setter == p_class->functions[j]->name) {
found_setter = true;
FunctionNode *setter = p_class->functions[j];
Expand Down Expand Up @@ -7734,10 +7776,10 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
return;
}
}
if (found_getter && found_setter) break;
if (found_getter && found_setter && found_observer) break;
}

if ((found_getter || v.getter == StringName()) && (found_setter || v.setter == StringName())) continue;
if ((found_getter || v.getter == StringName()) && (found_setter || v.setter == StringName()) && (found_observer || v.observer == StringName())) continue;

// Check for static functions
for (int j = 0; j < p_class->static_functions.size(); j++) {
Expand All @@ -7751,6 +7793,10 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
_set_error("The getter can't be a static function. See \"" + getter->name + "()\" definition at line " + itos(getter->line) + ".", v.line);
return;
}
if (v.observer == p_class->static_functions[j]->name) {
FunctionNode *observer = p_class->static_functions[j];
_set_error("The observer function can't be a static function. See \"" + observer->name + "()\" definitino at line " + itos(observer->line) + ".", v.line);
}
}

if (!found_setter && v.setter != StringName()) {
Expand All @@ -7762,6 +7808,11 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
_set_error("The getter function isn't defined.", v.line);
return;
}

if (!found_observer && v.observer != StringName()) {
_set_error("The observer function isn't defined.", v.line);
return;
}
}

// Inner classes
Expand Down
1 change: 1 addition & 0 deletions modules/gdscript/gdscript_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ class GDScriptParser {
DataType data_type;
StringName setter;
StringName getter;
StringName observer;
int line;
Node *expression;
OperatorNode *initial_assignment;
Expand Down
3 changes: 3 additions & 0 deletions modules/gdscript/gdscript_tokenizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ const char *GDScriptTokenizer::token_names[TK_MAX] = {
"static",
"export",
"setget",
"observer",
"const",
"var",
"as",
Expand Down Expand Up @@ -196,6 +197,7 @@ static const _kws _keyword_list[] = {
{ GDScriptTokenizer::TK_PR_TOOL, "tool" },
{ GDScriptTokenizer::TK_PR_STATIC, "static" },
{ GDScriptTokenizer::TK_PR_EXPORT, "export" },
{ GDScriptTokenizer::TK_PR_OBSERVER, "observer" },
{ GDScriptTokenizer::TK_PR_SETGET, "setget" },
{ GDScriptTokenizer::TK_PR_VAR, "var" },
{ GDScriptTokenizer::TK_PR_AS, "as" },
Expand Down Expand Up @@ -251,6 +253,7 @@ bool GDScriptTokenizer::is_token_literal(int p_offset, bool variable_safe) const
case TK_PR_STATIC:
case TK_PR_EXPORT:
case TK_PR_SETGET:
case TK_PR_OBSERVER:
case TK_PR_SIGNAL:
case TK_PR_REMOTE:
case TK_PR_MASTER:
Expand Down
1 change: 1 addition & 0 deletions modules/gdscript/gdscript_tokenizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ class GDScriptTokenizer {
TK_PR_STATIC,
TK_PR_EXPORT,
TK_PR_SETGET,
TK_PR_OBSERVER,
TK_PR_CONST,
TK_PR_VAR,
TK_PR_AS,
Expand Down

0 comments on commit 6f5b4d1

Please sign in to comment.