457 lines
14 KiB
GDScript
457 lines
14 KiB
GDScript
# warning-ignore-all:shadowed_variable
|
|
# warning-ignore-all:unused_class_variable
|
|
# ############################################################################ #
|
|
# Copyright © 2015-2021 inkle Ltd.
|
|
# Copyright © 2019-2022 Frédéric Maquin <fred@ephread.com>
|
|
# 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<String, InkObject>
|
|
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<Element>
|
|
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<string, object>, 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<string, object>
|
|
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<InkElement>
|
|
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<string, object>, 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 += "<SOMEWHERE IN "
|
|
sb += pointer.container.path._to_string()
|
|
sb += "\n>"
|
|
|
|
i += 1
|
|
t += 1
|
|
|
|
return sb
|
|
|
|
var _threads = null # Array<InkThread>
|
|
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)
|