# warning-ignore-all:shadowed_variable # warning-ignore-all:unused_class_variable # ############################################################################ # # Copyright © 2015-2021 inkle Ltd. # Copyright © 2019-2022 Frédéric Maquin # All Rights Reserved # # This file is part of inkgd. # inkgd is licensed under the terms of the MIT license. # ############################################################################ # extends InkBase class_name InkCallStack # ############################################################################ # class Element extends InkBase: var current_pointer: InkPointer = InkPointer.null_pointer var in_expression_evaluation: bool = false var temporary_variables = null # Dictionary var type: int = 0 # Ink.PushPopType var evaluation_stack_height_when_pushed: int = 0 var function_start_in_ouput_stream: int = 0 # (Ink.PushPopType, Pointer, bool) -> InkElement func _init(type, pointer, in_expression_evaluation = false): self.current_pointer = pointer self.in_expression_evaluation = in_expression_evaluation self.temporary_variables = {} self.type = type # () -> InkElement func copy(): var copy = Element.new(self.type, self.current_pointer, self.in_expression_evaluation) copy.temporary_variables = self.temporary_variables.duplicate() copy.evaluation_stack_height_when_pushed = evaluation_stack_height_when_pushed copy.function_start_in_ouput_stream = function_start_in_ouput_stream return copy # ######################################################################## # # GDScript extra methods # ######################################################################## # func is_ink_class(type): return type == "CallStack.Element" || super.is_ink_class(type) func get_ink_class(): return "CallStack.Element" class InkThread extends InkBase: var callstack = null # Array var thread_index: int = 0 # int var previous_pointer: InkPointer = InkPointer.null_pointer func _init(static_json = null): get_static_json(static_json) callstack = [] # Dictionary, Story func _init_with(jthread_obj, story_context): thread_index = int(jthread_obj["threadIndex"]) var jthread_callstack = jthread_obj["callstack"] for jel_tok in jthread_callstack: var jelement_obj = jel_tok var push_pop_type = int(jelement_obj["type"]) var pointer = InkPointer.null_pointer var current_container_path_str = null var current_container_path_str_token = null if jelement_obj.has("cPath"): current_container_path_str_token = jelement_obj["cPath"] current_container_path_str = str(current_container_path_str_token) var thread_pointer_result = story_context.content_at_path(InkPath.new_with_components_string(current_container_path_str)) pointer = InkPointer.new(thread_pointer_result.container, int(jelement_obj["idx"])) if thread_pointer_result.obj == null: InkUtils.throw_exception( "When loading state, internal story location " + "couldn't be found: '%s'. " % current_container_path_str + "Has the story changed since this save data was created?" ) return elif thread_pointer_result.approximate: story_context.warning( "When loading state, exact internal story location " + "couldn't be found: '%s', so it was" % current_container_path_str + "approximated to '%s' " + pointer.container.path._to_string() + "to recover. Has the story changed since this save data was created?" ) var in_expression_evaluation = bool(jelement_obj["exp"]) var el = Element.new(push_pop_type, pointer, in_expression_evaluation) var temps if jelement_obj.has("temp"): temps = jelement_obj["temp"] # Dictionary el.temporary_variables = self.StaticJSON.jobject_to_dictionary_runtime_objs(temps) else: el.temporary_variables.clear() callstack.append(el) var prev_content_obj_path if jthread_obj.has("previousContentObject"): prev_content_obj_path = str(jthread_obj["previousContentObject"]) var prev_path = InkPath.new_with_components_string(prev_content_obj_path) self.previous_pointer = story_context.pointer_at_path(prev_path) # () -> InkThread func copy(): var copy = InkThread.new(self.StaticJSON) copy.thread_index = self.thread_index for e in callstack: copy.callstack.append(e.copy()) copy.previous_pointer = self.previous_pointer return copy # (SimpleJson.Writer) -> void func write_json(writer): writer.write_object_start() writer.write_property_start("callstack") writer.write_array_start() for el in self.callstack: writer.write_object_start() if !el.current_pointer.is_null: writer.write_property("cPath", el.current_pointer.container.path.components_string) writer.write_property("idx", el.current_pointer.index) writer.write_property("exp", el.in_expression_evaluation) writer.write_property("type", int(el.type)) if el.temporary_variables.size() > 0: writer.write_property_start("temp") self.StaticJSON.write_dictionary_runtime_objs(writer, el.temporary_variables) writer.write_property_end() writer.write_object_end() writer.write_array_end() writer.write_property_end() writer.write_property("threadIndex", self.thread_index) if !self.previous_pointer.is_null: writer.write_property("previousContentObject", self.previous_pointer.resolve().path._to_string()) writer.write_object_end() # ######################################################################## # # GDScript extra methods # ######################################################################## # func is_ink_class(type): return type == "CallStack.InkThread" || super.is_ink_class(type) func get_ink_class(): return "CallStack.InkThread" # ######################################################################## # static func new_with(jthread_obj, story_context, static_json = null): var thread = InkThread.new(static_json) thread._init_with(jthread_obj, story_context) return thread # ######################################################################## # var StaticJSON: InkStaticJSON: get: return _static_json.get_ref() var _static_json = WeakRef.new() func get_static_json(static_json = null): if static_json != null: _static_json = weakref(static_json) return var InkRuntime = Engine.get_main_loop().root.get_node("__InkRuntime") InkUtils.__assert__(InkRuntime != null, str("[InkCallStack.InkThread] Could not retrieve 'InkRuntime' singleton from the scene tree.")) _static_json = weakref(InkRuntime.json) # () -> Array var elements : get = get_elements func get_elements(): return self.callstack # () -> int var depth : get = get_depth func get_depth(): return self.elements.size() # () -> InkElement var current_element : get = get_current_element func get_current_element(): var thread = self._threads.back() var cs = thread.callstack return cs.back() # () -> int var current_element_index : get = get_current_element_index func get_current_element_index(): return self.callstack.size() - 1 # () -> InkThread # (InkThread) -> void var current_thread : get = get_current_thread, set = set_current_thread func get_current_thread(): return self._threads.back() func set_current_thread(value): InkUtils.__assert__(_threads.size() == 1, "Shouldn't be directly setting the current thread when we have a stack of them") self._threads.clear() self._threads.append(value) # () -> bool var can_pop : get = get_can_pop func get_can_pop(): return self.callstack.size() > 1 # (InkStory | CallStack) -> CallStack func _init(story_context_or_to_copy, static_json = null): get_static_json(static_json) if story_context_or_to_copy.is_ink_class("Story"): var story_context = story_context_or_to_copy _start_of_root = InkPointer.start_of(story_context.root_content_container) reset() elif story_context_or_to_copy.is_ink_class("CallStack"): var to_copy = story_context_or_to_copy self._threads = [] for other_thread in to_copy._threads: self._threads.append(other_thread.copy()) self._thread_counter = to_copy._thread_counter self._start_of_root = to_copy._start_of_root # () -> void func reset(): self._threads = [] self._threads.append(InkThread.new(self.StaticJSON)) self._threads[0].callstack.append(Element.new(Ink.PushPopType.TUNNEL, self._start_of_root)) # (Dictionary, InkStory) -> void func set_json_token(jobject, story_context): self._threads.clear() var jthreads = jobject["threads"] for jthread_tok in jthreads: var jthread_obj = jthread_tok var thread = InkThread.new_with(jthread_obj, story_context) self._threads.append(thread) self._thread_counter = int(jobject["threadCounter"]) self._start_of_root = InkPointer.start_of(story_context.root_content_container) # (SimpleJson.Writer) -> void func write_json(writer): writer.write_object(Callable(self, "_anonymous_write_json")) # () -> void func push_thread(): var new_thread = self.current_thread.copy() self._thread_counter += 1 new_thread.thread_index = self._thread_counter self._threads.append(new_thread) # () -> void func fork_thread(): var forked_thread = self.current_thread.copy() self._thread_counter += 1 forked_thread.thread_index = self._thread_counter return forked_thread # () -> void func pop_thread(): if self.can_pop_thread: self._threads.erase(self.current_thread) else: InkUtils.throw_exception("Can't pop thread") # () -> bool var can_pop_thread : get = get_can_pop_thread func get_can_pop_thread(): return _threads.size() > 1 && !self.element_is_evaluate_from_game # () -> bool var element_is_evaluate_from_game : get = get_element_is_evaluate_from_game func get_element_is_evaluate_from_game(): return self.current_element.type == Ink.PushPopType.FUNCTION_EVALUATION_FROM_GAME # (Ink.PushPopType, int, int) -> void func push(type, external_evaluation_stack_height = 0, output_stream_length_with_pushed = 0): var element = Element.new(type, self.current_element.current_pointer, false) element.evaluation_stack_height_when_pushed = external_evaluation_stack_height element.function_start_in_ouput_stream = output_stream_length_with_pushed self.callstack.append(element) # (Ink.PushPopType | null) -> void func can_pop_type(type = null): if !self.can_pop: return false if type == null: return true return self.current_element.type == type # (Ink.PushPopType | null) -> void func pop(type = null): if can_pop_type(type): self.callstack.pop_back() return else: InkUtils.throw_exception("Mismatched push/pop in Callstack") # (String, int) -> InkObject func get_temporary_variable_with_name(name, context_index = -1) -> InkObject: if context_index == -1: context_index = self.current_element_index + 1 var var_value = null var context_element = self.callstack[context_index - 1] if context_element.temporary_variables.has(name): var_value = context_element.temporary_variables[name] return var_value else: return null # (String, InkObject, bool, int) -> void func set_temporary_variable(name, value, declare_new, context_index = -1): if context_index == -1: context_index = self.current_element_index + 1 var context_element = self.callstack[context_index - 1] if !declare_new && !context_element.temporary_variables.has(name): InkUtils.throw_exception("Could not find temporary variable to set: %s" % name) return if context_element.temporary_variables.has(name): var old_value = context_element.temporary_variables[name] InkListValue.retain_list_origins_for_assignment(old_value, value) context_element.temporary_variables[name] = value # (String) -> int func context_for_variable_named(name): if self.current_element.temporary_variables.has(name): return self.current_element_index + 1 else: return 0 # (int) -> InkThread | null func thread_with_index(index): for thread in self._threads: if thread.thread_index == index: return thread return null var callstack : get = get_callstack func get_callstack(): return self.current_thread.callstack var callstack_trace : get = get_callstack_trace func get_callstack_trace(): var sb = "" var t = 0 while t < _threads.size(): var thread = _threads[t] var is_current = (t == _threads.size() - 1) sb += str("=== THREAD ", str(t + 1), "/", str(_threads.size()), " ", ("(current) " if is_current else "" ), "===\n") var i = 0 while i < thread.callstack.size(): if thread.callstack[i].type == Ink.PushPopType.FUNCTION: sb += " [FUNCTION] " else: sb += " [TUNNEL] " var pointer = thread.callstack[i].current_pointer if !pointer.is_null: sb += " var _thread_counter = 0 # int var _start_of_root = InkPointer.null_pointer # Pointer # ############################################################################ # # GDScript extra methods # ############################################################################ # func is_ink_class(type): return type == "CallStack" || super.is_ink_class(type) func get_ink_class(): return "CallStack" # C# Actions & Delegates ##################################################### # # (SimpleJson.Writer) -> void func _anonymous_write_json(writer: InkSimpleJSON.Writer) -> void: writer.write_property_start("threads") writer.write_array_start() for thread in self._threads: thread.write_json(writer) writer.write_array_end() writer.write_property_end() writer.write_property_start("threadCounter") writer.write(self._thread_counter) writer.write_property_end() # ######################################################################## # var StaticJSON: InkStaticJSON: get: return _static_json.get_ref() var _static_json = WeakRef.new() func get_static_json(static_json = null): if static_json != null: _static_json = weakref(static_json) return var InkRuntime = Engine.get_main_loop().root.get_node("__InkRuntime") InkUtils.__assert__(InkRuntime != null, str("[InkCallStack] Could not retrieve 'InkRuntime' singleton from the scene tree.")) _static_json = weakref(InkRuntime.json)