commit ee86cc2a3ce1fff34cfea666900a021ec36f2224 Author: user Date: Fri Dec 27 21:00:07 2024 +0100 . diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e2dce33 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Redot 4+ specific ignores +.godot/ +/android/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5969b61 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,23 @@ +## Contributing to TODO Manager +Firstly, thank you for being interested in contributing to the Godot TODO Manager plugin! +TODO Manager has benefitted greatly from enthusiastic users who have suggested new features, noticed bugs, and contributed code to the plugin. + +### Code Style Guide +For the sake of clarity, TODO Manager takes advantage of GDScripts optional static typing in most circumstances. +In particular, when declaring variables use colons to infer the type where possible: + +`todo := "#TODO"` + +If the type is not obvious then explicit typing is desirable: + +`items : PoolStringArray = todo.split()` + +Typed arguments and return values for functions are required: +``` +func example(name: String, amount: int) -> Array: + # code + return array_of_names +``` + +For more info on static typing in Godot please refer to the documentation. +https://docs.godotengine.org/en/stable/getting_started/scripting/gdscript/static_typing.html diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bb9b409 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Peter DV + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b723248 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ + +### Localised READMEs + - [简体中文](READMECN.md) (Simplified Chinese) + + +# TODO Manager + +![example_image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/example1.png) + +## Simple and flexible + +- Supports GDScript, C# and GDNative +- Seamlessly integrated into the Godot dock +- Lenient syntax. Write TODOs that suit your style +- Quickly jump to lines and launch external editors + +## Customizable + +![settings_example](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/example2.png) + +- Add your own RegEx patterns +- Set colours to your liking + +## Installation + +### Method 1 (Godot Asset Library) + +The most simple way to get started using TODO Manager is to use Godot's inbuilt Asset Library to install the plugin into your project. + +#### Step 1 + +Find TODO Manager in the Godot Asset Library. +![AssetLib image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct1.png) + +#### Step 2 + +Install the package. You may want to untick the /doc folder at this point as it is not necessary for the functions of the plugin. +![Filestrcture image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct3.png) + +#### Step 4 + +Enable the plugin in the project settings. +![Project image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct4.png) + +### Method 2 (GitHub) + +#### Step 1 + +Click Download ZIP from the 'Code' dropdown. +![GitHub image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct5.png) + +#### Step 2 + +- Unzip the file and add it into your project folder. Make sure 'addons' is a subdirectory of res:// +- DO NOT change the name of the 'addons' or 'Todo_Manager' folders as this will break the saving and loading of your settings. + +#### Step 3 + +Enable the plugin in the project settings. +![Project image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct4.png) diff --git a/READMECN.md b/READMECN.md new file mode 100644 index 0000000..7a248c8 --- /dev/null +++ b/READMECN.md @@ -0,0 +1,56 @@ +# TODO Manager + + ![example_image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/example1.png) + +## 简单而灵活 + +- 支持 GDScript,C# 和 GDNative。 +- 无缝集成到 Godot dock 栏。 +- 宽松的语法,用适合你自己的风格写TODOs。 +- 快速跳转到某一行并启用外部编辑器。 + +## 可定制 + +![settings_example](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/example2.png) + +- 添加你自己的正则表达式。 +- 设置你喜欢的颜色。 + +## 安装 + +### 方法一 (Godot Asset Library) + +最简单的使用 TODO Manager 的方法,使用 Godot 内置的资源商店(Asset Library)来安装这个插件到你的项目。 + +#### 第一步 + +在资源商店搜索 TODO Manager。 +![AssetLib image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct1.png) + +#### 第二步 + +安装下载的插件,你可能需要取消勾选 /doc 文件夹,因为插件的功能不需要。 +![Filestrcture image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct3.png) + +#### 第三步 + +在项目设置里启用插件。 +![Project image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct4.png) + +### 方法二 (GitHub) + +#### 第一步 + +点击 Download ZIP。 +![GitHub image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct5.png) + +#### 第二步 + +- 解压文件并且放到你的项目文件夹。确保 “addons” 是 res:// 的子文件夹。 +- DO NOT change the name of the 'addons' or 'Todo_Manager' folders as this will break the saving and loading of your settings. +- 不要更改 “addons” 或 “Todo_Manager” 文件夹的名称,因为这会打破预设的保存和加载。 + +#### 第三步 + +在项目设置里启用这个插件。 +![Project image](https://github.com/OrigamiDev-Pete/TODO_Manager/blob/main/addons/Todo_Manager/doc/images/Instruct4.png) diff --git a/addons/Todo_Manager/ColourPicker.gd b/addons/Todo_Manager/ColourPicker.gd new file mode 100644 index 0000000..39a3f9a --- /dev/null +++ b/addons/Todo_Manager/ColourPicker.gd @@ -0,0 +1,17 @@ +@tool +extends HBoxContainer + +var colour : Color +var title : String: + set = set_title +var index : int + +@onready var colour_picker := $TODOColourPickerButton + +func _ready() -> void: + $TODOColourPickerButton.color = colour + $Label.text = title + +func set_title(value: String) -> void: + title = value + $Label.text = value diff --git a/addons/Todo_Manager/Current.gd b/addons/Todo_Manager/Current.gd new file mode 100644 index 0000000..d3961c9 --- /dev/null +++ b/addons/Todo_Manager/Current.gd @@ -0,0 +1,44 @@ +@tool +extends Panel + +signal tree_built # used for debugging + +const Todo := preload("res://addons/Todo_Manager/todo_class.gd") +const TodoItem := preload("res://addons/Todo_Manager/todoItem_class.gd") + +var _sort_alphabetical := true + +@onready var tree := $Tree as Tree + +func build_tree(todo_item : TodoItem, patterns : Array, cased_patterns : Array[String]) -> void: + tree.clear() + var root := tree.create_item() + root.set_text(0, "Scripts") + var script := tree.create_item(root) + script.set_text(0, todo_item.get_short_path() + " -------") + script.set_metadata(0, todo_item) + for todo in todo_item.todos: + var item := tree.create_item(script) + var content_header : String = todo.content + if "\n" in todo.content: + content_header = content_header.split("\n")[0] + "..." + item.set_text(0, "(%0) - %1".format([todo.line_number, content_header], "%_")) + item.set_tooltip_text(0, todo.content) + item.set_metadata(0, todo) + for i in range(0, len(cased_patterns)): + if cased_patterns[i] == todo.pattern: + item.set_custom_color(0, patterns[i][1]) + emit_signal("tree_built") + + +func sort_alphabetical(a, b) -> bool: + if a.script_path > b.script_path: + return true + else: + return false + +func sort_backwards(a, b) -> bool: + if a.script_path < b.script_path: + return true + else: + return false diff --git a/addons/Todo_Manager/Dock.gd b/addons/Todo_Manager/Dock.gd new file mode 100644 index 0000000..c71b6b2 --- /dev/null +++ b/addons/Todo_Manager/Dock.gd @@ -0,0 +1,297 @@ +@tool +extends Control + +#signal tree_built # used for debugging +enum { CASE_INSENSITIVE, CASE_SENSITIVE } + +const Project := preload("res://addons/Todo_Manager/Project.gd") +const Current := preload("res://addons/Todo_Manager/Current.gd") + +const Todo := preload("res://addons/Todo_Manager/todo_class.gd") +const TodoItem := preload("res://addons/Todo_Manager/todoItem_class.gd") +const ColourPicker := preload("res://addons/Todo_Manager/UI/ColourPicker.tscn") +const Pattern := preload("res://addons/Todo_Manager/UI/Pattern.tscn") +const DEFAULT_PATTERNS := [["\\bTODO\\b", Color("96f1ad"), CASE_INSENSITIVE], ["\\bHACK\\b", Color("d5bc70"), CASE_INSENSITIVE], ["\\bFIXME\\b", Color("d57070"), CASE_INSENSITIVE]] +const DEFAULT_SCRIPT_COLOUR := Color("ccced3") +const DEFAULT_SCRIPT_NAME := false +const DEFAULT_SORT := true + +var plugin : EditorPlugin + +var todo_items : Array + +var script_colour := Color("ccced3") +var ignore_paths : Array[String] = [] +var full_path := false +var auto_refresh := true +var builtin_enabled := false +var _sort_alphabetical := true + +var patterns := [["\\bTODO\\b", Color("96f1ad"), CASE_INSENSITIVE], ["\\bHACK\\b", Color("d5bc70"), CASE_INSENSITIVE], ["\\bFIXME\\b", Color("d57070"), CASE_INSENSITIVE]] + + +@onready var tabs := $VBoxContainer/TabContainer as TabContainer +@onready var project := $VBoxContainer/TabContainer/Project as Project +@onready var current := $VBoxContainer/TabContainer/Current as Current +@onready var project_tree := $VBoxContainer/TabContainer/Project/Tree as Tree +@onready var current_tree := $VBoxContainer/TabContainer/Current/Tree as Tree +@onready var settings_panel := $VBoxContainer/TabContainer/Settings as Panel +@onready var colours_container := $VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer3/Colours as VBoxContainer +@onready var pattern_container := $VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer4/Patterns as VBoxContainer +@onready var ignore_textbox := $VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/IgnorePaths/TextEdit as LineEdit +@onready var auto_refresh_button := $VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns/RefreshCheckButton as CheckButton + +func _ready() -> void: + load_config() + populate_settings() + + +func build_tree() -> void: + if tabs: + match tabs.current_tab: + 0: + project.build_tree(todo_items, ignore_paths, patterns, plugin.cased_patterns, _sort_alphabetical, full_path) + create_config_file() + 1: + current.build_tree(get_active_script(), patterns, plugin.cased_patterns) + create_config_file() + 2: + pass + _: + pass + + +func get_active_script() -> TodoItem: + var current_script : Script = plugin.get_editor_interface().get_script_editor().get_current_script() + if current_script: + var script_path = current_script.resource_path + for todo_item in todo_items: + if todo_item.script_path == script_path: + return todo_item + + # nothing found + var todo_item := TodoItem.new(script_path, []) + return todo_item + else: + # not a script + var todo_item := TodoItem.new("res://Documentation", []) + return todo_item + + +func go_to_script(script_path: String, line_number : int = 0) -> void: + if plugin.get_editor_interface().get_editor_settings().get_setting("text_editor/external/use_external_editor"): + var exec_path = plugin.get_editor_interface().get_editor_settings().get_setting("text_editor/external/exec_path") + var args := get_exec_flags(exec_path, script_path, line_number) + OS.execute(exec_path, args) + else: + var script := load(script_path) + plugin.get_editor_interface().edit_resource(script) + plugin.get_editor_interface().get_script_editor().goto_line(line_number - 1) + +func get_exec_flags(editor_path : String, script_path : String, line_number : int) -> PackedStringArray: + var args : PackedStringArray + var script_global_path = ProjectSettings.globalize_path(script_path) + + if editor_path.ends_with("code.cmd") or editor_path.ends_with("code"): ## VS Code + args.append(ProjectSettings.globalize_path("res://")) + args.append("--goto") + args.append(script_global_path + ":" + str(line_number)) + + elif editor_path.ends_with("rider64.exe") or editor_path.ends_with("rider"): ## Rider + args.append("--line") + args.append(str(line_number)) + args.append(script_global_path) + + else: ## Atom / Sublime + args.append(script_global_path + ":" + str(line_number)) + + return args + +func sort_alphabetical(a, b) -> bool: + if a.script_path > b.script_path: + return true + else: + return false + +func sort_backwards(a, b) -> bool: + if a.script_path < b.script_path: + return true + else: + return false + + +func populate_settings() -> void: + for i in patterns.size(): + ## Create Colour Pickers + var colour_picker: Variant = ColourPicker.instantiate() + colour_picker.colour = patterns[i][1] + colour_picker.title = patterns[i][0] + colour_picker.index = i + colours_container.add_child(colour_picker) + colour_picker.colour_picker.color_changed.connect(change_colour.bind(i)) + + ## Create Patterns + var pattern_edit: Variant = Pattern.instantiate() + pattern_edit.text = patterns[i][0] + pattern_edit.index = i + pattern_container.add_child(pattern_edit) + pattern_edit.line_edit.text_changed.connect(change_pattern.bind(i, + colour_picker)) + pattern_edit.remove_button.pressed.connect(remove_pattern.bind(i, + pattern_edit, colour_picker)) + pattern_edit.case_checkbox.button_pressed = patterns[i][2] + pattern_edit.case_checkbox.toggled.connect(case_sensitive_pattern.bind(i)) + + var pattern_button := $VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer4/Patterns/AddPatternButton + $VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer4/Patterns.move_child(pattern_button, 0) + + # path filtering + var ignore_paths_field := ignore_textbox + if not ignore_paths_field.is_connected("text_changed", _on_ignore_paths_changed): + ignore_paths_field.connect("text_changed", _on_ignore_paths_changed) + var ignore_paths_text := "" + for path in ignore_paths: + ignore_paths_text += path + ", " + ignore_paths_text = ignore_paths_text.trim_suffix(", ") + ignore_paths_field.text = ignore_paths_text + + auto_refresh_button.button_pressed = auto_refresh + + +func rebuild_settings() -> void: + for node in colours_container.get_children(): + node.queue_free() + for node in pattern_container.get_children(): + if node is Button: + continue + node.queue_free() + populate_settings() + + +#### CONFIG FILE #### +func create_config_file() -> void: + var config = ConfigFile.new() + config.set_value("scripts", "full_path", full_path) + config.set_value("scripts", "sort_alphabetical", _sort_alphabetical) + config.set_value("scripts", "script_colour", script_colour) + config.set_value("scripts", "ignore_paths", ignore_paths) + + config.set_value("patterns", "patterns", patterns) + + config.set_value("config", "auto_refresh", auto_refresh) + config.set_value("config", "builtin_enabled", builtin_enabled) + + var err = config.save("res://addons/Todo_Manager/todo.cfg") + + +func load_config() -> void: + var config := ConfigFile.new() + if config.load("res://addons/Todo_Manager/todo.cfg") == OK: + full_path = config.get_value("scripts", "full_path", DEFAULT_SCRIPT_NAME) + _sort_alphabetical = config.get_value("scripts", "sort_alphabetical", DEFAULT_SORT) + script_colour = config.get_value("scripts", "script_colour", DEFAULT_SCRIPT_COLOUR) + ignore_paths = config.get_value("scripts", "ignore_paths", [] as Array[String]) + patterns = config.get_value("patterns", "patterns", DEFAULT_PATTERNS) + auto_refresh = config.get_value("config", "auto_refresh", true) + builtin_enabled = config.get_value("config", "builtin_enabled", false) + else: + create_config_file() + + +#### Events #### +func _on_SettingsButton_toggled(button_pressed: bool) -> void: + settings_panel.visible = button_pressed + if button_pressed == false: + create_config_file() +# plugin.find_tokens_from_path(plugin.script_cache) + if auto_refresh: + plugin.rescan_files(true) + +func _on_Tree_item_activated() -> void: + var item : TreeItem + match tabs.current_tab: + 0: + item = project_tree.get_selected() + 1: + item = current_tree.get_selected() + if item.get_metadata(0) is Todo: + var todo : Todo = item.get_metadata(0) + call_deferred("go_to_script", todo.script_path, todo.line_number) + else: + var todo_item = item.get_metadata(0) + call_deferred("go_to_script", todo_item.script_path) + +func _on_FullPathCheckBox_toggled(button_pressed: bool) -> void: + full_path = button_pressed + +func _on_ScriptColourPickerButton_color_changed(color: Color) -> void: + script_colour = color + +func _on_RescanButton_pressed() -> void: + plugin.rescan_files(true) + +func change_colour(colour: Color, index: int) -> void: + patterns[index][1] = colour + +func change_pattern(value: String, index: int, this_colour: Node) -> void: + patterns[index][0] = value + this_colour.title = value + plugin.rescan_files(true) + +func remove_pattern(index: int, this: Node, this_colour: Node) -> void: + patterns.remove_at(index) + this.queue_free() + this_colour.queue_free() + plugin.rescan_files(true) + +func case_sensitive_pattern(active: bool, index: int) -> void: + if active: + patterns[index][2] = CASE_SENSITIVE + else: + patterns[index][2] = CASE_INSENSITIVE + plugin.rescan_files(true) + +func _on_DefaultButton_pressed() -> void: + patterns = DEFAULT_PATTERNS.duplicate(true) + _sort_alphabetical = DEFAULT_SORT + script_colour = DEFAULT_SCRIPT_COLOUR + full_path = DEFAULT_SCRIPT_NAME + rebuild_settings() + plugin.rescan_files(true) + +func _on_AlphSortCheckBox_toggled(button_pressed: bool) -> void: + _sort_alphabetical = button_pressed + plugin.rescan_files(true) + +func _on_AddPatternButton_pressed() -> void: + patterns.append(["\\bplaceholder\\b", Color.WHITE, CASE_INSENSITIVE]) + rebuild_settings() + +func _on_RefreshCheckButton_toggled(button_pressed: bool) -> void: + auto_refresh = button_pressed + +func _on_Timer_timeout() -> void: + plugin.refresh_lock = false + +func _on_ignore_paths_changed(new_text: String) -> void: + var text = ignore_textbox.text + var split: Array = text.split(',') + ignore_paths.clear() + for elem in split: + if elem == " " || elem == "": + continue + ignore_paths.push_front(elem.lstrip(' ').rstrip(' ')) + # validate so no empty string slips through (all paths ignored) + var i := 0 + for path in ignore_paths: + if (path == "" || path == " "): + ignore_paths.remove_at(i) + i += 1 + plugin.rescan_files(true) + +func _on_TabContainer_tab_changed(tab: int) -> void: + build_tree() + +func _on_BuiltInCheckButton_toggled(button_pressed: bool) -> void: + builtin_enabled = button_pressed + plugin.rescan_files(true) diff --git a/addons/Todo_Manager/Pattern.gd b/addons/Todo_Manager/Pattern.gd new file mode 100644 index 0000000..4e610af --- /dev/null +++ b/addons/Todo_Manager/Pattern.gd @@ -0,0 +1,21 @@ +@tool +extends HBoxContainer + + +var text : String : set = set_text +var disabled : bool +var index : int + +@onready var line_edit := $LineEdit as LineEdit +@onready var remove_button := $RemoveButton as Button +@onready var case_checkbox := %CaseSensativeCheckbox as CheckBox + +func _ready() -> void: + line_edit.text = text + remove_button.disabled = disabled + + +func set_text(value: String) -> void: + text = value + if line_edit: + line_edit.text = value diff --git a/addons/Todo_Manager/Project.gd b/addons/Todo_Manager/Project.gd new file mode 100644 index 0000000..4af0847 --- /dev/null +++ b/addons/Todo_Manager/Project.gd @@ -0,0 +1,73 @@ +@tool +extends Panel + +signal tree_built # used for debugging + +const Todo := preload("res://addons/Todo_Manager/todo_class.gd") + +var _sort_alphabetical := true +var _full_path := false + +@onready var tree := $Tree as Tree + +func build_tree(todo_items : Array, ignore_paths : Array, patterns : Array, cased_patterns: Array[String], sort_alphabetical : bool, full_path : bool) -> void: + _full_path = full_path + tree.clear() + if sort_alphabetical: + todo_items.sort_custom(Callable(self, "sort_alphabetical")) + else: + todo_items.sort_custom(Callable(self, "sort_backwards")) + var root := tree.create_item() + root.set_text(0, "Scripts") + for todo_item in todo_items: + var ignore := false + for ignore_path in ignore_paths: + var script_path : String = todo_item.script_path + if script_path.begins_with(ignore_path) or script_path.begins_with("res://" + ignore_path) or script_path.begins_with("res:///" + ignore_path): + ignore = true + break + if ignore: + continue + var script := tree.create_item(root) + if full_path: + script.set_text(0, todo_item.script_path + " -------") + else: + script.set_text(0, todo_item.get_short_path() + " -------") + script.set_metadata(0, todo_item) + for todo in todo_item.todos: + var item := tree.create_item(script) + var content_header : String = todo.content + if "\n" in todo.content: + content_header = content_header.split("\n")[0] + "..." + item.set_text(0, "(%0) - %1".format([todo.line_number, content_header], "%_")) + item.set_tooltip_text(0, todo.content) + item.set_metadata(0, todo) + for i in range(0, len(cased_patterns)): + if cased_patterns[i] == todo.pattern: + item.set_custom_color(0, patterns[i][1]) + emit_signal("tree_built") + + +func sort_alphabetical(a, b) -> bool: + if _full_path: + if a.script_path < b.script_path: + return true + else: + return false + else: + if a.get_short_path() < b.get_short_path(): + return true + else: + return false + +func sort_backwards(a, b) -> bool: + if _full_path: + if a.script_path > b.script_path: + return true + else: + return false + else: + if a.get_short_path() > b.get_short_path(): + return true + else: + return false diff --git a/addons/Todo_Manager/UI/ColourPicker.tscn b/addons/Todo_Manager/UI/ColourPicker.tscn new file mode 100644 index 0000000..650899f --- /dev/null +++ b/addons/Todo_Manager/UI/ColourPicker.tscn @@ -0,0 +1,21 @@ +[gd_scene load_steps=2 format=3 uid="uid://bie1xn8v1kd66"] + +[ext_resource type="Script" path="res://addons/Todo_Manager/ColourPicker.gd" id="1"] + +[node name="TODOColour" type="HBoxContainer"] +offset_right = 105.0 +offset_bottom = 31.0 +script = ExtResource("1") +metadata/_edit_use_custom_anchors = false + +[node name="Label" type="Label" parent="."] +offset_top = 4.0 +offset_right = 1.0 +offset_bottom = 27.0 + +[node name="TODOColourPickerButton" type="ColorPickerButton" parent="."] +custom_minimum_size = Vector2(40, 0) +offset_left = 65.0 +offset_right = 105.0 +offset_bottom = 31.0 +size_flags_horizontal = 10 diff --git a/addons/Todo_Manager/UI/Dock.tscn b/addons/Todo_Manager/UI/Dock.tscn new file mode 100644 index 0000000..096662a --- /dev/null +++ b/addons/Todo_Manager/UI/Dock.tscn @@ -0,0 +1,315 @@ +[gd_scene load_steps=6 format=3 uid="uid://b6k0dtftankcx"] + +[ext_resource type="Script" path="res://addons/Todo_Manager/Dock.gd" id="1"] +[ext_resource type="Script" path="res://addons/Todo_Manager/Project.gd" id="2"] +[ext_resource type="Script" path="res://addons/Todo_Manager/Current.gd" id="3"] + +[sub_resource type="ButtonGroup" id="ButtonGroup_kqxcu"] + +[sub_resource type="ButtonGroup" id="ButtonGroup_kltg3"] + +[node name="Dock" type="Control"] +custom_minimum_size = Vector2(0, 200) +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_vertical = 3 +script = ExtResource("1") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_top = 4.0 +grow_horizontal = 2 +grow_vertical = 2 +metadata/_edit_layout_mode = 1 + +[node name="Header" type="HBoxContainer" parent="VBoxContainer"] +visible = false +layout_mode = 2 + +[node name="HeaderLeft" type="HBoxContainer" parent="VBoxContainer/Header"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Title" type="Label" parent="VBoxContainer/Header/HeaderLeft"] +layout_mode = 2 +text = "Todo Dock:" + +[node name="HeaderRight" type="HBoxContainer" parent="VBoxContainer/Header"] +layout_mode = 2 +size_flags_horizontal = 3 +alignment = 2 + +[node name="SettingsButton" type="Button" parent="VBoxContainer/Header/HeaderRight"] +visible = false +layout_mode = 2 +toggle_mode = true +text = "Settings" + +[node name="TabContainer" type="TabContainer" parent="VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="Project" type="Panel" parent="VBoxContainer/TabContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource("2") + +[node name="Tree" type="Tree" parent="VBoxContainer/TabContainer/Project"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +hide_root = true + +[node name="Current" type="Panel" parent="VBoxContainer/TabContainer"] +visible = false +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource("3") + +[node name="Tree" type="Tree" parent="VBoxContainer/TabContainer/Current"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +hide_folding = true +hide_root = true + +[node name="Settings" type="Panel" parent="VBoxContainer/TabContainer"] +visible = false +layout_mode = 2 + +[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer/TabContainer/Settings"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Scripts" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/Scripts"] +layout_mode = 2 +text = "Scripts:" + +[node name="HSeparator" type="HSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/Scripts"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 5 + +[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer"] +layout_mode = 2 + +[node name="VSeparator" type="VSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2"] +layout_mode = 2 + +[node name="Scripts" type="VBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2"] +layout_mode = 2 + +[node name="ScriptName" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptName"] +layout_mode = 2 +text = "Script Name:" + +[node name="FullPathCheckBox" type="CheckBox" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptName"] +layout_mode = 2 +button_group = SubResource("ButtonGroup_kqxcu") +text = "Full path" + +[node name="ShortNameCheckBox" type="CheckBox" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptName"] +layout_mode = 2 +button_pressed = true +button_group = SubResource("ButtonGroup_kqxcu") +text = "Short name" + +[node name="ScriptSort" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptSort"] +layout_mode = 2 +text = "Sort Order:" + +[node name="AlphSortCheckBox" type="CheckBox" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptSort"] +layout_mode = 2 +button_pressed = true +button_group = SubResource("ButtonGroup_kltg3") +text = "Alphabetical" + +[node name="RAlphSortCheckBox" type="CheckBox" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptSort"] +layout_mode = 2 +button_group = SubResource("ButtonGroup_kltg3") +text = "Reverse Alphabetical" + +[node name="ScriptColour" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptColour"] +layout_mode = 2 +text = "Script Colour:" + +[node name="ScriptColourPickerButton" type="ColorPickerButton" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptColour"] +custom_minimum_size = Vector2(40, 0) +layout_mode = 2 +color = Color(0.8, 0.807843, 0.827451, 1) + +[node name="IgnorePaths" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/IgnorePaths"] +layout_mode = 2 +text = "Ignore Paths:" + +[node name="TextEdit" type="LineEdit" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/IgnorePaths"] +custom_minimum_size = Vector2(100, 0) +layout_mode = 2 +expand_to_text_length = true + +[node name="Label3" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/IgnorePaths"] +layout_mode = 2 +text = "(Separated by commas)" + +[node name="TODOColours" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/TODOColours"] +layout_mode = 2 +text = "TODO Colours:" + +[node name="HSeparator" type="HSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/TODOColours"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="HBoxContainer3" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="VSeparator" type="VSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer3"] +layout_mode = 2 + +[node name="Colours" type="VBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer3"] +layout_mode = 2 + +[node name="Patterns" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/Patterns"] +layout_mode = 2 +text = "Patterns:" + +[node name="HSeparator" type="HSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/Patterns"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="HBoxContainer4" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="VSeparator" type="VSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer4"] +layout_mode = 2 + +[node name="Patterns" type="VBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer4"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="AddPatternButton" type="Button" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer4/Patterns"] +layout_mode = 2 +size_flags_horizontal = 0 +text = "Add" + +[node name="Config" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/Config"] +layout_mode = 2 +text = "Config:" + +[node name="HSeparator" type="HSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/Config"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="HBoxContainer5" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="VSeparator" type="VSeparator" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5"] +layout_mode = 2 + +[node name="Patterns" type="VBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5"] +layout_mode = 2 + +[node name="RefreshCheckButton" type="CheckButton" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns"] +layout_mode = 2 +size_flags_horizontal = 0 +button_pressed = true +text = "Auto Refresh" + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns"] +layout_mode = 2 + +[node name="BuiltInCheckButton" type="CheckButton" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns/HBoxContainer"] +layout_mode = 2 +text = "Scan Built-in Scripts" + +[node name="Label" type="Label" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns/HBoxContainer"] +layout_mode = 2 + +[node name="DefaultButton" type="Button" parent="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns"] +layout_mode = 2 +size_flags_horizontal = 0 +text = "Reset to default" + +[node name="Timer" type="Timer" parent="."] +one_shot = true + +[node name="RescanButton" type="Button" parent="."] +layout_mode = 1 +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -102.0 +offset_top = 3.0 +offset_bottom = 34.0 +grow_horizontal = 0 +text = "Rescan Files" +flat = true + +[connection signal="toggled" from="VBoxContainer/Header/HeaderRight/SettingsButton" to="." method="_on_SettingsButton_toggled"] +[connection signal="tab_changed" from="VBoxContainer/TabContainer" to="." method="_on_TabContainer_tab_changed"] +[connection signal="item_activated" from="VBoxContainer/TabContainer/Project/Tree" to="." method="_on_Tree_item_activated"] +[connection signal="item_activated" from="VBoxContainer/TabContainer/Current/Tree" to="." method="_on_Tree_item_activated"] +[connection signal="toggled" from="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptName/FullPathCheckBox" to="." method="_on_FullPathCheckBox_toggled"] +[connection signal="toggled" from="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptSort/AlphSortCheckBox" to="." method="_on_AlphSortCheckBox_toggled"] +[connection signal="color_changed" from="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer2/Scripts/ScriptColour/ScriptColourPickerButton" to="." method="_on_ScriptColourPickerButton_color_changed"] +[connection signal="pressed" from="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer4/Patterns/AddPatternButton" to="." method="_on_AddPatternButton_pressed"] +[connection signal="toggled" from="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns/RefreshCheckButton" to="." method="_on_RefreshCheckButton_toggled"] +[connection signal="toggled" from="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns/HBoxContainer/BuiltInCheckButton" to="." method="_on_BuiltInCheckButton_toggled"] +[connection signal="pressed" from="VBoxContainer/TabContainer/Settings/ScrollContainer/MarginContainer/VBoxContainer/HBoxContainer5/Patterns/DefaultButton" to="." method="_on_DefaultButton_pressed"] +[connection signal="timeout" from="Timer" to="." method="_on_Timer_timeout"] +[connection signal="pressed" from="RescanButton" to="." method="_on_RescanButton_pressed"] diff --git a/addons/Todo_Manager/UI/Pattern.tscn b/addons/Todo_Manager/UI/Pattern.tscn new file mode 100644 index 0000000..fb45615 --- /dev/null +++ b/addons/Todo_Manager/UI/Pattern.tscn @@ -0,0 +1,26 @@ +[gd_scene load_steps=2 format=3 uid="uid://bx11sel2q5wli"] + +[ext_resource type="Script" path="res://addons/Todo_Manager/Pattern.gd" id="1"] + +[node name="Pattern" type="HBoxContainer"] +script = ExtResource("1") + +[node name="LineEdit" type="LineEdit" parent="."] +layout_mode = 2 +size_flags_horizontal = 0 +expand_to_text_length = true + +[node name="RemoveButton" type="Button" parent="."] +layout_mode = 2 +text = "-" + +[node name="MarginContainer" type="MarginContainer" parent="."] +custom_minimum_size = Vector2(20, 0) +layout_mode = 2 +size_flags_horizontal = 0 + +[node name="CaseSensativeCheckbox" type="CheckBox" parent="."] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 0 +text = "Case Sensitive" diff --git a/addons/Todo_Manager/plugin.cfg b/addons/Todo_Manager/plugin.cfg new file mode 100644 index 0000000..256be46 --- /dev/null +++ b/addons/Todo_Manager/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Todo Manager" +description="Dock for housing TODO messages." +author="Peter de Vroom" +version="2.3.1" +script="plugin.gd" diff --git a/addons/Todo_Manager/plugin.gd b/addons/Todo_Manager/plugin.gd new file mode 100644 index 0000000..511458d --- /dev/null +++ b/addons/Todo_Manager/plugin.gd @@ -0,0 +1,286 @@ +@tool +extends EditorPlugin + +const DockScene := preload("res://addons/Todo_Manager/UI/Dock.tscn") +const Dock := preload("res://addons/Todo_Manager/Dock.gd") +const Todo := preload("res://addons/Todo_Manager/todo_class.gd") +const TodoItem := preload("res://addons/Todo_Manager/todoItem_class.gd") + +var _dockUI : Dock + +class TodoCacheValue: + var todos: Array + var last_modified_time: int + + func _init(todos: Array, last_modified_time: int): + self.todos = todos + self.last_modified_time = last_modified_time + +var todo_cache : Dictionary # { key: script_path, value: TodoCacheValue } +var remove_queue : Array +var combined_pattern : String +var cased_patterns : Array[String] + +var refresh_lock := false # makes sure _on_filesystem_changed only triggers once + + +func _enter_tree() -> void: + _dockUI = DockScene.instantiate() as Control + add_control_to_bottom_panel(_dockUI, "TODO") + get_editor_interface().get_resource_filesystem().connect("filesystem_changed", + _on_filesystem_changed) + get_editor_interface().get_file_system_dock().connect("file_removed", queue_remove) + get_editor_interface().get_script_editor().connect("editor_script_changed", + _on_active_script_changed) + _dockUI.plugin = self + + combined_pattern = combine_patterns(_dockUI.patterns) + find_tokens_from_path(find_scripts()) + _dockUI.build_tree() + + +func _exit_tree() -> void: + _dockUI.create_config_file() + remove_control_from_bottom_panel(_dockUI) + _dockUI.free() + + +func queue_remove(file: String): + for i in _dockUI.todo_items.size() - 1: + if _dockUI.todo_items[i].script_path == file: + _dockUI.todo_items.remove_at(i) + + +func find_tokens_from_path(scripts: Array[String]) -> void: + for script_path in scripts: + var file := FileAccess.open(script_path, FileAccess.READ) + var contents := file.get_as_text() + if script_path.ends_with(".tscn"): + handle_built_in_scripts(contents, script_path) + else: + find_tokens(contents, script_path) + + +func handle_built_in_scripts(contents: String, resource_path: String): + var s := contents.split("sub_resource type=\"GDScript\"") + if s.size() <= 1: + return + for i in range(1, s.size()): + var script_components := s[i].split("script/source") + var script_name = script_components[0].substr(5, 14) + find_tokens(script_components[1], resource_path + "::" + script_name) + + +func find_tokens(text: String, script_path: String) -> void: + var cached_todos = get_cached_todos(script_path) + if cached_todos.size() != 0: +# var i := 0 +# for todo_item in _dockUI.todo_items: +# if todo_item.script_path == script_path: +# _dockUI.todo_items.remove_at(i) +# i += 1 + var todo_item := TodoItem.new(script_path, cached_todos) + _dockUI.todo_items.append(todo_item) + else: + var regex = RegEx.new() + # if regex.compile("#\\s*\\bTODO\\b.*|#\\s*\\bHACK\\b.*") == OK: + if regex.compile(combined_pattern) == OK: + var result : Array[RegExMatch] = regex.search_all(text) + if result.is_empty(): + for i in _dockUI.todo_items.size(): + if _dockUI.todo_items[i].script_path == script_path: + _dockUI.todo_items.remove_at(i) + return # No tokens found + var match_found : bool + var i := 0 + for todo_item in _dockUI.todo_items: + if todo_item.script_path == script_path: + match_found = true + var updated_todo_item := update_todo_item(todo_item, result, text, script_path) + _dockUI.todo_items.remove_at(i) + _dockUI.todo_items.insert(i, updated_todo_item) + break + i += 1 + if !match_found: + _dockUI.todo_items.append(create_todo_item(result, text, script_path)) + + +func create_todo_item(regex_results: Array[RegExMatch], text: String, script_path: String) -> TodoItem: + var todo_item = TodoItem.new(script_path, []) + todo_item.script_path = script_path + var last_line_number := 0 + var lines := text.split("\n") + for r in regex_results: + var new_todo : Todo = create_todo(r.get_string(), script_path) + new_todo.line_number = get_line_number(r.get_string(), text, last_line_number) + # GD Multiline comment + var trailing_line := new_todo.line_number + var should_break = false + while trailing_line < lines.size() and lines[trailing_line].dedent().begins_with("#"): + for other_r in regex_results: + if lines[trailing_line] in other_r.get_string(): + should_break = true + break + if should_break: + break + + new_todo.content += "\n" + lines[trailing_line] + trailing_line += 1 + + last_line_number = new_todo.line_number + todo_item.todos.append(new_todo) + cache_todos(todo_item.todos, script_path) + return todo_item + + +func update_todo_item(todo_item: TodoItem, regex_results: Array[RegExMatch], text: String, script_path: String) -> TodoItem: + todo_item.todos.clear() + var lines := text.split("\n") + for r in regex_results: + var new_todo : Todo = create_todo(r.get_string(), script_path) + new_todo.line_number = get_line_number(r.get_string(), text) + # GD Multiline comment + var trailing_line := new_todo.line_number + var should_break = false + while trailing_line < lines.size() and lines[trailing_line].dedent().begins_with("#"): + for other_r in regex_results: + if lines[trailing_line] in other_r.get_string(): + should_break = true + break + if should_break: + break + + new_todo.content += "\n" + lines[trailing_line] + trailing_line += 1 + todo_item.todos.append(new_todo) + return todo_item + + +func get_line_number(what: String, from: String, start := 0) -> int: + what = what.split('\n')[0] # Match first line of multiline C# comments + var temp_array := from.split('\n') + var lines := Array(temp_array) + var line_number# = lines.find(what) + 1 + for i in range(start, lines.size()): + if what in lines[i]: + line_number = i + 1 # +1 to account of 0-based array vs 1-based line numbers + break + else: + line_number = 0 # This is an error + return line_number + + +func _on_filesystem_changed() -> void: + if !refresh_lock: + if _dockUI.auto_refresh: + refresh_lock = true + _dockUI.get_node("Timer").start() + rescan_files(false) + + +func find_scripts() -> Array[String]: + var scripts : Array[String] + var directory_queue : Array[String] + var dir := DirAccess.open("res://") + if dir.get_open_error() == OK: + get_dir_contents(dir, scripts, directory_queue) + else: + printerr("TODO_Manager: There was an error during find_scripts()") + + while not directory_queue.is_empty(): + if dir.change_dir(directory_queue[0]) == OK: + get_dir_contents(dir, scripts, directory_queue) + else: + printerr("TODO_Manager: There was an error at: " + directory_queue[0]) + directory_queue.pop_front() + + return scripts + + +func cache_todos(todos: Array, script_path: String) -> void: + var last_modified_time = FileAccess.get_modified_time(script_path) + todo_cache[script_path] = TodoCacheValue.new(todos, last_modified_time) + + +func get_cached_todos(script_path: String) -> Array: + if todo_cache.has(script_path) and !script_path.contains("tscn::"): + var cached_value: TodoCacheValue = todo_cache[script_path] + if cached_value.last_modified_time == FileAccess.get_modified_time(script_path): + + return cached_value.todos + return [] + +func get_dir_contents(dir: DirAccess, scripts: Array[String], directory_queue: Array[String]) -> void: + dir.include_navigational = false + dir.include_hidden = false + dir.list_dir_begin() + var file_name : String = dir.get_next() + + while file_name != "": + if dir.current_is_dir(): + if file_name == ".import" or file_name == ".mono": # Skip .import folder which should never have scripts + pass + else: + directory_queue.append(dir.get_current_dir().path_join(file_name)) + else: + if file_name.ends_with(".gd") or file_name.ends_with(".cs") \ + or file_name.ends_with(".c") or file_name.ends_with(".cpp") or file_name.ends_with(".h") \ + or ((file_name.ends_with(".tscn") and _dockUI.builtin_enabled)): + scripts.append(dir.get_current_dir().path_join(file_name)) + file_name = dir.get_next() + + +func rescan_files(clear_cache: bool) -> void: + _dockUI.todo_items.clear() + if clear_cache: + todo_cache.clear() + combined_pattern = combine_patterns(_dockUI.patterns) + find_tokens_from_path(find_scripts()) + _dockUI.build_tree() + + +func combine_patterns(patterns: Array) -> String: + # Case Sensitivity + cased_patterns = [] + for pattern in patterns: + if pattern[2] == _dockUI.CASE_INSENSITIVE: + cased_patterns.append(pattern[0].insert(0, "((?i)") + ")") + else: + cased_patterns.append("(" + pattern[0] + ")") + + if patterns.size() == 1: + return cased_patterns[0] + else: + var pattern_string := "((\\/\\*)|(#|\\/\\/))\\s*(" + for i in range(patterns.size()): + if i == 0: + pattern_string += cased_patterns[i] + else: + pattern_string += "|" + cased_patterns[i] + pattern_string += ")(?(2)[\\s\\S]*?\\*\\/|.*)" + return pattern_string + + +func create_todo(todo_string: String, script_path: String) -> Todo: + var todo := Todo.new() + var regex = RegEx.new() + for pattern in cased_patterns: + if regex.compile(pattern) == OK: + var result : RegExMatch = regex.search(todo_string) + if result: + todo.pattern = pattern + todo.title = result.strings[0] + else: + continue + else: + printerr("Error compiling " + pattern) + + todo.content = todo_string + todo.script_path = script_path + return todo + + +func _on_active_script_changed(script) -> void: + if _dockUI: + if _dockUI.tabs.current_tab == 1: + _dockUI.build_tree() diff --git a/addons/Todo_Manager/todo.cfg b/addons/Todo_Manager/todo.cfg new file mode 100644 index 0000000..acef3bc --- /dev/null +++ b/addons/Todo_Manager/todo.cfg @@ -0,0 +1,15 @@ +[scripts] + +full_path=false +sort_alphabetical=true +script_colour=Color(0.8, 0.807843, 0.827451, 1) +ignore_paths=Array[String]([]) + +[patterns] + +patterns=[["\\bTODO\\b", Color(0.588235, 0.945098, 0.678431, 1), 0], ["\\bHACK\\b", Color(0.835294, 0.737255, 0.439216, 1), 0], ["\\bFIXME\\b", Color(0.835294, 0.439216, 0.439216, 1), 0]] + +[config] + +auto_refresh=true +builtin_enabled=false diff --git a/addons/Todo_Manager/todoItem_class.gd b/addons/Todo_Manager/todoItem_class.gd new file mode 100644 index 0000000..9bcb000 --- /dev/null +++ b/addons/Todo_Manager/todoItem_class.gd @@ -0,0 +1,18 @@ +@tool +extends RefCounted + +var script_path : String +var todos : Array + +func _init(script_path: String, todos: Array): + self.script_path = script_path + self.todos = todos + +func get_short_path() -> String: + var temp_array := script_path.rsplit('/', false, 1) + var short_path : String + if not temp_array.size() > 1: + short_path = "(!)" + temp_array[0] + else: + short_path = temp_array[1] + return short_path diff --git a/addons/Todo_Manager/todo_class.gd b/addons/Todo_Manager/todo_class.gd new file mode 100644 index 0000000..af6b26b --- /dev/null +++ b/addons/Todo_Manager/todo_class.gd @@ -0,0 +1,9 @@ +@tool +extends RefCounted + + +var pattern : String +var title : String +var content : String +var script_path : String +var line_number : int diff --git a/addons/console/console.gd b/addons/console/console.gd new file mode 100644 index 0000000..b85f7df --- /dev/null +++ b/addons/console/console.gd @@ -0,0 +1,427 @@ +extends Node + +var enabled := true +var enable_on_release_build := false : set = set_enable_on_release_build +var pause_enabled := false + +signal console_opened +signal console_closed +signal console_unknown_command + + +class ConsoleCommand: + var function : Callable + var arguments : PackedStringArray + var required : int + var description : String + func _init(in_function : Callable, in_arguments : PackedStringArray, in_required : int = 0, in_description : String = ""): + function = in_function + arguments = in_arguments + required = in_required + description = in_description + + +var control := Control.new() + +# If you want to customize the way the console looks, you can direcly modify +# the properties of the rich text and line edit here: +var rich_label := RichTextLabel.new() +var line_edit := LineEdit.new() + +var console_commands := {} +var console_history := [] +var console_history_index := 0 +var was_paused_already := false + +# Usage: Console.add_command("command_name", , , , "Help description") +func add_command(command_name : String, function : Callable, arguments = [], required: int = 0, description : String = "") -> void: + if arguments is int: + # Legacy call using an argument number + var param_array : PackedStringArray + for i in range(arguments): + param_array.append("arg_" + str(i + 1)) + console_commands[command_name] = ConsoleCommand.new(function, param_array, required, description) + + elif arguments is Array: + # New array argument system + var str_args : PackedStringArray + for argument in arguments: + str_args.append(str(argument)) + console_commands[command_name] = ConsoleCommand.new(function, str_args, required, description) + + +func remove_command(command_name : String) -> void: + console_commands.erase(command_name) + + +func _enter_tree() -> void: + var console_history_file := FileAccess.open("user://console_history.txt", FileAccess.READ) + if (console_history_file): + while (!console_history_file.eof_reached()): + var line := console_history_file.get_line() + if (line.length()): + add_input_history(line) + + var canvas_layer := CanvasLayer.new() + canvas_layer.layer = 3 + add_child(canvas_layer) + control.anchor_bottom = 1.0 + control.anchor_right = 1.0 + canvas_layer.add_child(control) + var style := StyleBoxFlat.new() + style.bg_color = Color("000000d7") + rich_label.selection_enabled = true + rich_label.context_menu_enabled = true + rich_label.bbcode_enabled = true + rich_label.scroll_following = true + rich_label.anchor_right = 1.0 + rich_label.anchor_bottom = 0.5 + rich_label.add_theme_stylebox_override("normal", style) + control.add_child(rich_label) + rich_label.append_text("Development console.\n") + line_edit.anchor_top = 0.5 + line_edit.anchor_right = 1.0 + line_edit.anchor_bottom = 0.5 + line_edit.placeholder_text = "Enter \"help\" for instructions" + control.add_child(line_edit) + line_edit.text_submitted.connect(on_text_entered) + line_edit.text_changed.connect(on_line_edit_text_changed) + control.visible = false + process_mode = PROCESS_MODE_ALWAYS + + +func _exit_tree() -> void: + var console_history_file := FileAccess.open("user://console_history.txt", FileAccess.WRITE) + if (console_history_file): + var write_index := 0 + var start_write_index := console_history.size() - 100 # Max lines to write + for line in console_history: + if (write_index >= start_write_index): + console_history_file.store_line(line) + write_index += 1 + + +func _ready() -> void: + add_command("quit", quit, 0, 0, "Quits the game.") + add_command("exit", quit, 0, 0, "Quits the game.") + add_command("clear", clear, 0, 0, "Clears the text on the console.") + add_command("delete_history", delete_history, 0, 0, "Deletes the history of previously entered commands.") + add_command("help", help, 0, 0, "Displays instructions on how to use the console.") + add_command("commands_list", commands_list, 0, 0, "Lists all commands and their descriptions.") + add_command("commands", commands, 0, 0, "Lists commands with no descriptions.") + add_command("calc", calculate, ["mathematical expression to evaluate"], 0, "Evaluates the math passed in for quick arithmetic.") + + +func _input(event : InputEvent) -> void: + if (event is InputEventKey): + if (event.get_physical_keycode_with_modifiers() == KEY_QUOTELEFT): # ~ key. + if (event.pressed): + toggle_console() + get_tree().get_root().set_input_as_handled() + elif (event.physical_keycode == KEY_QUOTELEFT and event.is_command_or_control_pressed()): # Toggles console size or opens big console. + if (event.pressed): + if (control.visible): + toggle_size() + else: + toggle_console() + toggle_size() + get_tree().get_root().set_input_as_handled() + elif (event.get_physical_keycode_with_modifiers() == KEY_ESCAPE && control.visible): # Disable console on ESC + if (event.pressed): + toggle_console() + get_tree().get_root().set_input_as_handled() + if (control.visible and event.pressed): + if (event.get_physical_keycode_with_modifiers() == KEY_UP): + get_tree().get_root().set_input_as_handled() + if (console_history_index > 0): + console_history_index -= 1 + if (console_history_index >= 0): + line_edit.text = console_history[console_history_index] + line_edit.caret_column = line_edit.text.length() + reset_autocomplete() + if (event.get_physical_keycode_with_modifiers() == KEY_DOWN): + get_tree().get_root().set_input_as_handled() + if (console_history_index < console_history.size()): + console_history_index += 1 + if (console_history_index < console_history.size()): + line_edit.text = console_history[console_history_index] + line_edit.caret_column = line_edit.text.length() + reset_autocomplete() + else: + line_edit.text = "" + reset_autocomplete() + if (event.get_physical_keycode_with_modifiers() == KEY_PAGEUP): + var scroll := rich_label.get_v_scroll_bar() + var tween := create_tween() + tween.tween_property(scroll, "value", scroll.value - (scroll.page - scroll.page * 0.1), 0.1) + get_tree().get_root().set_input_as_handled() + if (event.get_physical_keycode_with_modifiers() == KEY_PAGEDOWN): + var scroll := rich_label.get_v_scroll_bar() + var tween := create_tween() + tween.tween_property(scroll, "value", scroll.value + (scroll.page - scroll.page * 0.1), 0.1) + get_tree().get_root().set_input_as_handled() + if (event.get_physical_keycode_with_modifiers() == KEY_TAB): + autocomplete() + get_tree().get_root().set_input_as_handled() + + +var suggestions := [] +var current_suggest := 0 +var suggesting := false +func autocomplete() -> void: + if suggesting: + for i in range(suggestions.size()): + if current_suggest == i: + line_edit.text = str(suggestions[i]) + line_edit.caret_column = line_edit.text.length() + if current_suggest == suggestions.size() - 1: + current_suggest = 0 + else: + current_suggest += 1 + return + else: + suggesting = true + + var sorted_commands := [] + for command in console_commands: + sorted_commands.append(str(command)) + sorted_commands.sort() + sorted_commands.reverse() + + var prev_index := 0 + for command in sorted_commands: + if command.contains(line_edit.text): + var index : int = command.find(line_edit.text) + if index <= prev_index: + suggestions.push_front(command) + else: + suggestions.push_back(command) + prev_index = index + autocomplete() + + +func reset_autocomplete() -> void: + suggestions.clear() + current_suggest = 0 + suggesting = false + + +func toggle_size() -> void: + if (control.anchor_bottom == 1.0): + control.anchor_bottom = 1.9 + else: + control.anchor_bottom = 1.0 + + +func disable(): + enabled = false + toggle_console() # Ensure hidden if opened + + +func enable(): + enabled = true + + +func toggle_console() -> void: + if (enabled): + control.visible = !control.visible + else: + control.visible = false + + if (control.visible): + was_paused_already = get_tree().paused + get_tree().paused = was_paused_already || pause_enabled + line_edit.grab_focus() + console_opened.emit() + else: + control.anchor_bottom = 1.0 + scroll_to_bottom() + reset_autocomplete() + if (pause_enabled && !was_paused_already): + get_tree().paused = false + console_closed.emit() + + +func is_visible(): + return control.visible + + +func scroll_to_bottom() -> void: + var scroll: ScrollBar = rich_label.get_v_scroll_bar() + scroll.value = scroll.max_value - scroll.page + +func print_error(text : String, print_godot := false) -> void: + print_line("[color=light_coral] ERROR:[/color] %s" % text, print_godot) + + +func print_line(text : String, print_godot := false) -> void: + if (!rich_label): # Tried to print something before the console was loaded. + call_deferred("print_line", text) + else: + rich_label.append_text(text) + rich_label.append_text("\n") + if (print_godot): + print(text) + + +func parse_line_input(text : String) -> PackedStringArray: + var out_array : PackedStringArray + var first_char := true + var in_quotes := false + var escaped := false + var token : String + for c in text: + if (c == '\\'): + escaped = true + continue + elif (escaped): + if (c == 'n'): + c = '\n' + elif (c == 't'): + c = '\t' + elif (c == 'r'): + c = '\r' + elif (c == 'a'): + c = '\a' + elif (c == 'b'): + c = '\b' + elif (c == 'f'): + c = '\f' + escaped = false + elif (c == '\"'): + in_quotes = !in_quotes + continue + elif (c == ' ' || c == '\t'): + if (!in_quotes): + out_array.push_back(token) + token = "" + continue + token += c + out_array.push_back(token) + return out_array + + +func on_text_entered(new_text : String) -> void: + scroll_to_bottom() + reset_autocomplete() + line_edit.clear() + + if not new_text.strip_edges().is_empty(): + add_input_history(new_text) + print_line("[i]> " + new_text + "[/i]") + var text_split := parse_line_input(new_text) + var text_command := text_split[0] + + if console_commands.has(text_command): + var arguments := text_split.slice(1) + + # calc is a especial command that needs special treatment + if text_command.match("calc"): + var expression := "" + for word in arguments: + expression += word + console_commands[text_command].function.callv([expression]) + return + + if arguments.size() < console_commands[text_command].required: + print_error("Too few arguments! Required < %d >" % console_commands[text_command].required) + return + elif arguments.size() > console_commands[text_command].arguments.size(): + print_error("Too many arguments! < %d > Max" % console_commands[text_command].arguments.size()) + return + + # Functions fail to call if passed the incorrect number of arguments, so fill out with blank strings. + while (arguments.size() < console_commands[text_command].arguments.size()): + arguments.append("") + + console_commands[text_command].function.callv(arguments) + else: + console_unknown_command.emit(text_command) + print_error("Command not found.") + + +func on_line_edit_text_changed(new_text : String) -> void: + reset_autocomplete() + + +func quit() -> void: + get_tree().quit() + + +func clear() -> void: + rich_label.clear() + + +func delete_history() -> void: + console_history.clear() + console_history_index = 0 + DirAccess.remove_absolute("user://console_history.txt") + + +func help() -> void: + rich_label.append_text(" Built in commands: + [color=light_green]calc[/color]: Calculates a given expresion + [color=light_green]clear[/color]: Clears the registry view + [color=light_green]commands[/color]: Shows a reduced list of all the currently registered commands + [color=light_green]commands_list[/color]: Shows a detailed list of all the currently registered commands + [color=light_green]delete_hystory[/color]: Deletes the commands history + [color=light_green]quit[/color]: Quits the game + Controls: + [color=light_blue]Up[/color] and [color=light_blue]Down[/color] arrow keys to navigate commands history + [color=light_blue]PageUp[/color] and [color=light_blue]PageDown[/color] to scroll registry + [[color=light_blue]Ctr[/color] + [color=light_blue]~[/color]] to change console size between half screen and full screen + [color=light_blue]~[/color] or [color=light_blue]Esc[/color] key to close the console + [color=light_blue]Tab[/color] key to autocomplete, [color=light_blue]Tab[/color] again to cycle between matching suggestions\n\n") + + +func calculate(command : String) -> void: + var expression := Expression.new() + var error = expression.parse(command) + if error: + print_error("%s" % expression.get_error_text()) + return + var result = expression.execute() + if not expression.has_execute_failed(): + print_line(str(result)) + else: + print_error("%s" % expression.get_error_text()) + + +func commands() -> void: + var commands := [] + for command in console_commands: + commands.append(str(command)) + commands.sort() + rich_label.append_text(" ") + rich_label.append_text(str(commands) + "\n\n") + + +func commands_list() -> void: + var commands := [] + for command in console_commands: + commands.append(str(command)) + commands.sort() + + for command in commands: + var arguments_string := "" + var description : String = console_commands[command].description + for i in range(console_commands[command].arguments.size()): + if i < console_commands[command].required: + arguments_string += " [color=cornflower_blue]<" + console_commands[command].arguments[i] + ">[/color]" + else: + arguments_string += " <" + console_commands[command].arguments[i] + ">" + rich_label.append_text(" [color=light_green]%s[/color][color=gray]%s[/color]: %s\n" % [command, arguments_string, description]) + rich_label.append_text("\n") + + +func add_input_history(text : String) -> void: + if (!console_history.size() || text != console_history.back()): # Don't add consecutive duplicates + console_history.append(text) + console_history_index = console_history.size() + + +func set_enable_on_release_build(enable : bool): + enable_on_release_build = enable + if (!enable_on_release_build): + if (!OS.is_debug_build()): + disable() diff --git a/addons/console/console_plugin.gd b/addons/console/console_plugin.gd new file mode 100644 index 0000000..7c99968 --- /dev/null +++ b/addons/console/console_plugin.gd @@ -0,0 +1,11 @@ +@tool +extends EditorPlugin + + +func _enter_tree(): + print("Console plugin activated.") + add_autoload_singleton("Console", "res://addons/console/console.gd") + + +func _exit_tree(): + remove_autoload_singleton("Console") diff --git a/addons/console/plugin.cfg b/addons/console/plugin.cfg new file mode 100644 index 0000000..1e973aa --- /dev/null +++ b/addons/console/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Developer Console" +description="Developer console. Press ~ to activate it in game and execute commands." +author="jitspoe" +version="1.1.1" +script="console_plugin.gd" diff --git a/addons/reactivex/LICENSE b/addons/reactivex/LICENSE new file mode 100644 index 0000000..2564344 --- /dev/null +++ b/addons/reactivex/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Neroware + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/addons/reactivex/README.md b/addons/reactivex/README.md new file mode 100644 index 0000000..c451210 --- /dev/null +++ b/addons/reactivex/README.md @@ -0,0 +1,422 @@ +# GodotRx - Reactive Extensions for the Godot Game Engine version 4 (GDRx) + +(For the native version, +go to https://github.com/Neroware/NativeGodotRx) + +## Warning +**Untested** While it is almost a direct port of RxPY, this library has not +yet been fully battle tested in action. Proceed with caution! Test submissions +and bug reports are welcome! + +## What is GodotRx? +GodotRx (short: GDRx) is a full implementation of ReactiveX for the Godot Game +Engine 4. The code was originally ported from RxPY (see: +https://github.com/ReactiveX/RxPY) as Python shares a lot of similarities with +GDScript. + +**Why Rx?** ReactiveX allows a more declarative programming style working on +observable data streams. It encourages high cohesion +and low coupling rendering the code more easily readable and extendable. + +The Godot Engine offers a robust event system in form of signals and a seamless implementation of coroutines, making it easy to execute asynchronous code. This allows you to run code outside of the typical sequential order, which is essential for handling complex tasks like user inputs, network responses, and animations. + +In this context, an observer listens to an observable event, which triggers when something significant occurs in the program, leading to side effects in the connected instances. For example, this could be a player attacking an enemy on button press or picking up an item when a collision is detected. + +GDRx enhances this idea by converting all forms of data within the program, such as GD-signals, GD-lifecycle events, callbacks, data structures, coroutines, etc., into observable data streams that emit items. These data streams, known as 'Observables', are immutable and can be transformed using functional programming techniques to describe more complex behavior. (Say hello to Flat-Map, Filter, Reduce, and their functional friends!) + +## Installation +You can add GDRx to your Godot 4 project as followed: + +1. Download this repository as an archive. +2. Navigate to your project's root folder. +3. Extract GDRx into your project's `addons` directory. The path needs to be `res://addons/reactivex/`. +4. Ensure that the plugin is enabled. +5. Add the singleton script at `res://addons/reactivex/__gdrxsingleton__.gd` to autoload as `GDRx`. +6. GDRx should now be ready to use. Try creating a simple Observable using: + +```swift +GDRx.just(42).subscribe(func(i): print("The answer: " + str(i))) +``` + +## Features + +GDRx is a full implementation of the Observer design pattern combined with the Iterator design pattern. +The following classes are featured: + +**Observer:** An observer is an entity that listens to an observable sequence. In standard GDScript, you would connect a `Callable` to a `Signal` creating an implicit observer that receives a notification from the signal and forwards it to the corresponding callback. In GDRx, each observer follows a strict contract consisting of the three notifications `on_next(item)`, `on_error(error)` and `on_completed()`. + +**Observable:** An observable is an entity that can be subscribed to by an observer. This is achieved through the subscription-operator, a method with the signature `subscribe(on_next, on_error, on_completed)`. Whenever an observer subscribes to the observables via the subscription-method it receives notifications from the observer-observable-contract over the course of the observer's active subscription. + + +**Disposable:** Disposables are entities that represent subscriptions in GDRx. They are used for clean-up and dispose themselves automatically when they go out of scope or when the method `dispose()` is called explicitly. The subscription-operator returns the new subscription (in Godot you would call it connection) as a disposable. Since disposables delete themselves when going out of scope, their lifetime can be linked to another object's lifetime via the method `dispose_with(obj)`. +*Also a huge shoutout to Semickolon (https://github.com/semickolon/GodotRx) for his amazing +hack which automatically disposes subscriptions on instance death. Good on ya!* + +**Scheduler:** Schedulers schedule pieces of work (actions) for execution. The most prominent scheduling strategies in GDRx are as followed: + +- *ImmediateScheduler:* Schedules actions to be executed immediately. This would be equivalent to invoking the action directly. +- *TrampolineScheduler:* A scheduler with additional protection against recursive scheduling. +- *CurrentThreadScheduler:* A TrampolineScheduler that schedules actions on the current thread. This is usually the default scheduling strategy. +- *EventLoopScheduler:* Creates a new thread and schedules all actions on it. +- *NewThreadScheduler:* Creates a new thread for each scheduled action. +- *SceneTreeTimeoutScheduler:* Schedules actions for execution after a timeout has expired. The timer is based on Godot's `SceneTreeTimer`. +- *ThreadedTimeoutscheduler:* Schedules actions for execution after a timeout has expired. The timer is run on a separate thread. +- *PeriodicScheduler:* Allows periodic scheduling of actions. +- *GodotSignalScheduler:* Schedules an action for execution after a `Signal` is received. + +**Iterable:** Iterables are sequences that can be iterated using Godot's `for`-loop. They become relevant when working with observable data streams and can also be infinite. + +**Subject:** A subject implements both observable and observer behavior meaning it can receive notifications and allow other observers to subscribe to it at the same time. + +**ReactiveProperty:** A reactive property is a special form of observable that maintains a value and sends notifications whenever a change to said value occurs. Highly useful! + +**Operator:** An operator is a function taking an observable sequence and transforming it to another. A large set of functional operators can be used to transform observables. **Be careful! I have not tested them all...! Test submissions are welcome!** For more info, also check out the comments in the operator scripts! + +**Throwable:** The item of an `on_error`-notification. Raising an error sends a notification to all observers of the corresponding observable sequence, which terminates the stream ungracefully. This does not work with observables containing coroutines due to Godot's technical limitations! + +*For more information, it is recommended to read the RxPY documentation, which covers all the features of GDRx that are not directly related to the Godot Engine.* + +## Usage + +### Basics + +In GodotRx, an observer listens to an observable sequence. The `GDRx`-singleton contains a selection of constructors: + +```swift +var observable = GDRx.from([1, 2, 3, 4]) +``` + +A connection can be established via `Observable.subscribe(...)` as followed: + +```swift +var subscription = observable.subscribe( + func(i): print("next> ", i), + func(e): print("Err> ", e), + func(): print("Completed!")) +``` + +A subscription is automatically disposed whenever it goes out of scope. Do not forget to link subscription lifetime to an object via `Disposable.dispose_with(obj)`: + +```swift +GDRx.start_periodic_timer(1.0) \ + .subscribe(func(i): print("Tick: ", i)) \ + .dispose_with(self) +``` + +### Timers + +Timers were already possible either by using the `Timer`-Node or by combining a coroutine with an awaited timeout signal of a `SceneTreeTimer`. For periodic timers, code gets even more convoluted. GDRx drastically simplifies creating timers. + +```swift +func _ready(): + GDRx.start_periodic_timer(1.0) \ + .subscribe(func(i): print("Periodic: ", i)) \ + .dispose_with(self) + GDRx.start_timer(2.0) \ + .subscribe(func(i): print("One shot: ", i)) \ + .dispose_with(self) +``` + +If you want to schedule a timer running on a separate thread, the +ThreadedTimeoutScheduler Singleton allows you to do so. **Careful:** Once the thread +is started it will not stop until the interval has passed! + +```swift + GDRx.start_timer(3.0, ThreadedTimeoutScheduler.singleton()) \ + .subscribe(func(i): print("Threaded one shot: ", i)) \ + .dispose_with(self) + GDRx.start_periodic_timer(2.0, ThreadedTimeoutScheduler.singleton()) \ + .subscribe(func(i): print("Threaded periodic: ", i)) \ + .dispose_with(self) +``` + +Additionally, various process and pause modes are possible. I created +a list with various versions of the SceneTreeTimeoutScheduler for this. Access +them like this: + +```swift + Engine.time_scale = 0.5 + + var process_always = false + var process_in_physics = false + var ignore_time_scale = false + + var scheduler = SceneTreeTimeoutScheduler.singleton( + process_always, process_in_physics, ignore_time_scale) +``` + +Note that the default SceneTreeTimeoutScheduler runs at process timestep scaling with +`Engine.time_scale` and also considers pause mode. + + +### Transforming signals + +A very nice feature of GodotRx are signal transformations through observables. Let us take a simple example with two signals. Assume, we only want to execute logic, when both signals are emitted. In Godot, this would require some additional logic in the signal's callbacks. In GDRx, this can be achieved this behavior through observable transformations. + +```swift +signal signal_a(a) +signal signal_b(b) + +var combined_signal : Observable + +func _ready(): + combined_signal = GDRx.from_signal(signal_a) \ + .zip([GDRx.from_signal(signal_b)]) +``` + +Using the `from_signal`-constructor, an observable can be created on top of a signal, which emits items whenever the signal is emitted. Using the `zip`-operator, the resulting observable only emits items, when both signals have been emitted. This even has the advantages that the resulting observable can be passed around like a `Signal` instance as first-class-citizen. + +### Error handling + +GDRx features custom error handling. Raising an error inside an observable sequence causes all observers to be notified with said error. The following is an example of a safe division operation. + +```swift +var safe_division = func(a, b): + return a / b if b != 0 else DividedByZeroError.raise(-1) +var mapped = GDRx.of([6, 2, 1, 0, 2, 1]) \ + .pairwise() \ + .map(func(tup : Tuple): return safe_division.call(tup.first, tup.second)) +``` + +This code results in the sequence "3, 2" after which the observers are notified with a "DividedByZeroError" notification terminating the observable. + +**Warning** It is currently technically impossible to get the error handling working with asynchronous GDScript, meaning this will break in scenarios were errors are raised after an `await`-statement. If somebody has a solution to this problem, feel free to send me an E-Mail or answer to issue #20! + +### Coroutines + +In GDRx, sequential execution of coroutines can be managed through observable streams, which helps readability. + +```swift +var _reference + +func coroutine1(): + # ... + print("Do something.") + # ... + await get_tree().create_timer(1.0).timeout + # ... + print("Do something.") + # ... + +func coroutine3(): + await get_tree().create_timer(1.0).timeout + print("Done.") + +func _ready(): + GDRx.concat_streams([ + GDRx.from_coroutine(coroutine1), + GDRx.if_then( + func(): return self._reference != null, + GDRx.from_coroutine(func(): await self._reference.coroutine2()) + ), + GDRx.from_coroutine(coroutine3), + ]).subscribe().dispose_with(self) +``` + +### Type fixation + +GDScript is a fully dynamically typed language. This has many advantages, however, +at some point, we might want to fix types of a certain computation. +After all, variables can get type hints as well! Since Godot does not support +generic types of Observables, we can still fix the type of a sequence with the +`oftype` operator. Now observers can be sure to always receive items of the wanted type. +Generating a wrong type will cause an error notification via the `on_error` contract. Per +default, it also notifies the programmer via a push-error message in the editor. + +This would be a good style, I think: +```swift +var _obs1 : Observable +var _obs2 : Observable + +var Obs1 : Observable : #[int] + get: return self._obs1.oftype(TYPE_INT) +var Obs2 : Observable : #[RefValue] + get: return self._obs2.oftype(RefValue) + +func _ready(): + self._obs1 : Observable = GDRx.from_array([1, 2, 3]) + self._obs2 : Observable = GDRx.just(RefValue.Set(42)) +``` + +### Multithreading + +With GDRx multithreading is just one scheduling away. + +```swift +var nfs : NewThreadScheduler = NewThreadScheduler.singleton() +GDRx.just(0, nfs) \ + .repeat(10) \ + .subscribe(func(__): print("Thread ID: ", OS.get_thread_caller_id())) \ + .dispose_with(self) +``` + +Threads terminate automatically when they finish computation. No need to call `Thread.wait_to_finish()`. + +## Godot Features + +### Reactive Properties + +Reactive Properties are a special kind of Observable (and Disposable) which emit items whenever +their value is changed. This is very useful e.g. for UI implementations. +Creating a ReactiveProperty instance is straight forward. Access its contents +via the `Value` property inside the ReactiveProperty instance. + +```swift +var prop = ReactiveProperty.new(42) +prop.subscribe(func(i): print(">> ", i)) + +# Emits an item on the stream +prop.Value += 42 + +# Sends completion notification to observers and disposes the ReactiveProperty +prop.dispose() + +``` + +Sometimes we want to construct a ReactiveProperty from a class member. This can +be done via the `ReactiveProperty.FromMember()` constructor. The changed value +is reflected onto the class member, though changing the member will NOT change +the value of the ReactiveProperty. + +```swift +var _hp : int = 100 + +var _stamina : float = 1.0 +var _attack_damage : int = 100 + +func _ready(): + # Create ReactiveProperty from member + var _Hp : ReactiveProperty = ReactiveProperty.FromMember(self, "_hp") + var __ = _Hp.subscribe(func(i): print("Changed Hp ", i)) + _Hp.Value += 10 + print("Reflected: ", self._hp) +``` + +A ReadOnlyReactiveProperty with read-only access can be created via the +`ReactiveProperty.to_readonly()` method. Trying to set the value will throw +an error. + +```swift +# To ReadOnlyReactiveProperty +var Hp : ReadOnlyReactiveProperty = _Hp.to_readonly() + +# Writing to ReadOnlyReactiveProperty causes an error +GDRx.try(func(): + Hp.Value = -100 +) \ +.catch("Error", func(exc): + print("Err: ", exc) +) \ +.end_try_catch() +``` + +A ReactiveProperty can also be created from a Setter and a Getter function + +```swift +# Create Reactive Property from getter and setter +var set_stamina = func(v): + print("Setter Callback") + self._stamina = v + +var get_stamina = func() -> float: + print("Getter Callback") + return self._stamina + +var _Stamina = ReactiveProperty.FromGetSet(get_stamina, set_stamina) +_Stamina.Value = 0.8 +print("Reflected> ", self._stamina) +``` + +A ReadOnlyReactiveProperty can also represent a computational step from a set +of other properties. When one of the underlying properties is changed, the +computed ReadOnlyReactiveProperty emits an item accordingly. A computed +ReadOnlyReactiveProperty can be created via the `ReactiveProperty.Computed{n}()` +constructor. + +```swift +var Stamina : ReadOnlyReactiveProperty = _Stamina.to_readonly() +var _AttackDamage : ReactiveProperty = ReactiveProperty.FromMember( + self, "_attack_damage") +var AttackDamage : ReadOnlyReactiveProperty = _AttackDamage.to_readonly() + +# Create a computed ReadOnlyReactiveProperty +var TrueDamage : ReadOnlyReactiveProperty = ReactiveProperty.Computed2( + Stamina, AttackDamage, + func(st : float, ad : int): return (st * ad) as int +) +TrueDamage.subscribe(func(i): print("True Damage: ", i)).dispose_with(self) +_Stamina.Value = 0.2 +_AttackDamage.Value = 90 +``` + +### Reactive Collections + +A ReactiveCollection works similar to a ReactiveProperty with the main difference +that it represents not a single value but a listing of values. + +```swift +var collection : ReactiveCollection = ReactiveCollection.new(["a", "b", "c", "d", "e", "f"]) +``` + +Its constructor supports generators of type `IterableBase` as well... + +### Input Events + +Very frequent input events are included as observables: + +```swift +GDRx.on_mouse_down() \ + .filter(func(ev : InputEventMouseButton): return ev.button_index == 1) \ + .subscribe(func(__): print("Left Mouse Down!")) \ + .dispose_with(self) + +GDRx.on_mouse_double_click() \ + .filter(func(ev : InputEventMouseButton): return ev.button_index == 1) \ + .subscribe(func(__): print("Left Mouse Double-Click!")) \ + .dispose_with(self) + +GDRx.on_key_pressed(KEY_W) \ + .subscribe(func(__): print("W")) \ + .dispose_with(self) +``` + +### Frame Events + +Main frame events can be directly accessed as observables as well: + +```swift +# Do stuff before `_process(delta)` calls. +GDRx.on_idle_frame() \ + .subscribe(func(delta : float): print("delta> ", delta)) \ + .dispose_with(self) + +# Do stuff before `_physics_process(delta)` calls. +GDRx.on_physics_step() \ + .subscribe(func(delta : float): print("delta> ", delta)) \ + .dispose_with(self) + +# Emits items at pre-draw +GDRx.on_frame_pre_draw() \ + .subscribe(func(__): print("Pre Draw!")) \ + .dispose_with(self) + +# Emits items at post-draw +GDRx.on_frame_post_draw() \ + .subscribe(func(__): print("Post Draw!")) \ + .dispose_with(self) +``` + +## Final Thoughts + +I hope I could clarify the usage of GDRx a bit using some of these examples. + +I do not know if this library is useful in the case of Godot 4 but if you are +familiar with and into ReactiveX, go for it! + +## Contributing + +Contributions and bug reports are always welcome! We also invite folks to submit unit tests verifiying the functionality of GDRx ;) + +## License + +Distributed under the [MIT License](https://github.com/Neroware/GodotRx/blob/master/LICENSE). diff --git a/addons/reactivex/__gdrxinit__.gd b/addons/reactivex/__gdrxinit__.gd new file mode 100644 index 0000000..02be6fa --- /dev/null +++ b/addons/reactivex/__gdrxinit__.gd @@ -0,0 +1,24 @@ +class_name __GDRx_Init__ +## Provides access to GDRx-library types. +## +## Bridge between GDRx-library type implementations and [__GDRx_Singleton__] + +# =========================================================================== # +# Notification +# =========================================================================== # +var NotificationOnNext_ = load("res://addons/reactivex/notification/onnext.gd") +var NotificationOnError_ = load("res://addons/reactivex/notification/onerror.gd") +var NotificationOnCompleted_ = load("res://addons/reactivex/notification/oncompleted.gd") + +# =========================================================================== # +# Internals +# =========================================================================== # +var Heap_ = load("res://addons/reactivex/internal/heap.gd") +var Basic_ = load("res://addons/reactivex/internal/basic.gd") +var Concurrency_ = load("res://addons/reactivex/internal/concurrency.gd") +var Util_ = load("res://addons/reactivex/internal/utils.gd") + +# =========================================================================== # +# Pipe +# =========================================================================== # +var Pipe_ = load("res://addons/reactivex/pipe.gd") diff --git a/addons/reactivex/__gdrxobs__.gd b/addons/reactivex/__gdrxobs__.gd new file mode 100644 index 0000000..dedd8f2 --- /dev/null +++ b/addons/reactivex/__gdrxobs__.gd @@ -0,0 +1,140 @@ +class_name __GDRx_Obs__ +## Provides access to [Observable] constructor. +## +## Bridge between [Observable] constructor implementations and [__GDRx_Singleton__] + +var _Amb_ = load("res://addons/reactivex/observable/amb.gd") +var _Case_ = load("res://addons/reactivex/observable/case.gd") +var _Catch_ = load("res://addons/reactivex/observable/catch.gd") +var _CombineLatest_ = load("res://addons/reactivex/observable/combinelatest.gd") +var _Concat_ = load("res://addons/reactivex/observable/concat.gd") +var _Defer_ = load("res://addons/reactivex/observable/defer.gd") +var _Empty_ = load("res://addons/reactivex/observable/empty.gd") +var _ForkJoin_ = load("res://addons/reactivex/observable/forkjoin.gd") +var _FromCallback_ = load("res://addons/reactivex/observable/fromcallback.gd") +var _FromIterable_ = load("res://addons/reactivex/observable/fromiterable.gd") +var _Generate_ = load("res://addons/reactivex/observable/generate.gd") +var _GenerateWithRealtiveTime_ = load("res://addons/reactivex/observable/generatewithrelativetime.gd") +var _IfThen_ = load("res://addons/reactivex/observable/ifthen.gd") +var _Interval_ = load("res://addons/reactivex/observable/interval.gd") +var _Merge_ = load("res://addons/reactivex/observable/merge.gd") +var _Never_ = load("res://addons/reactivex/observable/never.gd") +var _OnErrorResumeNext_ = load("res://addons/reactivex/observable/onerrorresumenext.gd") +var _Range_ = load("res://addons/reactivex/observable/range.gd") +var _Repeat_ = load("res://addons/reactivex/observable/repeat.gd") +var _ReturnValue_ = load("res://addons/reactivex/observable/returnvalue.gd") +var _Throw_ = load("res://addons/reactivex/observable/throw.gd") +var _Timer_ = load("res://addons/reactivex/observable/timer.gd") +var _ToAsync_ = load("res://addons/reactivex/observable/toasync.gd") +var _Using_ = load("res://addons/reactivex/observable/using.gd") +var _WithLatestFrom_ = load("res://addons/reactivex/observable/withlatestfrom.gd") +var _Zip_ = load("res://addons/reactivex/observable/zip.gd") + +## See: [b]res://addons/reactivex/observable/amb.gd[/b] +func amb(sources) -> Observable: + return _Amb_.amb_(sources) + +## See: [b]res://addons/reactivex/observable/case.gd[/b] +func case(mapper : Callable, sources : Dictionary, default_source : Observable = null) -> Observable: + return _Case_.case_(mapper, sources, default_source) + +## See: [b]res://addons/reactivex/observable/catch.gd[/b] +func catch_with_iterable(sources : IterableBase) -> Observable: + return _Catch_.catch_with_iterable_(sources) + +## See: [b]res://addons/reactivex/observable/combinelatest.gd[/b] +func combine_latest(sources) -> Observable: + return _CombineLatest_.combine_latest_(sources) + +## See: [b]res://addons/reactivex/observable/concat.gd[/b] +func concat_with_iterable(sources : IterableBase) -> Observable: + return _Concat_.concat_with_iterable_(sources) + +## See: [b]res://addons/reactivex/observable/defer.gd[/b] +func defer(factory : Callable = GDRx.basic.default_factory) -> Observable: + return _Defer_.defer_(factory) + +## See: [b]res://addons/reactivex/observable/empty.gd[/b] +func empty(scheduler : SchedulerBase = null) -> Observable: + return _Empty_.empty_(scheduler) + +## See: [b]res://addons/reactivex/observable/forkjoin.gd[/b] +func fork_join(sources) -> Observable: + return _ForkJoin_.fork_join_(sources) + +## See: [b]res://addons/reactivex/observable/fromcallback.gd[/b] +func from_callback(fun : Callable = func(_args : Array, _cb : Callable): return, mapper = null) -> Callable: + return _FromCallback_.from_callback_(fun, mapper) + +## See: [b]res://addons/reactivex/observable/fromiterable.gd[/b] +func from_iterable(iterable : IterableBase, scheduler : SchedulerBase = null) -> Observable: + return _FromIterable_.from_iterable_(iterable, scheduler) + +## See: [b]res://addons/reactivex/observable/generate.gd[/b] +func generate(initial_state, condition : Callable = GDRx.basic.default_condition, iterate : Callable = GDRx.basic.identity) -> Observable: + return _Generate_.generate_(initial_state, condition, iterate) + +## See: [b]res://addons/reactivex/observable/generatewithrealtivetime.gd[/b] +func generate_with_relative_time(initial_state, condition : Callable = GDRx.basic.default_condition, iterate : Callable = GDRx.basic.identity, time_mapper : Callable = func(_state) -> float: return 1.0) -> Observable: + return _GenerateWithRealtiveTime_.generate_with_relative_time_(initial_state, condition, iterate, time_mapper) + +## See: [b]res://addons/reactivex/observable/ifthen.gd[/b] +func if_then(condition : Callable = GDRx.basic.default_condition, then_source : Observable = null, else_source : Observable = null) -> Observable: + return _IfThen_.if_then_(condition, then_source, else_source) + +## See: [b]res://addons/reactivex/observable/interval.gd[/b] +func interval(period : float, scheduler : SchedulerBase = null) -> ObservableBase: + return _Interval_.interval_(period, scheduler) + +## See: [b]res://addons/reactivex/observable/merge.gd[/b] +func merge(sources) -> Observable: + return _Merge_.merge_(sources) + +## See: [b]res://addons/reactivex/observable/never.gd[/b] +func never() -> Observable: + return _Never_.never_() + +## See: [b]res://addons/reactivex/observable/onerrorresumenext.gd[/b] +func on_error_resume_next(sources) -> Observable: + return _OnErrorResumeNext_.on_error_resume_next_(sources) + +## See: [b]res://addons/reactivex/observable/range.gd[/b] +@warning_ignore("shadowed_global_identifier") +func range(start : int, stop = null, step = null, scheduler : SchedulerBase = null) -> Observable: + return _Range_.range_(start, stop, step, scheduler) + +## See: [b]res://addons/reactivex/observable/repeat.gd[/b] +func repeat_value(value, repeat_count = null) -> Observable: + return _Repeat_.repeat_value_(value, repeat_count) + +## See: [b]res://addons/reactivex/observable/returnvalue.gd[/b] +func return_value(value, scheduler : SchedulerBase = null) -> Observable: + return _ReturnValue_.return_value_(value, scheduler) + +## See: [b]res://addons/reactivex/observable/returnvalue.gd[/b] +func from_callable(supplier : Callable, scheduler : SchedulerBase = null) -> Observable: + return _ReturnValue_.from_callable_(supplier, scheduler) + +## See: [b]res://addons/reactivex/observable/throw.gd[/b] +func throw(err, scheduler : SchedulerBase = null) -> Observable: + return _Throw_.throw_(err, scheduler) + +## See: [b]res://addons/reactivex/observable/timer.gd[/b] +func timer(duetime : float, time_absolute : bool, period = null, scheduler : SchedulerBase = null) -> Observable: + return _Timer_.timer_(duetime, time_absolute, period, scheduler) + +## See: [b]res://addons/reactivex/observable/toasync.gd[/b] +func to_async(fun : Callable, scheduler : SchedulerBase = null) -> Callable: + return _ToAsync_.to_async_(fun, scheduler) + +## See: [b]res://addons/reactivex/observable/using.gd[/b] +func using(resource_factory : Callable, observable_factory : Callable,) -> Observable: + return _Using_.using_(resource_factory, observable_factory) + +## See: [b]res://addons/reactivex/observable/withlatestfrom.gd[/b] +func with_latest_from(parent : Observable, sources) -> Observable: + return _WithLatestFrom_.with_latest_from_(parent, sources) + +## See: [b]res://addons/reactivex/observable/zip.gd[/b] +func zip(sources) -> Observable: + return _Zip_.zip_(sources) diff --git a/addons/reactivex/__gdrxop__.gd b/addons/reactivex/__gdrxop__.gd new file mode 100644 index 0000000..bd35be4 --- /dev/null +++ b/addons/reactivex/__gdrxop__.gd @@ -0,0 +1,647 @@ +class_name __GDRx_Op__ +## Provides access to [Observable] operators. +## +## Bridge between operator implementations and [__GDRx_Singleton__] + +var _RefCount_ = load("res://addons/reactivex/operators/connectable/_refcount.gd") + +var _All_ = load("res://addons/reactivex/operators/_all.gd") +var _Amb_ = load("res://addons/reactivex/operators/_amb.gd") +var _AsObservable_ = load("res://addons/reactivex/operators/_asobservable.gd") +var _Average_ = load("res://addons/reactivex/operators/_average.gd") +var _Buffer_ = load("res://addons/reactivex/operators/_buffer.gd") +var _BufferWithTime_ = load("res://addons/reactivex/operators/_bufferwithtime.gd") +var _BufferWithTimeOrCount_ = load("res://addons/reactivex/operators/_bufferwithtimeorcount.gd") +var _Catch_ = load("res://addons/reactivex/operators/_catch.gd") +var _CombineLatest_ = load("res://addons/reactivex/operators/_combinelatest.gd") +var _Concat_ = load("res://addons/reactivex/operators/_concat.gd") +var _Contains_ = load("res://addons/reactivex/operators/_contains.gd") +var _Count_ = load("res://addons/reactivex/operators/_count.gd") +var _Debounce_ = load("res://addons/reactivex/operators/_debounce.gd") +var _DefaultIfEmpty_ = load("res://addons/reactivex/operators/_defaultifempty.gd") +var _Delay_ = load("res://addons/reactivex/operators/_delay.gd") +var _DelaySubscription_ = load("res://addons/reactivex/operators/_delaysubscription.gd") +var _DelayWithMapper_ = load("res://addons/reactivex/operators/_delaywithmapper.gd") +var _Dematerialize_ = load("res://addons/reactivex/operators/_dematerialize.gd") +var _Distinct_ = load("res://addons/reactivex/operators/_distinct.gd") +var _DistinctUntilChanged_ = load("res://addons/reactivex/operators/_distinctuntilchanged.gd") +var _Do_ = load("res://addons/reactivex/operators/_do.gd") +var _DoWhile_ = load("res://addons/reactivex/operators/_dowhile.gd") +var _ElementAtOrDefault_ = load("res://addons/reactivex/operators/_elementatordefault.gd") +var _Exclusive_ = load("res://addons/reactivex/operators/_exclusive.gd") +var _Expand_ = load("res://addons/reactivex/operators/_expand.gd") +var _Filter_ = load("res://addons/reactivex/operators/_filter.gd") +var _FinallyAction_ = load("res://addons/reactivex/operators/_finallyaction.gd") +var _Find_ = load("res://addons/reactivex/operators/_find.gd") +var _First_ = load("res://addons/reactivex/operators/_first.gd") +var _FirstOrDefault_ = load("res://addons/reactivex/operators/_firstordefault.gd") +var _FlatMap_ = load("res://addons/reactivex/operators/_flatmap.gd") +var _ForkJoin_ = load("res://addons/reactivex/operators/_forkjoin.gd") +var _GroupBy_ = load("res://addons/reactivex/operators/_groupby.gd") +var _GroupByUntil_ = load("res://addons/reactivex/operators/_groupbyuntil.gd") +var _GroupJoin_ = load("res://addons/reactivex/operators/_groupjoin.gd") +var _IgnoreElements_ = load("res://addons/reactivex/operators/_ignoreelements.gd") +var _IsEmpty_ = load("res://addons/reactivex/operators/_isempty.gd") +var _Join_ = load("res://addons/reactivex/operators/_join.gd") +var _Last_ = load("res://addons/reactivex/operators/_last.gd") +var _LastOrDefault_ = load("res://addons/reactivex/operators/_lastordefault.gd") +var _Map_ = load("res://addons/reactivex/operators/_map.gd") +var _Materialize_ = load("res://addons/reactivex/operators/_materialize.gd") +var _Merge_ = load("res://addons/reactivex/operators/_merge.gd") +var _Max_ = load("res://addons/reactivex/operators/_max.gd") +var _MaxBy_ = load("res://addons/reactivex/operators/_maxby.gd") +var _Min_ = load("res://addons/reactivex/operators/_min.gd") +var _MinBy_ = load("res://addons/reactivex/operators/_minby.gd") +var _Multicast_ = load("res://addons/reactivex/operators/_multicast.gd") +var _ObserveOn_ = load("res://addons/reactivex/operators/_observeon.gd") +var _OfType_ = load("res://addons/reactivex/operators/_oftype.gd") +var _OnErrorResumeNext_ = load("res://addons/reactivex/operators/_onerrorresumenext.gd") +var _Pairwise_ = load("res://addons/reactivex/operators/_pairwise.gd") +var _Partition_ = load("res://addons/reactivex/operators/_partition.gd") +var _Pluck_ = load("res://addons/reactivex/operators/_pluck.gd") +var _Publish_ = load("res://addons/reactivex/operators/_publish.gd") +var _PublishValue_ = load("res://addons/reactivex/operators/_publishvalue.gd") +var _Reduce_ = load("res://addons/reactivex/operators/_reduce.gd") +var _Repeat_ = load("res://addons/reactivex/operators/_repeat.gd") +var _Replay_ = load("res://addons/reactivex/operators/_replay.gd") +var _Retry_ = load("res://addons/reactivex/operators/_retry.gd") +var _Sample_ = load("res://addons/reactivex/operators/_sample.gd") +var _Scan_ = load("res://addons/reactivex/operators/_scan.gd") +var _SequenceEqual_ = load("res://addons/reactivex/operators/_sequenceequal.gd") +var _Single_ = load("res://addons/reactivex/operators/_single.gd") +var _SingleOrDefault_ = load("res://addons/reactivex/operators/_singleordefault.gd") +var _Skip_ = load("res://addons/reactivex/operators/_skip.gd") +var _SkipLast_ = load("res://addons/reactivex/operators/_skiplast.gd") +var _SkipLastWithTime_ = load("res://addons/reactivex/operators/_skiplastwithtime.gd") +var _SkipUntil_ = load("res://addons/reactivex/operators/_skipuntil.gd") +var _SkipUntilWithTime_ = load("res://addons/reactivex/operators/_skipuntilwithtime.gd") +var _SkipWhile_ = load("res://addons/reactivex/operators/_skipwhile.gd") +var _SkipWithTime_ = load("res://addons/reactivex/operators/_skipwithtime.gd") +var _Slice_ = load("res://addons/reactivex/operators/_slice.gd") +var _Some_ = load("res://addons/reactivex/operators/_some.gd") +var _StartWith_ = load("res://addons/reactivex/operators/_startswith.gd") +var _SubscribeOn_ = load("res://addons/reactivex/operators/_subscribeon.gd") +var _Sum_ = load("res://addons/reactivex/operators/_sum.gd") +var _SwitchLatest_ = load("res://addons/reactivex/operators/_switchlatest.gd") +var _TakeLast_ = load("res://addons/reactivex/operators/_takelast.gd") +var _Take_ = load("res://addons/reactivex/operators/_take.gd") +var _TakeLastBuffer_ = load("res://addons/reactivex/operators/_takelastbuffer.gd") +var _TakeLastWithTime_ = load("res://addons/reactivex/operators/_takelastwithtime.gd") +var _TakeUntil_ = load("res://addons/reactivex/operators/_takeuntil.gd") +var _TakeUntilWithTime_ = load("res://addons/reactivex/operators/_takeuntilwithtime.gd") +var _TakeWhile_ = load("res://addons/reactivex/operators/_takewhile.gd") +var _TakeWithTime_ = load("res://addons/reactivex/operators/_takewithtime.gd") +var _ThrottleFirst_ = load("res://addons/reactivex/operators/_throttlefirst.gd") +var _TimeInterval_ = load("res://addons/reactivex/operators/_timeinterval.gd") +var _Timeout_ = load("res://addons/reactivex/operators/_timeout.gd") +var _TimeoutWithMapper_ = load("res://addons/reactivex/operators/_timeoutwithmapper.gd") +var _TimeStamp_ = load("res://addons/reactivex/operators/_timestamp.gd") +var _ToDict_ = load("res://addons/reactivex/operators/_todict.gd") +var _ToIterable_ = load("res://addons/reactivex/operators/_toiterable.gd") +var _ToList_ = load("res://addons/reactivex/operators/_tolist.gd") +var _ToSet_ = load("res://addons/reactivex/operators/_toset.gd") +var _WhileDo_ = load("res://addons/reactivex/operators/_whiledo.gd") +var _Window_ = load("res://addons/reactivex/operators/_window.gd") +var _WindowWithCount_ = load("res://addons/reactivex/operators/_windowwithcount.gd") +var _WindowWithTime_ = load("res://addons/reactivex/operators/_windowwithtime.gd") +var _WindowWithTimeOrCount_ = load("res://addons/reactivex/operators/_windowwithtimeorcount.gd") +var _WithLatestFrom_ = load("res://addons/reactivex/operators/_withlatestfrom.gd") +var _Zip_ = load("res://addons/reactivex/operators/_zip.gd") + +## See: [b]res://addons/reactivex/operators/connectable/_refcount.gd[/b] +func ref_count() -> Callable: + return _RefCount_.ref_count_() + +## See: [b]res://addons/reactivex/operators/_all.gd[/b] +func all(predicate : Callable) -> Callable: + return _All_.all_(predicate) + +## See: [b]res://addons/reactivex/operators/_amb.gd[/b] +func amb(right_source : Observable) -> Callable: + return _Amb_.amb_(right_source) + +## See: [b]res://addons/reactivex/operators/_asobservable.gd[/b] +func as_observable() -> Callable: + return _AsObservable_.as_observable_() + +## See: [b]res://addons/reactivex/operators/_average.gd[/b] +func average(key_mapper = null) -> Callable: + return _Average_.average_(key_mapper) + +## See: [b]res://addons/reactivex/operators/_buffer.gd[/b] +func buffer(boundaries : Observable) -> Callable: + return _Buffer_.buffer_(boundaries) + +## See: [b]res://addons/reactivex/operators/_buffer.gd[/b] +func buffer_when(closing_mapper : Callable) -> Callable: + return _Buffer_.buffer_when_(closing_mapper) + +## See: [b]res://addons/reactivex/operators/_buffer.gd[/b] +func buffer_toggle(openings : Observable, closing_mapper : Callable) -> Callable: + return _Buffer_.buffer_toggle_(openings, closing_mapper) + +## See: [b]res://addons/reactivex/operators/_buffer.gd[/b] +func buffer_with_count(count_ : int, skip_ = null) -> Callable: + return _Buffer_.buffer_with_count_(count_, skip_) + +## See: [b]res://addons/reactivex/operators/_bufferwithtime.gd[/b] +func buffer_with_time(timespan : float, timeshift = null, scheduler : SchedulerBase = null) -> Callable: + return _BufferWithTime_.buffer_with_time_(timespan, timeshift, scheduler) + +## See: [b]res://addons/reactivex/operators/_bufferwithtimeourcount.gd[/b] +func buffer_with_time_or_count(timespan : float, count_ : int, scheduler : SchedulerBase = null) -> Callable: + return _BufferWithTimeOrCount_.buffer_with_time_or_count_(timespan, count_, scheduler) + +## See: [b]res://addons/reactivex/operators/_catch.gd[/b] +func catch_handler(source : Observable, handler : Callable) -> Observable: + return _Catch_.catch_handler(source, handler) + +## See: [b]res://addons/reactivex/operators/_catch.gd[/b] +func catch(handler) -> Callable: + return _Catch_.catch_(handler) + +## See: [b]res://addons/reactivex/operators/_combinelatest.gd[/b] +func combine_latest(others) -> Callable: + return _CombineLatest_.combine_latest_(others) + +## See: [b]res://addons/reactivex/operators/_concat.gd[/b] +func concat(sources) -> Callable: + return _Concat_.concat_(sources) + +## See: [b]res://addons/reactivex/operators/_contains.gd[/b] +func contains(value, comparer = GDRx.basic.default_comparer) -> Callable: + return _Contains_.contains_(value, comparer) + +## See: [b]res://addons/reactivex/operators/_count.gd[/b] +func count(predicate = null) -> Callable: + return _Count_.count_(predicate) + +## See: [b]res://addons/reactivex/operators/_debounce.gd[/b] +func debounce(duetime : float, scheduler : SchedulerBase = null) -> Callable: + return _Debounce_.debounce_(duetime, scheduler) + +## See: [b]res://addons/reactivex/operators/_debounce.gd[/b] +func throttle_with_mapper(throttle_duration_mapper : Callable) -> Callable: + return _Debounce_.throttle_with_mapper_(throttle_duration_mapper) + +## See: [b]res://addons/reactivex/operators/_defaultifempty.gd[/b] +func default_if_empty(default_value = null) -> Callable: + return _DefaultIfEmpty_.default_if_empty_(default_value) + +## See: [b]res://addons/reactivex/operators/_delay.gd[/b] +func observable_delay_timespan(source : Observable, duetime : float, scheduler : SchedulerBase = null) -> Observable: + return _Delay_.observable_delay_timespan(source, duetime, scheduler) + +## See: [b]res://addons/reactivex/operators/_delay.gd[/b] +func delay(duetime : float, scheduler : SchedulerBase = null) -> Callable: + return _Delay_.delay_(duetime, scheduler) + +## See: [b]res://addons/reactivex/operators/_delaysubscription.gd[/b] +func delay_subscription(duetime : float, time_absolute : bool = false, scheduler : SchedulerBase = null) -> Callable: + return _DelaySubscription_.delay_subscription_(duetime, time_absolute, scheduler) + +## See: [b]res://addons/reactivex/operators/_delaywithmapper.gd[/b] +func delay_with_mapper(subscription_delay = null, delay_duration_mapper = null) -> Callable: + return _DelayWithMapper_.delay_with_mapper_(subscription_delay, delay_duration_mapper) + +## See: [b]res://addons/reactivex/operators/_dematerialize.gd[/b] +func dematerialize() -> Callable: + return _Dematerialize_.dematerialize_() + +## See: [b]res://addons/reactivex/operators/_distinct.gd[/b] +func distinct(key_mapper : Callable = GDRx.basic.identity, comparer : Callable = GDRx.basic.default_comparer) -> Callable: + return _Distinct_.distinct_(key_mapper, comparer) + +## See: [b]res://addons/reactivex/operators/_distinctuntilchanged.gd[/b] +func distinct_until_changed(key_mapper : Callable = GDRx.basic.identity, comparer : Callable = GDRx.basic.default_comparer) -> Callable: + return _DistinctUntilChanged_.distinct_until_changed_(key_mapper, comparer) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do_action(on_next = null, on_error = null, on_completed = null) -> Callable: + return _Do_.do_action_(on_next, on_error, on_completed) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do(observer : ObserverBase) -> Callable: + return _Do_.do_(observer) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do_after_next(source : Observable, after_next : Callable) -> Observable: + return _Do_.do_after_next(source, after_next) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do_on_subscribe(source : Observable, on_subscribe : Callable) -> Observable: + return _Do_.do_on_subscribe(source, on_subscribe) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do_on_dispose(source : Observable, on_dispose : Callable) -> Observable: + return _Do_.do_on_dispose(source, on_dispose) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do_on_terminate(source : Observable, on_terminate : Callable) -> Observable: + return _Do_.do_on_terminate(source, on_terminate) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do_after_terminate(source : Observable, after_terminate : Callable) -> Observable: + return _Do_.do_after_terminate(source, after_terminate) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do_finally(finally_action_ : Callable) -> Callable: + return _Do_.do_finally(finally_action_) + +## See: [b]res://addons/reactivex/operators/_dowhile.gd[/b] +func do_while(condition : Callable) -> Callable: + return _DoWhile_.do_while_(condition) + +## See: [b]res://addons/reactivex/operators/_elementordefault.gd[/b] +func element_at_or_default(index : int, has_default : bool = false, default_value = GDRx.util.GetNotSet()) -> Callable: + return _ElementAtOrDefault_.element_at_or_default_(index, has_default, default_value) + +## See: [b]res://addons/reactivex/operators/_exclusive.gd[/b] +func exclusive() -> Callable: + return _Exclusive_.exclusive_() + +## See: [b]res://addons/reactivex/operators/_expand.gd[/b] +func expand(mapper : Callable) -> Callable: + return _Expand_.expand_(mapper) + +## See: [b]res://addons/reactivex/operators/_filter.gd[/b] +func filter(predicate : Callable = GDRx.basic.default_condition) -> Callable: + return _Filter_.filter_(predicate) + +## See: [b]res://addons/reactivex/operators/_filter.gd[/b] +func filter_indexed(predicate : Callable = GDRx.basic.default_condition) -> Callable: + return _Filter_.filter_indexed_(predicate) + +## See: [b]res://addons/reactivex/operators/_finallyaction.gd[/b] +func finally_action(action : Callable) -> Callable: + return _FinallyAction_.finally_action_(action) + +## See: [b]res://addons/reactivex/operators/_find.gd[/b] +func find_value(predicate : Callable, yield_index : bool) -> Callable: + return _Find_.find_value_(predicate, yield_index) + +## See: [b]res://addons/reactivex/operators/_first.gd[/b] +func first(predicate = null) -> Callable: + return _First_.first_(predicate) + +## See: [b]res://addons/reactivex/operators/_firstordefault.gd[/b] +func first_or_default_async(has_default : bool = false, default_value = null) -> Callable: + return _FirstOrDefault_.first_or_default_async_(has_default, default_value) + +## See: [b]res://addons/reactivex/operators/_firstordefault.gd[/b] +func first_or_default(predicate = null, default_value = null) -> Callable: + return _FirstOrDefault_.first_or_default_(predicate, default_value) + +## See: [b]res://addons/reactivex/operators/_flatmap.gd[/b] +func flat_map(mapper = null) -> Callable: + return _FlatMap_.flat_map_(mapper) + +## See: [b]res://addons/reactivex/operators/_flatmap.gd[/b] +func flat_map_indexed(mapper_indexed = null) -> Callable: + return _FlatMap_.flat_map_indexed_(mapper_indexed) + +## See: [b]res://addons/reactivex/operators/_flatmap.gd[/b] +func flat_map_latest(mapper = null) -> Callable: + return _FlatMap_.flat_map_latest_(mapper) + +## See: [b]res://addons/reactivex/operators/_forkjoin.gd[/b] +func fork_join(args) -> Callable: + return _ForkJoin_.fork_join_(args) + +## See: [b]res://addons/reactivex/operators/_groupby.gd[/b] +func group_by(key_mapper : Callable, element_mapper = null, subject_mapper = null) -> Callable: + return _GroupBy_.group_by_(key_mapper, element_mapper, subject_mapper) + +## See: [b]res://addons/reactivex/operators/_groupbyuntil.gd[/b] +func group_by_until(key_mapper : Callable, duration_mapper : Callable, element_mapper = null, subject_mapper = null) -> Callable: + return _GroupByUntil_.group_by_until_(key_mapper, duration_mapper, element_mapper, subject_mapper) + +## See: [b]res://addons/reactivex/operators/_groupjoin.gd[/b] +func group_join(right : Observable, left_duration_mapper : Callable, right_duration_mapper : Callable) -> Callable: + return _GroupJoin_.group_join_(right, left_duration_mapper, right_duration_mapper) + +## See: [b]res://addons/reactivex/operators/_ignoreelements.gd[/b] +func ignore_elements() -> Callable: + return _IgnoreElements_.ignore_elements_() + +## See: [b]res://addons/reactivex/operators/_isempty.gd[/b] +func is_empty() -> Callable: + return _IsEmpty_.is_empty_() + +## See: [b]res://addons/reactivex/operators/_join.gd[/b] +func join(right : Observable, left_duration_mapper : Callable, right_duration_mapper : Callable) -> Callable: + return _Join_.join_(right, left_duration_mapper, right_duration_mapper) + +## See: [b]res://addons/reactivex/operators/_last.gd[/b] +func last(predicate = null) -> Callable: + return _Last_.last_(predicate) + +## See: [b]res://addons/reactivex/operators/_lastordefault.gd[/b] +func last_or_default_async(source : Observable, has_default : bool = false, default_value = null) -> Observable: + return _LastOrDefault_.last_or_default_async(source, has_default, default_value) + +## See: [b]res://addons/reactivex/operators/_lastordefault.gd[/b] +func last_or_default(default_value = null, predicate = null) -> Callable: + return _LastOrDefault_.last_or_default_(default_value, predicate) + +## See: [b]res://addons/reactivex/operators/_map.gd[/b] +func map(mapper : Callable = GDRx.basic.identity) -> Callable: + return _Map_.map_(mapper) + +## See: [b]res://addons/reactivex/operators/_map.gd[/b] +func map_indexed(mapper_indexed : Callable = GDRx.basic.identity) -> Callable: + return _Map_.map_indexed_(mapper_indexed) + +## See: [b]res://addons/reactivex/operators/_materialize.gd[/b] +func materialize() -> Callable: + return _Materialize_.materialize_() + +## See: [b]res://addons/reactivex/operators/_max.gd[/b] +@warning_ignore("shadowed_global_identifier") +func max(comparer = null) -> Callable: + return _Max_.max_(comparer) + +## See: [b]res://addons/reactivex/operators/_maxby.gd[/b] +func max_by(key_mapper : Callable, comparer = null) -> Callable: + return _MaxBy_.max_by_(key_mapper, comparer) + +## See: [b]res://addons/reactivex/operators/_merge.gd[/b] +func merge(sources, max_concorrent : int = -1) -> Callable: + return _Merge_.merge_(sources, max_concorrent) + +## See: [b]res://addons/reactivex/operators/_merge.gd[/b] +func merge_all() -> Callable: + return _Merge_.merge_all_() + +## See: [b]res://addons/reactivex/operators/_min.gd[/b] +@warning_ignore("shadowed_global_identifier") +func min(comparer = null) -> Callable: + return _Min_.min_(comparer) + +## See: [b]res://addons/reactivex/operators/_minby.gd[/b] +func extrema_by(source : Observable, key_mapper : Callable, comparer : Callable) -> Observable: + return _MinBy_.extrema_by(source, key_mapper, comparer) + +## See: [b]res://addons/reactivex/operators/_minby.gd[/b] +func min_by(key_mapper : Callable, comparer = null) -> Callable: + return _MinBy_.min_by_(key_mapper, comparer) + +## See: [b]res://addons/reactivex/operators/_multicast.gd[/b] +func multicast(subject : SubjectBase = null, subject_factory = null, mapper = null) -> Callable: + return _Multicast_.multicast_(subject, subject_factory, mapper) + +## See: [b]res://addons/reactivex/operators/_observeon.gd[/b] +func observe_on(scheduler : SchedulerBase) -> Callable: + return _ObserveOn_.observe_on_(scheduler) + +## See: [b]res://addons/reactivex/operators/_oftype.gd[/b] +func oftype(type, push_err : bool = true, type_equality : Callable = GDRx.basic.default_type_equality) -> Callable: + return _OfType_.oftype_(type, push_err, type_equality) + +## See: [b]res://addons/reactivex/operators/_onerrorresumenext.gd[/b] +func on_error_resume_next(second : Observable) -> Callable: + return _OnErrorResumeNext_.on_error_resume_next_(second) + +## See: [b]res://addons/reactivex/operators/_pairwise.gd[/b] +func pairwise() -> Callable: + return _Pairwise_.pairwise_() + +## See: [b]res://addons/reactivex/operators/_partiton.gd[/b] +func partition(predicate : Callable = GDRx.basic.default_condition) -> Callable: + return _Partition_.partition_(predicate) + +## See: [b]res://addons/reactivex/operators/_partition.gd[/b] +func partition_indexed(predicate_indexed : Callable = GDRx.basic.default_condition) -> Callable: + return _Partition_.partition_indexed_(predicate_indexed) + +## See: [b]res://addons/reactivex/operators/_pluck.gd[/b] +func pluck(key) -> Callable: + return _Pluck_.pluck_(key) + +## See: [b]res://addons/reactivex/operators/_pluck.gd[/b] +func pluck_attr(prop : String) -> Callable: + return _Pluck_.pluck_attr_(prop) + +## See: [b]res://addons/reactivex/operators/_publish.gd[/b] +func publish(mapper = null) -> Callable: + return _Publish_.publish_(mapper) + +## See: [b]res://addons/reactivex/operators/_publish.gd[/b] +func share() -> Callable: + return _Publish_.share_() + +## See: [b]res://addons/reactivex/operators/_publishvalue.gd[/b] +func publish_value(initial_value, mapper = null) -> Callable: + return _PublishValue_.publish_value_(initial_value, mapper) + +## See: [b]res://addons/reactivex/operators/_reduce.gd[/b] +func reduce(accumulator : Callable, seed_ = GDRx.util.GetNotSet()) -> Callable: + return _Reduce_.reduce_(accumulator, seed_) + +## See: [b]res://addons/reactivex/operators/_repeat.gd[/b] +func repeat(repeat_count = null) -> Callable: + return _Repeat_.repeat_(repeat_count) + +## See: [b]res://addons/reactivex/operators/_replay.gd[/b] +func replay(mapper = null, buffer_size = null, window_ = null, scheduler : SchedulerBase = null) -> Callable: + return _Replay_.replay_(mapper, buffer_size, window_, scheduler) + +## See: [b]res://addons/reactivex/operators/_retry.gd[/b] +func retry(retry_count : int = -1) -> Callable: + return _Retry_.retry_(retry_count) + +## See: [b]res://addons/reactivex/operators/_sample.gd[/b] +func sample_observable(source : Observable, sampler : Observable) -> Observable: + return _Sample_.sample_observable(source, sampler) + +## See: [b]res://addons/reactivex/operators/_sample.gd[/b] +func sample(sampler : Observable, sampler_time : float = NAN, scheduler : SchedulerBase = null) -> Callable: + return _Sample_.sample_(sampler, sampler_time, scheduler) + +## See: [b]res://addons/reactivex/operators/_scan.gd[/b] +func scan(accumulator : Callable, seed_ = GDRx.util.GetNotSet()) -> Callable: + return _Scan_.scan_(accumulator, seed_) + +## See: [b]res://addons/reactivex/operators/_sequenceequal.gd[/b] +func sequence_equal(second, comparer = null, second_it : IterableBase = null) -> Callable: + return _SequenceEqual_.sequence_equal_(second, comparer, second_it) + +## See: [b]res://addons/reactivex/operators/_single.gd[/b] +func single(predicate = null) -> Callable: + return _Single_.single_(predicate) + +## See: [b]res://addons/reactivex/operators/_singleordefault.gd[/b] +func single_or_default_async(has_default : bool = false, default_value = null) -> Callable: + return _SingleOrDefault_.single_or_default_async_(has_default, default_value) + +## See: [b]res://addons/reactivex/operators/_singleordefault.gd[/b] +func single_or_default(predicate = null, default_value = null) -> Callable: + return _SingleOrDefault_.single_or_default_(predicate, default_value) + +## See: [b]res://addons/reactivex/operators/_skip.gd[/b] +func skip(count_ : int) -> Callable: + return _Skip_.skip_(count_) + +## See: [b]res://addons/reactivex/operators/_skiplast.gd[/b] +func skip_last(count_ : int) -> Callable: + return _SkipLast_.skip_last_(count_) + +## See: [b]res://addons/reactivex/operators/_skiplastwithtime.gd[/b] +func skip_last_with_time(duration : float, scheduler : SchedulerBase = null) -> Callable: + return _SkipLastWithTime_.skip_last_with_time_(duration, scheduler) + +## See: [b]res://addons/reactivex/operators/_skipuntil.gd[/b] +func skip_until(other : Observable) -> Callable: + return _SkipUntil_.skip_until_(other) + +## See: [b]res://addons/reactivex/operators/_skipuntilwithtime.gd[/b] +func skip_until_with_time(start_time : float, time_absolute : bool = false, scheduler : SchedulerBase = null) -> Callable: + return _SkipUntilWithTime_.skip_until_with_time_(start_time, time_absolute, scheduler) + +## See: [b]res://addons/reactivex/operators/_skipwhile.gd[/b] +func skip_while(predicate : Callable) -> Callable: + return _SkipWhile_.skip_while_(predicate) + +## See: [b]res://addons/reactivex/operators/_skipwhile.gd[/b] +func skip_while_indexed(predicate : Callable) -> Callable: + return _SkipWhile_.skip_while_indexed_(predicate) + +## See: [b]res://addons/reactivex/operators/_skipwithtime.gd[/b] +func skip_with_time(duration : float, scheduler : SchedulerBase = null) -> Callable: + return _SkipWithTime_.skip_with_time_(duration, scheduler) + +## See: [b]res://addons/reactivex/operators/_slice.gd[/b] +func slice(start : int = 0, stop : int = GDRx.util.MAX_SIZE, step : int = 1) -> Callable: + return _Slice_.slice_(start, stop, step) + +## See: [b]res://addons/reactivex/operators/_some.gd[/b] +func some(predicate = null) -> Callable: + return _Some_.some_(predicate) + +## See: [b]res://addons/reactivex/operators/_startswith.gd[/b] +func start_with(args) -> Callable: + return _StartWith_.start_with_(args) + +## See: [b]res://addons/reactivex/operators/_subscribeon.gd[/b] +func subscribe_on(scheduler : SchedulerBase) -> Callable: + return _SubscribeOn_.subscribe_on_(scheduler) + +## See: [b]res://addons/reactivex/operators/_sum.gd[/b] +func sum(key_mapper = null) -> Callable: + return _Sum_.sum_(key_mapper) + +## See: [b]res://addons/reactivex/operators/_switchlatest.gd[/b] +func switch_latest() -> Callable: + return _SwitchLatest_.switch_latest_() + +## See: [b]res://addons/reactivex/operators/_take.gd[/b] +func take(count_ : int) -> Callable: + return _Take_.take_(count_) + +## See: [b]res://addons/reactivex/operators/_takelast.gd[/b] +func take_last(count_ : int) -> Callable: + return _TakeLast_.take_last_(count_) + +## See: [b]res://addons/reactivex/operators/_takelastbuffer.gd[/b] +func take_last_buffer(count_ : int) -> Callable: + return _TakeLastBuffer_.take_last_buffer_(count_) + +## See: [b]res://addons/reactivex/operators/_takelastwithtime.gd[/b] +func take_last_with_time(duration : float, scheduler : SchedulerBase = null) -> Callable: + return _TakeLastWithTime_.take_last_with_time_(duration, scheduler) + +## See: [b]res://addons/reactivex/operators/_takeuntil.gd[/b] +func take_until(other : Observable) -> Callable: + return _TakeUntil_.take_until_(other) + +## See: [b]res://addons/reactivex/operators/_takeuntilwithtime.gd[/b] +func take_until_with_time(end_time : float, absolute : bool = false, scheduler : SchedulerBase = null) -> Callable: + return _TakeUntilWithTime_.take_until_with_time_(end_time, absolute, scheduler) + +## See: [b]res://addons/reactivex/operators/_takewhile.gd[/b] +func take_while(predicate : Callable = GDRx.basic.default_condition, inclusive : bool = false) -> Callable: + return _TakeWhile_.take_while_(predicate, inclusive) + +## See: [b]res://addons/reactivex/operators/_takewhile.gd[/b] +func take_while_indexed(predicate : Callable = GDRx.basic.default_condition, inclusive : bool = false) -> Callable: + return _TakeWhile_.take_while_indexed_(predicate, inclusive) + +## See: [b]res://addons/reactivex/operators/_takewithtime.gd[/b] +func take_with_time(duration : float, scheduler : SchedulerBase = null) -> Callable: + return _TakeWithTime_.take_with_time_(duration, scheduler) + +## See: [b]res://addons/reactivex/operators/_throttlefirst.gd[/b] +func throttle_first(window_duration : float, scheduler : SchedulerBase = null) -> Callable: + return _ThrottleFirst_.throttle_first_(window_duration, scheduler) + +## See: [b]res://addons/reactivex/operators/_timeinterval.gd[/b] +func time_interval(scheduler : SchedulerBase = null) -> Callable: + return _TimeInterval_.time_interval_(scheduler) + +## See: [b]res://addons/reactivex/operators/_timeout.gd[/b] +func timeout(duetime : float, absolute : bool = false, other : Observable = null, scheduler : SchedulerBase = null) -> Callable: + return _Timeout_.timeout_(duetime, absolute, other, scheduler) + +## See: [b]res://addons/reactivex/operators/_timeoutwithmapper.gd[/b] +func timeout_with_mapper(first_timeout : Observable = null, timeout_duration_mapper : Callable = func(__) -> Observable: return GDRx.obs.never(), other : Observable = null) -> Callable: + return _TimeoutWithMapper_.timeout_with_mapper_(first_timeout, timeout_duration_mapper, other) + +## See: [b]res://addons/reactivex/operators/_timestamp.gd[/b] +func timestamp(scheduler : SchedulerBase = null) -> Callable: + return _TimeStamp_.timestamp_(scheduler) + +## See: [b]res://addons/reactivex/operators/_todict.gd[/b] +func to_dict(key_mapper : Callable, element_mapper : Callable = GDRx.basic.identity) -> Callable: + return _ToDict_.to_dict_(key_mapper, element_mapper) + +## See: [b]res://addons/reactivex/operators/_toiterable.gd[/b] +func to_iterable() -> Callable: + return _ToIterable_.to_iterable_() + +## See: [b]res://addons/reactivex/operators/_tolist.gd[/b] +func to_list() -> Callable: + return _ToList_.to_list_() + +## See: [b]res://addons/reactivex/operators/_toset.gd[/b] +func to_set() -> Callable: + return _ToSet_.to_set_() + +## See: [b]res://addons/reactivex/operators/_whiledo.gd[/b] +func while_do(condition : Callable = GDRx.basic.default_condition) -> Callable: + return _WhileDo_.while_do_(condition) + +## See: [b]res://addons/reactivex/operators/_window.gd[/b] +func window_toggle(openings : Observable, closing_mapper : Callable) -> Callable: + return _Window_.window_toggle_(openings, closing_mapper) + +## See: [b]res://addons/reactivex/operators/_window.gd[/b] +func window(boundaries : Observable) -> Callable: + return _Window_.window_(boundaries) + +## See: [b]res://addons/reactivex/operators/_window.gd[/b] +func window_when(closing_mapper : Callable) -> Callable: + return _Window_.window_when_(closing_mapper) + +## See: [b]res://addons/reactivex/operators/_windowwithcount.gd[/b] +func window_with_count(count_ : int, skip_ = null) -> Callable: + return _WindowWithCount_.window_with_count_(count_, skip_) + +## See: [b]res://addons/reactivex/operators/_windowwithtime.gd[/b] +func window_with_time(timespan : float, timeshift = null, scheduler : SchedulerBase = null) -> Callable: + return _WindowWithTime_.window_with_time_(timespan, timeshift, scheduler) + +## See: [b]res://addons/reactivex/operators/_windowwithtimeorcount.gd[/b] +func window_with_time_or_count(timespan : float, count_ : int, scheduler : SchedulerBase = null) -> Callable: + return _WindowWithTimeOrCount_.window_with_time_or_count_(timespan, count_, scheduler) + +## See: [b]res://addons/reactivex/operators/_withlatestfrom.gd[/b] +func with_latest_from(sources) -> Callable: + return _WithLatestFrom_.with_latest_from_(sources) + +## See: [b]res://addons/reactivex/operators/_zip.gd[/b] +func zip(args) -> Callable: + return _Zip_.zip_(args) + +## See: [b]res://addons/reactivex/operators/_zip.gd[/b] +func zip_with_iterable(seq : IterableBase) -> Callable: + return _Zip_.zip_with_iterable_(seq) diff --git a/addons/reactivex/__gdrxsingleton__.gd b/addons/reactivex/__gdrxsingleton__.gd new file mode 100644 index 0000000..a46be74 --- /dev/null +++ b/addons/reactivex/__gdrxsingleton__.gd @@ -0,0 +1,528 @@ +extends Node +class_name __GDRx_Singleton__ + +## GDRx Singleton Script +## +## Provides access to [Observable] constructors and operators. +## [color=yellow] Please add to autoload with name [code]GDRx[/code]![/color] + +# =========================================================================== # +# Init script database +# =========================================================================== # +## Access to internals +var __init__ : __GDRx_Init__ = __GDRx_Init__.new() +## [Observable] constructor functions +var obs : __GDRx_Obs__ = __GDRx_Obs__.new() +## [Observable] operator functions +var op : __GDRx_Op__ = __GDRx_Op__.new() +## Engine Backend +var gd : __GDRx_Engine__ = __GDRx_Engine__.new() +## See [OnNextNotification] +var OnNext = __init__.NotificationOnNext_ +## See [OnErrorNotification] +var OnError = __init__.NotificationOnError_ +## See [OnCompletedNotification] +var OnCompleted = __init__.NotificationOnCompleted_ + +## Internal heap implementation +var heap = __init__.Heap_.new() +## Basic functions & types +var basic = __init__.Basic_.new() +## Concurrency functions & types +var concur = __init__.Concurrency_.new() +## Utility functions & types +var util = __init__.Util_.new() +## Access to pipe operators +var pipe = __init__.Pipe_.new() + +# =========================================================================== # +# Constructor/Destructor +# =========================================================================== # + +func _init(): + ## Insert Main Thread + if true: + var reglock : ReadWriteLock = self.THREAD_MANAGER.THREAD_REGISTRY.at(0) + reglock.w_lock() + self.THREAD_MANAGER.THREAD_REGISTRY.at(1)[MAIN_THREAD_ID] = MAIN_THREAD + reglock.w_unlock() + + ## Init [SceneTreeScheduler] singleton + for i in range(8): + var process_always : bool = bool(i & 0b1) + var process_in_physics : bool = bool(i & 0b10) + var ignore_time_scale : bool = bool(i & 0b100) + self.SceneTreeTimeoutScheduler_.push_back(SceneTreeTimeoutScheduler.new( + "GDRx", process_always, process_in_physics, ignore_time_scale)) + +func _notification(what): + if what == NOTIFICATION_PREDELETE: + self.THREAD_MANAGER.stop_and_join() + self.THREAD_MANAGER.free() + +# =========================================================================== # +# Multi-Threading +# =========================================================================== # + +## Dummy instance for the Main Thread +var MAIN_THREAD = concur.MainThreadDummy_.new() +## ID of the main thread +var MAIN_THREAD_ID = OS.get_thread_caller_id() +## Thread Manager +var THREAD_MANAGER = concur.ThreadManager.new() + +## Returns the caller's current [Thread] +## If the caller thread is the main thread, it returns [b]MAIN_THREAD[/b]. +func get_current_thread() -> Thread: + var id = OS.get_thread_caller_id() + var l : ReadWriteLock = self.THREAD_MANAGER.THREAD_REGISTRY.at(0) + l.r_lock() + id = self.THREAD_MANAGER.THREAD_REGISTRY.at(1)[id] + l.r_unlock() + return id + +# =========================================================================== # +# Scheduler Singletons +# =========================================================================== # + +## [ImmediateScheduler] Singleton; [color=red]Do [b]NOT[/b] access directly![/color] +var ImmediateScheduler_ : ImmediateScheduler = ImmediateScheduler.new("GDRx") +## [SceneTreeTimeoutScheduler] Singleton; [color=red]Do [b]NOT[/b] access directly![/color] +var SceneTreeTimeoutScheduler_ : Array[SceneTreeTimeoutScheduler] +## [ThreadedTimeoutScheduler] Singleton; [color=red]Do [b]NOT[/b] access directly![/color] +var ThreadedTimeoutScheduler_ : ThreadedTimeoutScheduler = ThreadedTimeoutScheduler.new("GDRx") +## [NewThreadScheduler] Singleton; [color=red]Do [b]NOT[/b] access directly![/color] +var NewThreadScheduler_ : NewThreadScheduler = NewThreadScheduler.new(self.concur.default_thread_factory) +## [GodotSignalScheduler] Singleton; [color=red]Do [b]NOT[/b] access directly![/color] +var GodotSignalScheduler_ : GodotSignalScheduler = GodotSignalScheduler.new("GDRx") + +## Global singleton of [CurrentThreadScheduler] +var CurrentThreadScheduler_global_ : WeakKeyDictionary = WeakKeyDictionary.new() +## Thread local singleton of [CurrentThreadScheduler] +var CurrentThreadScheduler_local_ = CurrentThreadScheduler._Local.new() + +# =========================================================================== # +# Error Handler Singleton +# =========================================================================== # +## [ErrorHandler] singleton; [color=red]Leave it alone![/color] +var ErrorHandler_ : WeakKeyDictionary = WeakKeyDictionary.new() + +# =========================================================================== # +# Helper functions +# =========================================================================== # + +## Equality +func eq(x, y) -> bool: + return GDRx.basic.default_comparer(x, y) +## Negated equality +func neq(x, y) -> bool: + return !eq(x, y) +## Less than operator +func lt(x, y) -> bool: + return x.lt(y) if (x is Object and x.has_method("lt")) else x < y +## Greater than operator +func gt(x, y) -> bool: + return x.gt(y) if (x is Object and x.has_method("gt")) else x > y +## Greater than equals operator +func gte(x, y) -> bool: + return x.gte(y) if (x is Object and x.has_method("gte")) else x >= y +## Less than equals operator +func lte(x, y) -> bool: + return x.lte(y) if (x is Object and x.has_method("lte")) else x <= y + +## Raises a [AssertionFailedError] and returns [b]true[/b] +## should the assertion fail. +func assert_(assertion : bool, message : String = "Assertion failed!") -> bool: + if not assertion: + AssertionFailedError.new(message).throw() + return not assertion + +## Creates a new [TryCatch] Statement +func try(fun : Callable) -> TryCatch: + return TryCatch.new(fun) + +## Raises a [ThrowableBase] +func raise(exc_ : ThrowableBase, default = null) -> Variant: + return ErrorHandler.singleton().raise(exc_, default) + +## Raises a [RxBaseError] containing the given message +func raise_message(msg : String, default = null): + return RxBaseError.raise(default, msg) + +## Construct an [IterableBase] onto x. +func to_iterable(x) -> IterableBase: + return Iterator.to_iterable(x) + +### Construct an [Iterator] onto x. +func iter(x) -> Iterator: + return Iterator.iter(x) + +### Creates a [WhileIterable] from a given condition and another [IterableBase] +func take_while(cond : Callable, it : IterableBase) -> IterableBase: + return WhileIterable.new(it, cond) + +### Generates an [InfiniteIterable] sequence of a given value. +func infinite(infval = NOT_SET) -> IterableBase: + return InfiniteIterable.new(infval) + +### NOT Set value +var NOT_SET: + get: return util.NOT_SET + +## Is NOT Set value +func not_set(value) -> bool: + return NOT_SET.eq(value) + +## Unit item +var UNIT: + get: return StreamItem.Unit() + +## Alias for [code]GDRx.util.add_ref()[/code] +func add_ref(xs : Observable, r : RefCountDisposable) -> Observable: + return util.add_ref(xs, r) + +## Create an observable sequence from an array +func of(data : Array, scheduler : SchedulerBase = null) -> Observable: + return self.from_array(data, scheduler) + +## Create an observable on any given iterable sequence +func from(seq, scheduler : SchedulerBase = null) -> Observable: + return self.from_iterable(to_iterable(seq), scheduler) + +## Alias for [code]GDRx.obs.return_value[/code] +func just(value, scheduler : SchedulerBase = null) -> Observable: + return self.return_value(value, scheduler) + +## Empty operation as defined in [code]GDRx.basic.noop[/code] +func noop(__ = null, ___ = null): + GDRx.basic.noop(__, ___) + +## Identity mapping as defined in [code]GDRx.basic.identity[/code] +func identity(x, __ = null): + return GDRx.basic.identity(x, __) + +# =========================================================================== # +# Observable Constructors +# =========================================================================== # + +## See: [b]res://addons/reactivex/observable/amb.gd[/b] +func amb(sources) -> Observable: + return obs.amb(sources) + +## See: [b]res://addons/reactivex/observable/case.gd[/b] +func case(mapper : Callable, sources : Dictionary, default_source : Observable = null) -> Observable: + return obs.case(mapper, sources, default_source) + +## Create a new observable +func create(subscribe : Callable = func(_observer : ObserverBase, _scheduler : SchedulerBase = null) -> DisposableBase: return Disposable.new()) -> Observable: + return Observable.new(subscribe) + +## See: [b]res://addons/reactivex/observable/catch.gd[/b] +func catch(sources : Array[Observable]) -> Observable: + return obs.catch_with_iterable(to_iterable(sources)) + +## See: [b]res://addons/reactivex/observable/catch.gd[/b] +func catch_with_iterable(sources : IterableBase) -> Observable: + return obs.catch_with_iterable(sources) + +## See: [b]res://addons/reactivex/observable/combinelatest.gd[/b] +func combine_latest(sources) -> Observable: + return obs.combine_latest(sources) + +## See: [b]res://addons/reactivex/observable/concat.gd[/b] +func concat_streams(sources : Array[Observable]) -> Observable: + return obs.concat_with_iterable(to_iterable(sources)) + +## See: [b]res://addons/reactivex/observable/concat.gd[/b] +func concat_with_iterable(sources : IterableBase) -> Observable: + return obs.concat_with_iterable(sources) + +## See: [b]res://addons/reactivex/observable/defer.gd[/b] +func defer(factory : Callable = GDRx.basic.default_factory) -> Observable: + return obs.defer(factory) + +## See: [b]res://addons/reactivex/observable/empty.gd[/b] +func empty(scheduler : SchedulerBase = null) -> Observable: + return obs.empty(scheduler) + +## See: [b]res://addons/reactivex/observable/forkjoin.gd[/b] +func fork_join(sources) -> Observable: + return obs.fork_join(sources) + +## See: [b]res://addons/reactivex/observable/fromcallback.gd[/b] +func from_callback(fun : Callable = func(_args : Array, _cb : Callable): return, mapper = null) -> Callable: + return obs.from_callback(fun, mapper) + +## Transforms an array into an observable sequence. +func from_array(array : Array, scheduler : SchedulerBase = null) -> Observable: + return obs.from_iterable(to_iterable(array), scheduler) + +## See: [b]res://addons/reactivex/observable/fromiterable.gd[/b] +func from_iterable(iterable : IterableBase, scheduler : SchedulerBase = null) -> Observable: + return obs.from_iterable(iterable, scheduler) + +## See: [b]res://addons/reactivex/observable/generate.gd[/b] +func generate(initial_state, condition : Callable = GDRx.basic.default_condition, iterate : Callable = GDRx.basic.identity) -> Observable: + return obs.generate(initial_state, condition, iterate) + +## See: [b]res://addons/reactivex/observable/generatewithrealtivetime.gd[/b] +func generate_with_relative_time(initial_state, condition : Callable = GDRx.basic.default_condition, iterate : Callable = GDRx.basic.identity, time_mapper : Callable = func(_state) -> float: return 1.0) -> Observable: + return obs.generate_with_relative_time(initial_state, condition, iterate, time_mapper) + +## See: [b]res://addons/reactivex/observable/ifthen.gd[/b] +func if_then(condition : Callable = GDRx.basic.default_condition, then_source : Observable = null, else_source : Observable = null) -> Observable: + return obs.if_then(condition, then_source, else_source) + +## See: [b]res://addons/reactivex/observable/interval.gd[/b] +func interval(period : float, scheduler : SchedulerBase = null) -> ObservableBase: + return obs.interval(period, scheduler) + +## See: [b]res://addons/reactivex/observable/merge.gd[/b] +func merge(sources) -> Observable: + return obs.merge(sources) + +## See: [b]res://addons/reactivex/observable/never.gd[/b] +func never() -> Observable: + return obs.never() + +## See: [b]res://addons/reactivex/observable/onerrorresumenext.gd[/b] +func on_error_resume_next(sources : Array) -> Observable: + return obs.on_error_resume_next(sources) + +## See: [b]res://addons/reactivex/observable/range.gd[/b] +@warning_ignore("shadowed_global_identifier") +func range(start : int, stop = null, step = null, scheduler : SchedulerBase = null) -> Observable: + return obs.range(start, stop, step, scheduler) + +## See: [b]res://addons/reactivex/observable/repeat.gd[/b] +func repeat_value(value, repeat_count = null) -> Observable: + return obs.repeat_value(value, repeat_count) + +## See: [b]res://addons/reactivex/observable/returnvalue.gd[/b] +func return_value(value, scheduler : SchedulerBase = null) -> Observable: + return obs.return_value(value, scheduler) + +## See: [b]res://addons/reactivex/observable/returnvalue.gd[/b] +func from_callable(supplier : Callable, scheduler : SchedulerBase = null) -> Observable: + return obs.from_callable(supplier, scheduler) + +## See: [b]res://addons/reactivex/observable/throw.gd[/b] +func throw(err, scheduler : SchedulerBase = null) -> Observable: + return obs.throw(err, scheduler) + +## See: [b]res://addons/reactivex/observable/timer.gd[/b] +func timer(duetime : float, time_absolute : bool, period = null, scheduler : SchedulerBase = null) -> Observable: + return obs.timer(duetime, time_absolute, period, scheduler) + +## See: [b]res://addons/reactivex/observable/toasync.gd[/b] +func to_async(fun : Callable, scheduler : SchedulerBase = null) -> Callable: + return obs.to_async(fun, scheduler) + +## See: [b]res://addons/reactivex/observable/using.gd[/b] +func using(resource_factory : Callable, observable_factory : Callable) -> Observable: + return obs.using(resource_factory, observable_factory) + +## See: [b]res://addons/reactivex/observable/withlatestfrom.gd[/b] +func with_latest_from(sources) -> Observable: + var _sources : Array[Observable] = util.unpack_arg(sources) + assert_(_sources.size() > 0) + var parent = _sources.pop_front() + return obs.with_latest_from(parent, _sources) + +## See: [b]res://addons/reactivex/observable/zip.gd[/b] +func zip(sources) -> Observable: + return obs.zip(sources) + +# =========================================================================== # +# Timers +# =========================================================================== # + +## Creates an observable timer +func start_timer(timespan_sec : float, scheduler : SchedulerBase = null) -> Observable: + return obs.timer(timespan_sec, false, null, scheduler) + +## Creates an observable periodic timer +func start_periodic_timer(period_sec : float, scheduler : SchedulerBase = null) -> Observable: + return obs.timer(period_sec, false, period_sec, scheduler) + +## Creates an observable periodic timer which starts after a timespan has passed +func start_periodic_timer_after_timespan(timespan_sec : float, period_sec : float, scheduler : SchedulerBase = null) -> Observable: + return obs.timer(timespan_sec, false, period_sec, scheduler) + +## Creates an observable timer +func schedule_datetime(datetime_sec : float, scheduler : SchedulerBase = null) -> Observable: + return obs.timer(datetime_sec, true, null, scheduler) + +## Creates an observable periodic timer which starts at a given timestamp. +func start_periodic_timer_at_datetime(datetime_sec : float, period_sec : float, scheduler : SchedulerBase = null) -> Observable: + return obs.timer(datetime_sec, true, period_sec, scheduler) + +# =========================================================================== # +# Godot-specific Observable Constructors +# =========================================================================== # + +## Creates an observable from a Godot Signal +func from_signal(sig : Signal) -> Observable: + return gd.from_godot_signal(sig) + +## Creates an observable from a Coroutine +func from_coroutine(fun : Callable, bindings : Array = [], scheduler : SchedulerBase = null) -> Observable: + return gd.from_godot_coroutine(fun, bindings, scheduler) + +## Emits items from [method Node._process]. +func on_process_as_observable(conn : Node) -> Observable: + return gd.from_godot_node_lifecycle_event(conn, 0) + +## Emits items from [method Node._physics_process]. +func on_physics_process_as_observable(conn : Node) -> Observable: + return gd.from_godot_node_lifecycle_event(conn, 1) + +## Emits items from [method Node._input]. +func on_input_as_observable(conn : Node) -> Observable: + return gd.from_godot_node_lifecycle_event(conn, 2) + +## Emits items from [method Node._shortcut_input]. +func on_shortcut_input_as_observable(conn : Node) -> Observable: + return gd.from_godot_node_lifecycle_event(conn, 3) + +## Emits items from [method Node._unhandled_input]. +func on_unhandled_input_as_observable(conn : Node) -> Observable: + return gd.from_godot_node_lifecycle_event(conn, 4) + +## Emits items from [method Node._unhandled_key_input]. +func on_unhandled_key_input_as_observable(conn : Node) -> Observable: + return gd.from_godot_node_lifecycle_event(conn, 5) + +## Tranforms an input action into an observable sequence emiting items on check. +func input_action(input_action_ : String, checks : Observable) -> Observable: + return gd.from_godot_input_action(input_action_, checks) + +## Creates a new Compute Shader as [Observable]. +func from_compute_shader(shader_path : String, rd : RenderingDevice, work_groups : Vector3i, uniform_sets : Array = [], scheduler : SchedulerBase = null) -> Observable: + return gd.from_compute_shader(shader_path, rd, work_groups, uniform_sets, scheduler) + +## Emits items when the node enters the scene tree +func on_tree_enter_as_observable(node : Node) -> Observable: + return from_signal(node.tree_entered) + +## Emits items when the node just exited the scene tree +func on_tree_exit_as_observable(node : Node) -> Observable: + return from_signal(node.tree_exited) + +## Emits items when the node is about to exit the scene tree +func on_tree_exiting_as_observable(node : Node) -> Observable: + return from_signal(node.tree_exiting) + +## Creates an HTTP Request +func from_http_request(url : String, request_data = "", raw : bool = false, encoding : String = "", requester : HTTPRequest = null, custom_headers : PackedStringArray = PackedStringArray(), method : HTTPClient.Method = HTTPClient.METHOD_GET) -> Observable: + return gd.from_http_request(url, request_data, raw, encoding, requester, custom_headers, method) + +# =========================================================================== # +# Some useful Input Observables +# =========================================================================== # + +## Emits item when mouse button is just pressed +func on_mouse_down() -> Observable: + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventMouseButton) \ + .filter(func(ev : InputEventMouseButton): return ev.is_pressed()) + +## Emits item when mouse button is just released +func on_mouse_up() -> Observable: + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventMouseButton) \ + .filter(func(ev : InputEventMouseButton): return not ev.is_pressed()) + +## Emits item on mouse double-click +func on_mouse_double_click() -> Observable: + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventMouseButton) \ + .filter(func(ev : InputEventMouseButton): return ev.is_pressed() and ev.double_click) + +## Emits items on mouse motion +func on_mouse_motion() -> Observable: + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventMouseMotion) + +## Emits the relative mouse motion as a [Vector2]. +func relative_mouse_movement_as_observable() -> Observable: + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventMouseMotion) \ + .map(func(ev : InputEventMouseMotion): return ev.relative) + +## Emits an item when the given keycode is just pressed. +func on_key_just_pressed(key : int) -> Observable: + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventKey) \ + .filter(func(ev : InputEventKey): return ev.keycode == key and ev.pressed and not ev.echo) + +## Emits an item when the given keycode is pressed. +func on_key_pressed(key : int) -> Observable: + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventKey) \ + .filter(func(ev : InputEventKey): return ev.keycode == key and ev.pressed) + +## Emits an item when the given keycode is just released. +func on_key_just_released(key : int) -> Observable: + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventKey) \ + .filter(func(ev : InputEventKey): return ev.keycode == key and not ev.pressed) + +## Emits an item, when the screen is touched (touch devices). +func on_screen_touch() -> Observable: + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventScreenTouch) + +## Emits an item, when the touch screen notices a drag gesture (touch devices) +func on_screen_drag() -> Observable: + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventScreenDrag) + +## Emits an item on Midi event. +func on_midi_event() -> Observable: + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventMIDI) + +## Emits an item, when a joypad button is just pressed. +func on_joypad_button_down() -> Observable: + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventJoypadButton) \ + .filter(func(ev : InputEventJoypadButton): return not ev.is_echo() and ev.is_pressed()) + +## Emits an item, when a joypad button is pressed. +func on_joypad_button_pressed() -> Observable: + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventJoypadButton) \ + .filter(func(ev : InputEventJoypadButton): return ev.is_pressed()) + +## Emits an item, when a joypad button is just released. +func on_joypad_button_released() -> Observable: + return on_input_as_observable(self) \ + .filter(func(ev : InputEvent): return ev is InputEventJoypadButton) \ + .filter(func(ev : InputEventJoypadButton): return not ev.is_pressed()) + +# =========================================================================== # +# Frame Events +# =========================================================================== # + +## Emits items on idle frame events. +func on_idle_frame() -> Observable: + return from_signal(self.get_tree().process_frame) \ + .map(func(__): return get_process_delta_time()) + +## Emits items on physics frame events. +func on_physics_step() -> Observable: + return from_signal(self.get_tree().physics_frame) \ + .map(func(__): return get_physics_process_delta_time()) + +## Emits an item when the scene tree has changed. +func on_tree_changed() -> Observable: + return from_signal(self.get_tree().tree_changed) + +## Emits an item at post-draw frame event. +func on_frame_post_draw() -> Observable: + return from_signal(RenderingServer.frame_post_draw) + +## Emits an item at pre-draw frame event. +func on_frame_pre_draw() -> Observable: + return from_signal(RenderingServer.frame_pre_draw) diff --git a/addons/reactivex/abc/comparable.gd b/addons/reactivex/abc/comparable.gd new file mode 100644 index 0000000..b463cda --- /dev/null +++ b/addons/reactivex/abc/comparable.gd @@ -0,0 +1,27 @@ +class_name Comparable + +## Interface for comparable objects +## +## This interface provides comparison functions. It is needed because GDScript +## does not allow defining custom operators. + +func eq(_other) -> bool: + NotImplementedError.raise() + return false + +func lt(_other) -> bool: + NotImplementedError.raise() + return false + +func gt(_other) -> bool: + NotImplementedError.raise() + return false + +func gte(other) -> bool: + return eq(other) or gt(other) + +func lte(other) -> bool: + return eq(other) or lt(other) + +func neq(other) -> bool: + return not eq(other) diff --git a/addons/reactivex/abc/disposable.gd b/addons/reactivex/abc/disposable.gd new file mode 100644 index 0000000..07f580c --- /dev/null +++ b/addons/reactivex/abc/disposable.gd @@ -0,0 +1,28 @@ +class_name DisposableBase + +## Disposables represent Subscriptions in GDRx +## [br][br] +## Whenever [method dispose] is called, the corresponding +## action is performed, e.g. to destroy and clean up a subscription. + +## This is the [b]this[/b] reference. It acts like +## [code]std::enable_shared_from_this<>[/code] and is ignored in ref-counting. +## Yes, I do know what I am doing! I coded something similar in C++! +var this + +func _init(): + this = self + this.unreference() + +## Disposes the disposable and executes a defined action. +func dispose(): + NotImplementedError.raise() + +## Links disposable to [Object] lifetime via an [AutoDisposer] +func dispose_with(_obj : Object) -> DisposableBase: + NotImplementedError.raise() + return null + +func _notification(what): + if what == NOTIFICATION_PREDELETE: + this.dispose() diff --git a/addons/reactivex/abc/iterable.gd b/addons/reactivex/abc/iterable.gd new file mode 100644 index 0000000..885789b --- /dev/null +++ b/addons/reactivex/abc/iterable.gd @@ -0,0 +1,28 @@ +class_name IterableBase + +## An iterable type +## +## An [IterableBase] constructs an [IteratorBase] to iterate over it. +## This is done using the [method iter] method. + +var __it__ : Iterator +var __curr__ : Variant + +## Returns an iterator onto the given iterable sequence. +func iter() -> Iterator: + NotImplementedError.raise() + return null + +func _iter_init(arg): + self.__it__ = iter() + var continue_ = self.__it__.has_next() + self.__curr__ = self.__it__.next() + return continue_ + +func _iter_next(arg): + var continue_ = self.__it__.has_next() + self.__curr__ = self.__it__.next() + return continue_ + +func _iter_get(arg): + return self.__curr__ diff --git a/addons/reactivex/abc/iterator.gd b/addons/reactivex/abc/iterator.gd new file mode 100644 index 0000000..0232e1d --- /dev/null +++ b/addons/reactivex/abc/iterator.gd @@ -0,0 +1,36 @@ +class_name IteratorBase + +## An iterator type +## +## An [IteratorBase] iterates over an [IterableBase] using [method next]. + +## Returns next element in the iterable sequence. Return instance of +## [ItEnd] when end is reached. +func next() -> Variant: + NotImplementedError.raise() + return null + +## Return 'true' if the sequence has another element. +func has_next() -> bool: + NotImplementedError.raise() + return false + +## Returns the first element within the sequence +func front() -> Variant: + NotImplementedError.raise() + return null + +## Returns the last element within the sequence +func back() -> Variant: + NotImplementedError.raise() + return null + +## Returns 'true' if the iterable sequence is empty. +func empty() -> bool: + NotImplementedError.raise() + return false + +## Returns the n-th element within the sequence. +func at(_n : int) -> Variant: + NotImplementedError.raise() + return false diff --git a/addons/reactivex/abc/iteratorend.gd b/addons/reactivex/abc/iteratorend.gd new file mode 100644 index 0000000..f7c87d7 --- /dev/null +++ b/addons/reactivex/abc/iteratorend.gd @@ -0,0 +1,2 @@ +## Represents the end of an iterable sequence. +class_name ItEnd diff --git a/addons/reactivex/abc/lock.gd b/addons/reactivex/abc/lock.gd new file mode 100644 index 0000000..7719111 --- /dev/null +++ b/addons/reactivex/abc/lock.gd @@ -0,0 +1,25 @@ +class_name LockBase + +## Interface of a Lock +## +## Allows a thread to aquire and release it. + +func lock(): + NotImplementedError.raise() + +func unlock(): + NotImplementedError.raise() + +func try_lock() -> bool: + NotImplementedError.raise() + return false + +func is_locking_thread() -> bool: + NotImplementedError.raise() + return false + +func _unlock_and_store_recursion_depth(): + NotImplementedError.raise() + +func _lock_and_restore_recursion_depth(): + NotImplementedError.raise() diff --git a/addons/reactivex/abc/observable.gd b/addons/reactivex/abc/observable.gd new file mode 100644 index 0000000..48c13ec --- /dev/null +++ b/addons/reactivex/abc/observable.gd @@ -0,0 +1,60 @@ +class_name ObservableBase + +## Interface of an observable stream/subject/signal etc. +## +## All communication in GDRx is handled asynchronously via observable data +## streams, so-called [Observable]s. An [Observer] can subscribe to an +## [Observable] to receive emitted items sent on the stream. +## Create a new subscription via [method subscribe]. + +func _init(): + pass + +## Creates a new subscription. +## [br] +## There are two ways to invoke this method: +## [br] +## 1. Subscribes an instance of [ObserverBase]. +## [br] +## 2. Builds a new Observer in accordance to the Observer-Observable-Contract +## (see [ObserverBase]) from callbacks and subscribes it. +## +## [codeblock] +## var disp = obs.subscribe(observer) +## var disp = obs.subscribe(func(i) ..., func(e): ..., func(): ...) +## [/codeblock] +## [br] +## Since GDScript has no overloading to this date, use [code]subscribe{n}(...)[/code] +## for faster access! +func subscribe( + _on_next, # Callable or Observer or Object with callbacks + _on_error : Callable = GDRx.basic.noop, + _on_completed : Callable = GDRx.basic.noop, + _scheduler : SchedulerBase = null) -> DisposableBase: + NotImplementedError.raise() + return null + +## Simulated overload for [code]subscribe[/code] +func subscribe1(obv : ObserverBase = null, _scheduler : SchedulerBase = null) -> DisposableBase: + return self.subscribe(obv, GDRx.basic.noop, GDRx.basic.noop, _scheduler) +## Simulated overload for [code]subscribe[/code] +func subscribe2(on_next : Callable = GDRx.basic.noop, _scheduler : SchedulerBase = null) -> DisposableBase: + return self.subscribe(on_next, GDRx.basic.noop, GDRx.basic.noop, _scheduler) +## Simulated overload for [code]subscribe[/code] +func subscribe3(on_error : Callable = GDRx.basic.noop, _scheduler : SchedulerBase = null) -> DisposableBase: + return self.subscribe(GDRx.basic.noop, on_error, GDRx.basic.noop, _scheduler) +## Simulated overload for [code]subscribe[/code] +func subscribe4(on_completed : Callable = GDRx.basic.noop, _scheduler : SchedulerBase = null) -> DisposableBase: + return self.subscribe(GDRx.basic.noop, GDRx.basic.noop, on_completed, _scheduler) +## Simulated overload for [code]subscribe[/code] +func subscribe5(on_next : Callable = GDRx.basic.noop, on_completed : Callable = GDRx.basic.noop, _scheduler : SchedulerBase = null) -> DisposableBase: + return self.subscribe(on_next, GDRx.basic.noop, on_completed, _scheduler) +## Simulated overload for [code]subscribe[/code] +func subscribe6(on_next : Callable = GDRx.basic.noop, on_error : Callable = GDRx.basic.noop, _scheduler : SchedulerBase = null) -> DisposableBase: + return self.subscribe(on_next, on_error, GDRx.basic.noop, _scheduler) +## Simulated overload for [code]subscribe[/code] +func subscribe7(on_completed : Callable = GDRx.basic.noop, on_error : Callable = GDRx.basic.noop, _scheduler : SchedulerBase = null) -> DisposableBase: + return self.subscribe(GDRx.basic.noop, on_error, on_completed, _scheduler) +## Simulated overload for [code]subscribe[/code] +func subscribe8(_scheduler : SchedulerBase = null) -> DisposableBase: + return self.subscribe(GDRx.basic.noop, GDRx.basic.noop, GDRx.basic.noop, _scheduler) diff --git a/addons/reactivex/abc/observer.gd b/addons/reactivex/abc/observer.gd new file mode 100644 index 0000000..e15f627 --- /dev/null +++ b/addons/reactivex/abc/observer.gd @@ -0,0 +1,25 @@ +class_name ObserverBase + +## Interface of an observer +## +## All communication in GDRx is handled asynchronously via observable data +## streams, so-called [Observable]s. An [Observer] can subscribe to an +## [Observable] to receive emitted items sent on the stream. +## Create a new subscription via [method ObservableBase.subscribe]. +## An [Observer] needs to abide by the Observer-Observable-Contract as defined +## in this interface. +## +## Notifications are [method on_next], [method on_error] and +## [method on_completed]. + +## Called when the [Observable] emits a new item on the stream +func on_next(_i): + NotImplementedError.raise() + +## Called when the [Observable] emits an error on the stream +func on_error(_e): + NotImplementedError.raise() + +## Called when the [Observable] is finished and no more items are sent. +func on_completed(): + NotImplementedError.raise() diff --git a/addons/reactivex/abc/periodicscheduler.gd b/addons/reactivex/abc/periodicscheduler.gd new file mode 100644 index 0000000..4e5fcb3 --- /dev/null +++ b/addons/reactivex/abc/periodicscheduler.gd @@ -0,0 +1,15 @@ +extends SchedulerBase +class_name PeriodicSchedulerBase + +## A scheduler for periodic actions +## +## Periodically schedules actions for repeated future execution. + +## Schedule a periodic action for repeated execution every time +## [code]period[/code] seconds have expired. +func schedule_periodic( + _period : float, + _action : Callable, + _state = null) -> DisposableBase: + NotImplementedError.raise() + return null diff --git a/addons/reactivex/abc/scheduler.gd b/addons/reactivex/abc/scheduler.gd new file mode 100644 index 0000000..7cfc1cb --- /dev/null +++ b/addons/reactivex/abc/scheduler.gd @@ -0,0 +1,36 @@ +class_name SchedulerBase + +## A scheduler performs a scheduled action at some future point. +## +## Schedules actions for execution at some point in the future. +## [br] +## [color=yellow]Important: We will always use time values of type +## [float] representing seconds![/color] + +func _init(): + pass + +## Invoke the given action. +func invoke_action(_action : Callable, _state = null) -> DisposableBase: + NotImplementedError.raise() + return null + +## Returns the current point in time (timestamp) +func now() -> float: + NotImplementedError.raise() + return -1.0 + +## Schedule a new action for future execution +func schedule(_action : Callable, _state = null) -> DisposableBase: + NotImplementedError.raise() + return null + +## Schedule a new action for future execution in [code]duetime[/code] seconds. +func schedule_relative(_duetime : float, _action : Callable, _state = null) -> DisposableBase: + NotImplementedError.raise() + return null + +## Schedule a new action for future execution at [code]duetime[/code]. +func schedule_absolute(_duetime : float, _action : Callable, _state = null) -> DisposableBase: + NotImplementedError.raise() + return null diff --git a/addons/reactivex/abc/startable.gd b/addons/reactivex/abc/startable.gd new file mode 100644 index 0000000..4e48250 --- /dev/null +++ b/addons/reactivex/abc/startable.gd @@ -0,0 +1,7 @@ +class_name StartableBase + +func start(): + NotImplementedError.raise() + +func wait_to_finish(): + NotImplementedError.raise() diff --git a/addons/reactivex/abc/subject.gd b/addons/reactivex/abc/subject.gd new file mode 100644 index 0000000..6797b0a --- /dev/null +++ b/addons/reactivex/abc/subject.gd @@ -0,0 +1,72 @@ +class_name SubjectBase + +## Interface of a Subject. +## +## A subject is both an [Observer] and [Observable] in RxPY, +## meaning it implements both interfaces, however, in GDScript, this is not +## allowed! So, this interface provides all interface methods from +## [ObserverBase] and [ObservableBase]. + +func _init(): + pass + +## Creates a new subscription. +## [br] +## There are two ways to invoke this method: +## [br] +## 1. Subscribes an instance of [ObserverBase]. +## [br] +## 2. Builds a new Observer in accordance to the Observer-Observable-Contract +## (see [ObserverBase]) from callbacks and subscribes it. +## +## [codeblock] +## var disp = obs.subscribe(observer) +## var disp = obs.subscribe(func(i) ..., func(e): ..., func(): ...) +## [/codeblock] +## [br] +## Since GDScript has no overloading to this date, use [code]subscribe{n}(...)[/code] +## for faster access! +func subscribe( + _on_next, # Callable or Observer or Object with callbacks + _on_error : Callable = GDRx.basic.noop, + _on_completed : Callable = GDRx.basic.noop, + _scheduler : SchedulerBase = null) -> DisposableBase: + NotImplementedError.raise() + return null + +## Simulated overload for [code]subscribe[/code] +func subscribe1(obv : ObserverBase = null, _scheduler : SchedulerBase = null) -> DisposableBase: + return self.subscribe(obv, GDRx.basic.noop, GDRx.basic.noop, _scheduler) +## Simulated overload for [code]subscribe[/code] +func subscribe2(_on_next : Callable = GDRx.basic.noop, _scheduler : SchedulerBase = null) -> DisposableBase: + return self.subscribe(_on_next, GDRx.basic.noop, GDRx.basic.noop, _scheduler) +## Simulated overload for [code]subscribe[/code] +func subscribe3(_on_error : Callable = GDRx.basic.noop, _scheduler : SchedulerBase = null) -> DisposableBase: + return self.subscribe(GDRx.basic.noop, _on_error, GDRx.basic.noop, _scheduler) +## Simulated overload for [code]subscribe[/code] +func subscribe4(_on_completed : Callable = GDRx.basic.noop, _scheduler : SchedulerBase = null) -> DisposableBase: + return self.subscribe(GDRx.basic.noop, GDRx.basic.noop, _on_completed, _scheduler) +## Simulated overload for [code]subscribe[/code] +func subscribe5(_on_next : Callable = GDRx.basic.noop, _on_completed : Callable = GDRx.basic.noop, _scheduler : SchedulerBase = null) -> DisposableBase: + return self.subscribe(_on_next, GDRx.basic.noop, _on_completed, _scheduler) +## Simulated overload for [code]subscribe[/code] +func subscribe6(_on_next : Callable = GDRx.basic.noop, _on_error : Callable = GDRx.basic.noop, _scheduler : SchedulerBase = null) -> DisposableBase: + return self.subscribe(_on_next, _on_error, GDRx.basic.noop, _scheduler) +## Simulated overload for [code]subscribe[/code] +func subscribe7(_on_completed : Callable = GDRx.basic.noop, _on_error : Callable = GDRx.basic.noop, _scheduler : SchedulerBase = null) -> DisposableBase: + return self.subscribe(GDRx.basic.noop, _on_error, _on_completed, _scheduler) +## Simulated overload for [code]subscribe[/code] +func subscribe8(_scheduler : SchedulerBase = null) -> DisposableBase: + return self.subscribe(GDRx.basic.noop, GDRx.basic.noop, GDRx.basic.noop, _scheduler) + +## Called when the [Observable] emits a new item on the stream +func on_next(_i): + NotImplementedError.raise() + +## Called when the [Observable] emits an error on the stream +func on_error(_e): + NotImplementedError.raise() + +## Called when the [Observable] is finished and no more items are sent. +func on_completed(): + NotImplementedError.raise() diff --git a/addons/reactivex/abc/throwable.gd b/addons/reactivex/abc/throwable.gd new file mode 100644 index 0000000..fd75168 --- /dev/null +++ b/addons/reactivex/abc/throwable.gd @@ -0,0 +1,14 @@ +class_name ThrowableBase + +## Interface for throwable objects +## +## Objects that implement this interface can be used within a try-catch- +## structure. + +func throw(_default = null) -> Variant: + NotImplementedError.raise() + return null + +func tags() -> Array[String]: + NotImplementedError.raise() + return [] diff --git a/addons/reactivex/disposable/autodisposer.gd b/addons/reactivex/disposable/autodisposer.gd new file mode 100644 index 0000000..0c380a4 --- /dev/null +++ b/addons/reactivex/disposable/autodisposer.gd @@ -0,0 +1,43 @@ +class_name AutoDisposer + +## Links a [DisposableBase] to an [Object]'s lifetime. + +var _disp : DisposableBase + +func _init(disp : DisposableBase): + self._disp = disp + +func _notification(what): + if what == NOTIFICATION_PREDELETE: + self._disp.dispose() + +static func _meta_key(obj : Object, disp : DisposableBase): + var disp_id : int = disp.get_instance_id() + var obj_id : int = obj.get_instance_id() + var meta_entry = "autodispose_" + \ + ("0" if disp_id > 0 else "1") + \ + ("0" if obj_id > 0 else "1") + \ + "_d" + str(abs(disp_id)) + "_o" + str(abs(obj_id)) + return meta_entry + +static func _collect_garbage(obj : Object): + for meta_key in obj.get_meta_list(): + var meta_entry = obj.get_meta(meta_key) + if meta_entry is AutoDisposer: + if meta_entry._disp.get("is_disposed"): + obj.remove_meta(meta_entry) + +static func add(obj : Object, disp : DisposableBase) -> AutoDisposer: + AutoDisposer._collect_garbage(obj) + var auto_disposer : AutoDisposer = AutoDisposer.new(disp) + var meta_key = AutoDisposer._meta_key(obj, disp) + obj.set_meta(meta_key, auto_disposer) + return auto_disposer + +static func remove(obj : Object, disp : DisposableBase): + var meta_key = AutoDisposer._meta_key(obj, disp) + obj.remove_meta(meta_key) + +static func remove_and_dispose(obj : Object, disp : DisposableBase): + AutoDisposer.remove(obj, disp) + disp.dispose() diff --git a/addons/reactivex/disposable/booleandisposable.gd b/addons/reactivex/disposable/booleandisposable.gd new file mode 100644 index 0000000..6c92809 --- /dev/null +++ b/addons/reactivex/disposable/booleandisposable.gd @@ -0,0 +1,19 @@ +extends DisposableBase +class_name BooleanDisposable + +var is_disposed : bool +var lock : RLock + +func _init(): + self.is_disposed = false + self.lock = RLock.new() + + super._init() + +func dispose(): + this.is_disposed = true + +## Links disposable to [Object] lifetime via an [AutoDisposer] +func dispose_with(obj : Object) -> DisposableBase: + AutoDisposer.add(obj, self) + return self diff --git a/addons/reactivex/disposable/compositedisposable.gd b/addons/reactivex/disposable/compositedisposable.gd new file mode 100644 index 0000000..8fc4815 --- /dev/null +++ b/addons/reactivex/disposable/compositedisposable.gd @@ -0,0 +1,88 @@ +extends DisposableBase +class_name CompositeDisposable + +## A collection of disposables +## +## When disposed, the underlying composition of disposables are disposed as well. + +var disposable : Array +var is_disposed : bool +var lock : RLock + + +func _init(sources_ = []): + var sources = GDRx.iter(sources_) + while sources.has_next(): + self.disposable.push_back(sources.next()) + + self.is_disposed = false + self.lock = RLock.new() + super._init() + +func add(item : DisposableBase): + var should_dispose = false + if true: + var __ = LockGuard.new(self.lock) + if self.is_disposed: + should_dispose = true + else: + self.disposable.append(item) + + if should_dispose: + item.dispose() + +func remove(item : DisposableBase) -> bool: + if self.is_disposed: + return false + + var should_dispose = false + if true: + var __ = LockGuard.new(self.lock) + if item in self.disposable: + self.disposable.erase(item) + should_dispose = true + + if should_dispose: + item.dispose() + + return should_dispose + +func dispose(): + if this.is_disposed: + return + + var current_disposable + if true: + var __ = LockGuard.new(this.lock) + this.is_disposed = true + current_disposable = this.disposable + this.disposable = [] + + for disp in current_disposable: + disp.dispose() + +func clear(): + var current_disposable + if true: + var __ = LockGuard.new(self.lock) + current_disposable = self.disposable + self.disposable = [] + + for _disposable in current_disposable: + _disposable.dispose() + +func contains(item : DisposableBase) -> bool: + return item in self.disposable + +func to_list() -> Array[DisposableBase]: + return self.disposable.duplicate() + +func size() -> int: + return self.disposable.size() + +var length : int: get = size + +## Links disposable to [Object] lifetime via an [AutoDisposer] +func dispose_with(obj : Object) -> DisposableBase: + AutoDisposer.add(obj, self) + return self diff --git a/addons/reactivex/disposable/disposable.gd b/addons/reactivex/disposable/disposable.gd new file mode 100644 index 0000000..55f1882 --- /dev/null +++ b/addons/reactivex/disposable/disposable.gd @@ -0,0 +1,52 @@ +extends DisposableBase +class_name Disposable +## Main disposable class +## +## Invokes specified action when disposed. + +var is_disposed : bool +var action : Callable + +var lock : RLock + +## Creates a disposable object that invokes the specified +## action when disposed. +## [br] +## [b]Args:[/b] +## [br] +## [code]action[/code] Action to run during the first call to dispose. +## The action is guaranteed to be run at most once. +## [br][br] +## [b]Returns:[/b] +## [br] +## The disposable object that runs the given action upon +## disposal. +func _init(action_ : Callable = GDRx.basic.noop): + self.is_disposed = false + self.action = action_ + self.lock = RLock.new() + + super._init() + +## Performs the task of cleaning up resources. +func dispose(): + var disposed = false + if true: + var __ = LockGuard.new(this.lock) + if not this.is_disposed: + disposed = true + this.is_disposed = true + + if disposed: + this.action.call() + +## Links disposable to [Object] lifetime via an [AutoDisposer] +func dispose_with(obj : Object) -> DisposableBase: + AutoDisposer.add(obj, self) + return self + +## Casts any object with [code]dispose()[/code] to a [DisposableBase]. +static func Cast(obj) -> DisposableBase: + if obj.has_method("dispose"): + return Disposable.new(func(): obj.dispose()) + return Disposable.new() diff --git a/addons/reactivex/disposable/multiassignmentdisposable.gd b/addons/reactivex/disposable/multiassignmentdisposable.gd new file mode 100644 index 0000000..daae81b --- /dev/null +++ b/addons/reactivex/disposable/multiassignmentdisposable.gd @@ -0,0 +1,50 @@ +extends DisposableBase +class_name MultipleAssignmentDisposable + + +var current : DisposableBase +var is_disposed : bool +var lock : RLock + +func _init(): + self.current = null + self.is_disposed = false + self.lock = RLock.new() + + super._init() + +func get_disposable() -> DisposableBase: + return self.current + +func set_disposable(value : DisposableBase): + var should_dispose : bool + if true: + var __ = LockGuard.new(self.lock) + should_dispose = self.is_disposed + if not should_dispose: + self.current = value + + if should_dispose and value != null: + value.dispose() + +var disposable : DisposableBase: + set(value): set_disposable(value) + get: return get_disposable() + +func dispose(): + var old = null + + if true: + var __ = LockGuard.new(this.lock) + if not this.is_disposed: + this.is_disposed = true + old = this.current + this.current = null + + if old != null: + old.dispose() + +## Links disposable to [Object] lifetime via an [AutoDisposer] +func dispose_with(obj : Object) -> DisposableBase: + AutoDisposer.add(obj, self) + return self diff --git a/addons/reactivex/disposable/refcountdisposable.gd b/addons/reactivex/disposable/refcountdisposable.gd new file mode 100644 index 0000000..6cd3c2b --- /dev/null +++ b/addons/reactivex/disposable/refcountdisposable.gd @@ -0,0 +1,85 @@ +extends DisposableBase +class_name RefCountDisposable + +class InnerDisposable extends DisposableBase: + var parent : RefCountDisposable + var is_disposed : bool + var lock : RLock + + func _init(parent_ : RefCountDisposable): + self.parent = parent_ + self.is_disposed = false + self.lock = RLock.new() + super._init() + + func dispose(): + var _parent + if true: + var __ = LockGuard.new(this.lock) + _parent = this.parent + this.parent = null + + if _parent != null: + _parent.release() + +var underlying_disposable : DisposableBase +var is_primary_disposed : bool +var is_disposed : bool +var lock : RLock +var count : int + +func _init(disposable_ : DisposableBase): + self.underlying_disposable = disposable_ + self.is_primary_disposed = false + self.is_disposed = false + self.lock = RLock.new() + self.count = 0 + + super._init() + +func dispose(): + if this.is_disposed: + return + + var _underlying_disposable = null + if true: + var __ = LockGuard.new(this.lock) + if not this.is_primary_disposed: + this.is_primary_disposed = true + if not bool(this.count): + this.is_disposed = true + _underlying_disposable = this.underlying_disposable + + if _underlying_disposable != null: + _underlying_disposable.dispose() + +func release(): + if self.is_disposed: + return + + var should_dispose = false + if true: + var __ = LockGuard.new(self.lock) + self.count -= 1 + if not bool(self.count) and self.is_primary_disposed: + self.is_disposed = true + should_dispose = true + + if should_dispose: + self.underlying_disposable.dispose() + +func get_disposable() -> DisposableBase: + var __ = LockGuard.new(self.lock) + if self.is_disposed: + return Disposable.new() + + self.count += 1 + return InnerDisposable.new(self) + +var disposable : DisposableBase: + get: return get_disposable() + +## Links disposable to [Object] lifetime via an [AutoDisposer] +func dispose_with(obj : Object) -> DisposableBase: + AutoDisposer.add(obj, self) + return self diff --git a/addons/reactivex/disposable/scheduleddisposable.gd b/addons/reactivex/disposable/scheduleddisposable.gd new file mode 100644 index 0000000..ef067f6 --- /dev/null +++ b/addons/reactivex/disposable/scheduleddisposable.gd @@ -0,0 +1,28 @@ +extends DisposableBase +class_name ScheduledDisposable + + +var scheduler : SchedulerBase +var disposable : SingleAssignmentDisposable +var lock : RLock + +func _init(scheduler_ : SchedulerBase, disposable_ : DisposableBase): + self.scheduler = scheduler_ + self.disposable = SingleAssignmentDisposable.new() + self.disposable.disposable = disposable_ + self.lock = RLock.new() + + super._init() + +var is_disposed: + get: return self.disposable.is_disposed + +func dispose(): + var action = func(_scheduler : SchedulerBase, _state): + this.disposable.dispose() + this.scheduler.schedule(action) + +## Links disposable to [Object] lifetime via an [AutoDisposer] +func dispose_with(obj : Object) -> DisposableBase: + AutoDisposer.add(obj, self) + return self diff --git a/addons/reactivex/disposable/serialdisposable.gd b/addons/reactivex/disposable/serialdisposable.gd new file mode 100644 index 0000000..594b3cb --- /dev/null +++ b/addons/reactivex/disposable/serialdisposable.gd @@ -0,0 +1,55 @@ +extends DisposableBase +class_name SerialDisposable + +var current : DisposableBase +var is_disposed : bool +var lock : RLock + +func _init(): + self.current = null + self.is_disposed = false + self.lock = RLock.new() + + super._init() + +func get_disposable() -> DisposableBase: + return self.current + +func set_disposable(value : DisposableBase): + var old = null + + var should_dispose : bool + if true: + var __ = LockGuard.new(self.lock) + should_dispose = self.is_disposed + if not should_dispose: + old = self.current + self.current = value + + if old != null: + old.dispose() + + if should_dispose and value != null: + value.dispose() + +var disposable : DisposableBase: + get: return get_disposable() + set(value): set_disposable(value) + +func dispose(): + var old = null + + if true: + var __ = LockGuard.new(this.lock) + if not this.is_disposed: + this.is_disposed = true + old = this.current + this.current = null + + if old != null: + old.dispose() + +## Links disposable to [Object] lifetime via an [AutoDisposer] +func dispose_with(obj : Object) -> DisposableBase: + AutoDisposer.add(obj, self) + return self diff --git a/addons/reactivex/disposable/singleassignmentdisposable.gd b/addons/reactivex/disposable/singleassignmentdisposable.gd new file mode 100644 index 0000000..9ab4944 --- /dev/null +++ b/addons/reactivex/disposable/singleassignmentdisposable.gd @@ -0,0 +1,53 @@ +extends DisposableBase +class_name SingleAssignmentDisposable + +var is_disposed : bool +var current : DisposableBase +var lock : RLock + +func _init(): + self.is_disposed = false + self.current = null + self.lock = RLock.new() + + super._init() + +func get_disposabe() -> DisposableBase: + return self.current + +func set_disposable(value : DisposableBase): + if self.current != null: + DisposedError.raise() + return + + var should_dispose : bool + if true: + var __ = LockGuard.new(self.lock) + should_dispose = self.is_disposed + if not should_dispose: + self.current = value + + if self.is_disposed and value != null: + value.dispose() + +var disposable : DisposableBase: + set(value): set_disposable(value) + get: return get_disposabe() + +func dispose(): + var old = null + + if true: + var __ = LockGuard.new(this.lock) + if not this.is_disposed: + this.is_disposed = true + old = this.current + this.current = null + + if old != null: + old.dispose() + +## Links disposable to [Object] lifetime via an [AutoDisposer] +func dispose_with(obj : Object) -> DisposableBase: + AutoDisposer.add(obj, self) + return self diff --git a/addons/reactivex/engine/__gdrxengine__.gd b/addons/reactivex/engine/__gdrxengine__.gd new file mode 100644 index 0000000..f387434 --- /dev/null +++ b/addons/reactivex/engine/__gdrxengine__.gd @@ -0,0 +1,48 @@ +class_name __GDRx_Engine__ +## Provides access to Godot-specific [Observable] constructors. +## +## Bridge between Godot-specific implementations and [__GDRx_Singleton__] + +# =========================================================================== # +# Observables +# =========================================================================== # +var _GodotSignal_ = load("res://addons/reactivex/engine/observable/godotsignal.gd") +var _GodotLifecycle_ = load("res://addons/reactivex/engine/observable/godotnodelifecycle.gd") +var _GodotInputAction_ = load("res://addons/reactivex/engine/observable/godotinputaction.gd") +var _GodotCoroutine_ = load("res://addons/reactivex/engine/observable/godotcoroutine.gd") +var _ComputeShader_ = load("res://addons/reactivex/engine/observable/computeshader.gd") +var _HttpRequest_ = load("res://addons/reactivex/engine/observable/httprequest.gd") + +var _ProcessTimeInterval_ = load("res://addons/reactivex/engine/operators/_processtimeinterval.gd") + +## See: [b]res://addons/reactivex/engine/observable/godotsignal.gd[/b] +func from_godot_signal(sig : Signal, scheduler : SchedulerBase = null) -> Observable: + return _GodotSignal_.from_godot_signal_(sig, scheduler) + +## See: [b]res://addons/reactivex/engine/observable/godotnodelifecycle.gd[/b] +func from_godot_node_lifecycle_event(conn : Node, type : int) -> Observable: + return _GodotLifecycle_.from_godot_node_lifecycle_event_(conn, type) + +## See: [b]res://addons/reactivex/engine/observable/godotinputaction.gd[/b] +func from_godot_input_action(input_action : String, checks : Observable) -> Observable: + return _GodotInputAction_.from_godot_input_action_(input_action, checks) + +## See: [b]res://addons/reactivex/engine/observable/godotcoroutine.gd[/b] +func from_godot_coroutine(fun : Callable, bindings : Array = [], scheduler : SchedulerBase = null) -> Observable: + return _GodotCoroutine_.from_godot_coroutine_(fun, bindings, scheduler) + +## See: [b]"res://addons/reactivex/engine/observable/computeshader.gd"[/b] +func from_compute_shader(shader_path : String, rd : RenderingDevice, work_groups : Vector3i, uniform_sets : Array = [], scheduler : SchedulerBase = null) -> Observable: + return _ComputeShader_.from_compute_shader_(shader_path, rd, work_groups, uniform_sets, scheduler) + +## See: [b]"res://addons/reactivex/engine/observable/httprequest.gd"[/b] +func from_http_request(url : String, request_data = "", raw : bool = false, encoding : String = "", requester : HTTPRequest = null, custom_headers : PackedStringArray = PackedStringArray(), method : HTTPClient.Method = HTTPClient.METHOD_GET) -> Observable: + return _HttpRequest_.from_http_request_(url, request_data, raw, encoding, requester, custom_headers, method) + +## See: [b]"res://addons/reactivex/engine/operators/_processtimeinterval.gd"[/b] +func process_time_interval(initial_time : float = 0.0) -> Callable: + return _ProcessTimeInterval_.process_time_interval_(initial_time) + +## See: [b]"res://addons/reactivex/engine/operators/_processtimeinterval.gd"[/b] +func physics_time_interval(initial_time : float = 0.0) -> Callable: + return _ProcessTimeInterval_.physics_time_interval_(initial_time) diff --git a/addons/reactivex/engine/abc/godotsignalscheduler.gd b/addons/reactivex/engine/abc/godotsignalscheduler.gd new file mode 100644 index 0000000..47133f4 --- /dev/null +++ b/addons/reactivex/engine/abc/godotsignalscheduler.gd @@ -0,0 +1,17 @@ +extends SchedulerBase +class_name GodotSignalSchedulerBase + +## A scheduler for Godot signals +## +## Schedules actions for repeated future execution when a Godot [Signal] is emitted. + +## Schedule a signal callback for repeated execution every time a Godot [Signal] +## is emitted. +func schedule_signal( + _sig : Signal, + _n_args : int, + _action : Callable, + _state = null +) -> DisposableBase: + NotImplementedError.raise() + return null diff --git a/addons/reactivex/engine/abc/reactivecollection.gd b/addons/reactivex/engine/abc/reactivecollection.gd new file mode 100644 index 0000000..cd5edc3 --- /dev/null +++ b/addons/reactivex/engine/abc/reactivecollection.gd @@ -0,0 +1,36 @@ +extends ReadOnlyReactiveCollectionBase +class_name ReactiveCollectionBase + +func reset(): + NotImplementedError.raise() + +func add_item(item) -> int: + NotImplementedError.raise() + return -1 + +func remove_item(item) -> int: + NotImplementedError.raise() + return -1 + +func remove_at(index : int) -> Variant: + NotImplementedError.raise() + return null + +func replace_item(item, with) -> int: + NotImplementedError.raise() + return -1 + +func replace_at(index : int, item) -> Variant: + NotImplementedError.raise() + return null + +func swap(idx1 : int, idx2 : int) -> Tuple: + NotImplementedError.raise() + return Tuple.Empty() + +func move_to(curr_index : int, new_index : int): + NotImplementedError.raise() + +func insert_at(index : int, elem): + NotImplementedError.raise() + diff --git a/addons/reactivex/engine/abc/reactivedictionary.gd b/addons/reactivex/engine/abc/reactivedictionary.gd new file mode 100644 index 0000000..a177663 --- /dev/null +++ b/addons/reactivex/engine/abc/reactivedictionary.gd @@ -0,0 +1,12 @@ +extends ReadOnlyReactiveDictionaryBase +class_name ReactiveDictionaryBase + +func clear(): + NotImplementedError.raise() + +func erase(key) -> bool: + NotImplementedError.raise() + return false + +func set_pair(key, value): + NotImplementedError.raise() diff --git a/addons/reactivex/engine/abc/reactiveproperty.gd b/addons/reactivex/engine/abc/reactiveproperty.gd new file mode 100644 index 0000000..3497e17 --- /dev/null +++ b/addons/reactivex/engine/abc/reactiveproperty.gd @@ -0,0 +1,5 @@ +extends ReadOnlyReactivePropertyBase +class_name ReactivePropertyBase + +func _set_value(_value): + NotImplementedError.raise() diff --git a/addons/reactivex/engine/abc/reactivesignal.gd b/addons/reactivex/engine/abc/reactivesignal.gd new file mode 100644 index 0000000..2939e91 --- /dev/null +++ b/addons/reactivex/engine/abc/reactivesignal.gd @@ -0,0 +1,49 @@ +extends Observable +class_name ReactiveSignalBase + +var this + +func _init(subscribe_ : Callable): + this = self + this.unreference() + + super._init(subscribe_) + +func _notification(what): + if what == NOTIFICATION_PREDELETE: + this.dispose() + +func _emit(_args = []): + NotImplementedError.raise() + +func attach(_cb : Callable): + NotImplementedError.raise() + +func detach(_cb : Callable): + NotImplementedError.raise() + +func emit(): + self._emit() +func emit0(): + self._emit([]) +func emit1(arg0): + self._emit([arg0]) +func emit2(arg0, arg1): + self._emit([arg0, arg1]) +func emit3(arg0, arg1, arg2): + self._emit([arg0, arg1, arg2]) +func emit4(arg0, arg1, arg2, arg3): + self._emit([arg0, arg1, arg2, arg3]) +func emit5(arg0, arg1, arg2, arg3, arg4): + self._emit([arg0, arg1, arg2, arg3, arg4]) +func emit6(arg0, arg1, arg2, arg3, arg4, arg5): + self._emit([arg0, arg1, arg2, arg3, arg4, arg5]) +func emit7(arg0, arg1, arg2, arg3, arg4, arg5, arg6): + self._emit([arg0, arg1, arg2, arg3, arg4, arg5, arg6]) +func emit8(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7): + self._emit([arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7]) +func emit_unit(): + self._emit([StreamItem.Unit()]) + +func dispose(): + NotImplementedError.raise() diff --git a/addons/reactivex/engine/abc/readonlyreactivecollection.gd b/addons/reactivex/engine/abc/readonlyreactivecollection.gd new file mode 100644 index 0000000..d48e98d --- /dev/null +++ b/addons/reactivex/engine/abc/readonlyreactivecollection.gd @@ -0,0 +1,190 @@ +extends Comparable +class_name ReadOnlyReactiveCollectionBase + +class CollectionAddEvent extends Comparable: + var index : int + var value + + func _init(index_ : int, value_): + self.index = index_ + self.value = value_ + + func _to_string() -> String: + return "Index: " + str(index) + " Value: " + str(value) + + func get_hash_code() -> int: + return hash(index) ^ hash(value) << 2 + + func eq(other_ : Comparable) -> bool: + if not (other_ is ReadOnlyReactiveCollectionBase.CollectionAddEvent): + return false + var other = other_ as ReadOnlyReactiveCollectionBase.CollectionAddEvent + if index != other.index: + return false + return GDRx.eq(value, other.value) + +class CollectionRemoveEvent extends Comparable: + var index : int + var value + + func _init(index_ : int, value_): + self.index = index_ + self.value = value_ + + func _to_string() -> String: + return "Index: " + str(index) + " Value: " + str(value) + + func get_hash_code() -> int: + return hash(index) ^ hash(value) << 2 + + func eq(other_ : Comparable) -> bool: + if not (other_ is ReadOnlyReactiveCollectionBase.CollectionRemoveEvent): + return false + var other = other_ as ReadOnlyReactiveCollectionBase.CollectionRemoveEvent + if index != other.index: + return false + return GDRx.eq(value, other.value) + +class CollectionMoveEvent extends Comparable: + var old_index : int + var new_index : int + var value + + func _init(old_index_ : int, new_index_ : int, value_): + self.old_index = old_index_ + self.new_index = new_index_ + self.value = value_ + + func _to_string() -> String: + return "OldIndex: " + str(old_index) + " NewIndex: " + str(new_index) \ + + " Value: " + str(value) + + func get_hash_code() -> int: + return hash(old_index) ^ hash(new_index) << 2 ^ hash(value) + + func eq(other_ : Comparable) -> bool: + if not (other_ is ReadOnlyReactiveCollectionBase.CollectionMoveEvent): + return false + var other = other_ as ReadOnlyReactiveCollectionBase.CollectionMoveEvent + if old_index != other.old_index || new_index != other.new_index: + return false + return GDRx.eq(value, other.value) + +class CollectionReplaceEvent extends Comparable: + var index : int + var old_value + var new_value + + func _init(index_ : int, old_value_, new_value_): + self.index = index_ + self.old_value = old_value_ + self.new_value = new_value_ + + func _to_string() -> String: + return "Index: " + str(index) + " OldValue: " + str(old_value) \ + + " NewValue: " + str(new_value) + + func get_hash_code() -> int: + return hash(index) ^ hash(old_value) << 2 ^ hash(new_value) + + func eq(other_ : Comparable) -> bool: + if not (other_ is ReadOnlyReactiveCollectionBase.CollectionReplaceEvent): + return false + var other = other_ as ReadOnlyReactiveCollectionBase.CollectionReplaceEvent + if index != other.index: + return false + return GDRx.eq(old_value, other.old_value) and GDRx.eq(new_value, other.new_value) + +var Count : int: + get: return self.size() + +## [Observable]<[ReadOnlyReactiveCollectionBase.CollectionAddEvent]> +var ObserveAdd : Observable: + get: return self._observe_add.oftype(ReadOnlyReactiveCollectionBase.CollectionAddEvent) +var _observe_add : Observable = GDRx.throw(NotImplementedError.new()) + +## Creates an [Observable] which emits the collection's current element count +## when the size changes. +func ObserveCountChanged(_notify_current_count : bool = false) -> Observable: + NotImplementedError.raise() + return Observable.new() + +## [Observable]<[int]> +var ObserveCount : Observable: + get: return ObserveCountChanged(true).oftype(TYPE_INT) + +## [Observable]<[ReadOnlyReactiveCollectionBase.CollectionMoveEvent]> +var ObserveMove : Observable: + get: return self._observe_move.oftype(ReadOnlyReactiveCollectionBase.CollectionMoveEvent) +var _observe_move : Observable = GDRx.throw(NotImplementedError.new()) + +## [Observable]<[ReadOnlyReactiveCollectionBase.CollectionRemoveEvent]> +var ObserveRemove : Observable: + get: return self._observe_remove.oftype(ReadOnlyReactiveCollectionBase.CollectionRemoveEvent) +var _observe_remove : Observable = GDRx.throw(NotImplementedError.new()) + +## [Observable]<[ReadOnlyReactiveCollectionBase.CollectionReplaceEvent]> +var ObserveReplace : Observable: + get: return self._observe_replace.oftype(ReadOnlyReactiveCollectionBase.CollectionReplaceEvent) +var _observe_replace : Observable = GDRx.throw(NotImplementedError.new()) + +## [Observable]<[StreamItem._Unit]> +var ObserveReset : Observable: + get: return self._observe_reset.oftype(StreamItem._Unit) +var _observe_reset : Observable = GDRx.throw(NotImplementedError.new()) + +var this + +func _init(): + this = self + this.unreference() + +func _notification(what): + if what == NOTIFICATION_PREDELETE: + this.dispose() + +## Override from [Comparable] +func eq(other) -> bool: + if other is ReadOnlyReactiveCollectionBase: + return self.to_list().hash() == other.to_list().hash() + return false + +func at(index : int): + NotImplementedError.raise() + return null + +func find(item) -> int: + NotImplementedError.raise() + return -1 + +func to_list() -> Array: + NotImplementedError.raise() + return [] + +func iter() -> Iterator: + NotImplementedError.raise() + return null + +func size() -> int: + NotImplementedError.raise() + return -1 + +func dispose(): + NotImplementedError.raise() + +var __it__ : Iterator +var __curr__ : Variant + +func _iter_init(arg): + self.__it__ = iter() + var continue_ = self.__it__.has_next() + self.__curr__ = self.__it__.next() + return continue_ + +func _iter_next(arg): + var continue_ = self.__it__.has_next() + self.__curr__ = self.__it__.next() + return continue_ + +func _iter_get(arg): + return self.__curr__ diff --git a/addons/reactivex/engine/abc/readonlyreactivedictionary.gd b/addons/reactivex/engine/abc/readonlyreactivedictionary.gd new file mode 100644 index 0000000..2db6742 --- /dev/null +++ b/addons/reactivex/engine/abc/readonlyreactivedictionary.gd @@ -0,0 +1,161 @@ +extends Comparable +class_name ReadOnlyReactiveDictionaryBase + +class DictionaryAddKeyEvent extends Comparable: + var key + var value + + func _init(key_, value_): + self.key = key_ + self.value = value_ + + func _to_string() -> String: + return "Key: " + str(key) + " Value: " + str(value) + + func get_hash_code() -> int: + return hash(key) ^ hash(value) << 2 + + func eq(other) -> bool: + if not (other is DictionaryAddKeyEvent): + return false + return GDRx.eq(key, other.key) and GDRx.eq(value, other.value) + +class DictionaryRemoveKeyEvent extends Comparable: + var key + var value + + func _init(key_, value_): + self.key = key_ + self.value = value_ + + func _to_string() -> String: + return "Key: " + str(key) + " Value: " + str(value) + + func get_hash_code() -> int: + return hash(key) ^ hash(value) << 2 + + func eq(other) -> bool: + if not (other is DictionaryRemoveKeyEvent): + return false + return GDRx.eq(key, other.key) and GDRx.eq(value, other.value) + +class DictionaryUpdateValueEvent extends Comparable: + var key + var value + + func _init(key_, value_): + self.key = key_ + self.value = value_ + + func _to_string() -> String: + return "Key: " + str(key) + " Value: " + str(value) + + func get_hash_code() -> int: + return hash(key) ^ hash(value) << 2 + + func eq(other) -> bool: + if not (other is DictionaryUpdateValueEvent): + return false + return GDRx.eq(key, other.key) and GDRx.eq(value, other.value) + +var Count : int: + get: return self.size() + +## [Observable]<[ReadOnlyReactiveDictionaryBase.DictionaryAddKeyEvent]> +var ObserveAddKey : Observable: + get: return self._observe_add_key.oftype(ReadOnlyReactiveDictionaryBase.DictionaryAddKeyEvent) +var _observe_add_key : Observable = GDRx.throw(NotImplementedError.new()) + +## Creates an [Observable] which emits the dictionary's current key count +## when the size changes. +func ObserveCountChanged(_notify_current_count : bool = false) -> Observable: + return GDRx.throw(NotImplementedError.new()) + + +## [Observable]<[int]> +var ObserveCount : Observable: + get: return ObserveCountChanged(true).oftype(TYPE_INT) + +## [Observable]<[ReadOnlyReactiveDictionaryBase.DictionaryRemoveKeyEvent]> +var ObserveRemoveKey : Observable: + get: return self._observe_remove_key.oftype(ReadOnlyReactiveDictionaryBase.DictionaryRemoveKeyEvent) +var _observe_remove_key : Observable = GDRx.throw(NotImplementedError.new()) + +## [Observable]<[ReadOnlyReactiveDictionaryBase.DictionaryUpdateValueEvent]> +var ObserveUpdateValue : Observable: + get: return self._observer_update_value.oftype(ReadOnlyReactiveDictionaryBase.DictionaryUpdateValueEvent) +var _observer_update_value : Observable = GDRx.throw(NotImplementedError.new()) + +var this + +func _init(): + this = self + this.unreference() + +func _notification(what): + if what == NOTIFICATION_PREDELETE: + this.dispose() + +func to_dict() -> Dictionary: + NotImplementedError.raise() + return {} + +func find_key(value) -> Variant: + NotImplementedError.raise() + return null + +func get_value(key, default = null) -> Variant: + NotImplementedError.raise() + return null + +func has_key(key) -> bool: + NotImplementedError.raise() + return false + +func has_all(keys : Array) -> bool: + NotImplementedError.raise() + return false + +func hash() -> int: + NotImplementedError.raise() + return 0 + +func is_empty() -> bool: + NotImplementedError.raise() + return false + +func keys() -> Array: + NotImplementedError.raise() + return [] + +func size() -> int: + NotImplementedError.raise() + return -1 + +func values() -> Array: + NotImplementedError.raise() + return [] + +func dispose(): + NotImplementedError.raise() + +func iter() -> Iterator: + NotImplementedError.raise() + return null + +var __it__ : Iterator +var __curr__ : Variant + +func _iter_init(arg): + self.__it__ = self.iter() + var continue_ = self.__it__.has_next() + self.__curr__ = self.__it__.next() + return continue_ + +func _iter_next(arg): + var continue_ = self.__it__.has_next() + self.__curr__ = self.__it__.next() + return continue_ + +func _iter_get(arg): + return self.__curr__ diff --git a/addons/reactivex/engine/abc/readonlyreactiveproperty.gd b/addons/reactivex/engine/abc/readonlyreactiveproperty.gd new file mode 100644 index 0000000..5c9eab0 --- /dev/null +++ b/addons/reactivex/engine/abc/readonlyreactiveproperty.gd @@ -0,0 +1,34 @@ +extends Observable +class_name ReadOnlyReactivePropertyBase + +## Wrapped value +var Value: + set(value): self._set_value(value) + get: return self._get_value() + +var this + +func _init(subscribe : Callable): + this = self + this.unreference() + + super._init(subscribe) + +func _notification(what): + if what == NOTIFICATION_PREDELETE: + this.dispose() + +func _set_value(__): + GDRx.raise_message("Tried to write to a ReadOnlyReactiveProperty") + +func _get_value(): + NotImplementedError.raise() + return null + +func eq(other) -> bool: + if other is ReadOnlyReactivePropertyBase: + return GDRx.eq(Value, other.Value) + return GDRx.eq(Value, other) + +func dispose(): + NotImplementedError.raise() diff --git a/addons/reactivex/engine/await/await.gd b/addons/reactivex/engine/await/await.gd new file mode 100644 index 0000000..de7fc8b --- /dev/null +++ b/addons/reactivex/engine/await/await.gd @@ -0,0 +1,47 @@ +class_name ObservableAwait + +signal _on_next(item : Variant) +signal _on_error(error : Variant) +signal _on_completed() + +func on_next(obs : Observable) -> Variant: + var fired_item = RefValue.Set(GDRx.NOT_SET) + + var on_next_ = func(i): + fired_item.v = i + self._on_next.emit(i) + + var __ = obs.take(1).subscribe(on_next_) + + if not GDRx.not_set(fired_item.v): + return fired_item.v + var i = await self._on_next + return i + +func on_error(obs : Observable) -> Variant: + var fired_error = RefValue.Set(GDRx.NOT_SET) + + var on_error_ = func(e): + fired_error.v = e + self._on_error.emit(e) + + var __ = obs.subscribe3(on_error_) + + if not GDRx.not_set(fired_error.v): + return fired_error.v + var e = await self._on_error + return e + +func on_completed(obs : Observable): + var fired_complete = RefValue.Set(false) + + var on_completed_ = func(): + fired_complete.v = true + self._on_completed.emit() + + var __ = obs.subscribe4(on_completed_) + + if fired_complete.v: + return + await self._on_completed + return diff --git a/addons/reactivex/engine/observable/computeshader.gd b/addons/reactivex/engine/observable/computeshader.gd new file mode 100644 index 0000000..b3740db --- /dev/null +++ b/addons/reactivex/engine/observable/computeshader.gd @@ -0,0 +1,74 @@ +## Represents a Compute Shader as an Observable Sequence. +## [br][br] +## Please note: This is a [b]cold[/b] [Observable]. It transfers data to GPU +## on subscribe. Thus, each subscription causes one computational step. +## When the compute shader finishes, the [RenderingDevice] is emitted as item. +## Afterwards the stream completes. +## [br][br] +## [b]Args:[/b] +## [br] +## [code]shader_path[/code] the path to the GLSL file with the shader source +## [br] +## [code]rd[/code] The current instance of [RenderingDevice] +## [br] +## [code]work_groups[/code]: Set x, y and z dimensions of work groups. +## [br] +## [code]uniform_sets[/code]: A list containing lists of [RDUniform] +## representing n uniform sets starting with [code]set = 0[/code] going +## upwards. +## [br] +## [code]scheduler[/code]: Schedules the CPU side of the shader computation +## [br][br] +## [b]Return:[/b] +## An observable sequence which performs a new computation on the device, +## each time an [Observer] subscribes. +static func from_compute_shader_( + shader_path : String, + rd : RenderingDevice, + work_groups : Vector3i, + uniform_sets : Array = [], + scheduler : SchedulerBase = null +) -> Observable: + + # Create shader and pipeline + var shader_file = load(shader_path) + var shader_spirv = shader_file.get_spirv() + var shader = rd.shader_create_from_spirv(shader_spirv) + var pipeline = rd.compute_pipeline_create(shader) + + var uniform_sets_rids = [] + for sid in range(uniform_sets.size()): + uniform_sets_rids.append( + rd.uniform_set_create(uniform_sets[sid], shader, sid) + ) + + var subscribe = func( + observer : ObserverBase, + scheduler_ : SchedulerBase = null + ) -> DisposableBase: + + var _scheduler + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = CurrentThreadScheduler.singleton() + + var action = func(_scheduler = null, _state = null): + # Setup compute pipeline and uniforms + var compute_list = rd.compute_list_begin() + rd.compute_list_bind_compute_pipeline(compute_list, pipeline) + for sid in range(uniform_sets_rids.size()): + rd.compute_list_bind_uniform_set(compute_list, uniform_sets_rids[sid], sid) + rd.compute_list_dispatch(compute_list, work_groups.x, work_groups.y, work_groups.z) + rd.compute_list_end() + + # Send to GPU + rd.submit() + # Sync with CPU + rd.sync() + + observer.on_next(rd) + observer.on_completed() + + return _scheduler.schedule(action) + + return Observable.new(subscribe) diff --git a/addons/reactivex/engine/observable/godotcoroutine.gd b/addons/reactivex/engine/observable/godotcoroutine.gd new file mode 100644 index 0000000..0b93d0d --- /dev/null +++ b/addons/reactivex/engine/observable/godotcoroutine.gd @@ -0,0 +1,42 @@ +## Represents a Godot coroutine as an observable sequence +static func from_godot_coroutine_( + fun : Callable, + bindings : Array = [], + scheduler : SchedulerBase = null +) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler_ : SchedulerBase = null + ) -> DisposableBase: + var coroutine : Callable = fun + var n_bind = bindings.size() + for argi in range(n_bind): + coroutine = coroutine.bind(bindings[n_bind - argi - 1]) + + var _scheduler + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = CurrentThreadScheduler.singleton() + + var action = func(_scheduler : SchedulerBase, _state = null): + var res = RefValue.Null() + + if GDRx.try(func(): + # WARNING! This await is not redundant as it seems. + # At this point we do not know if 'coroutine' really is a + # coroutine but either way should this code work! If 'coroutine' + # was a member function Godot would throw an error telling us + # that this is, in fact, a coroutine if it contains an 'await'. + @warning_ignore("redundant_await") + res.v = await coroutine.call() + observer.on_next(res.v) + observer.on_completed() + ) \ + .catch("Error", func(e): + observer.on_error(e) + ) \ + .end_try_catch(): return + + return _scheduler.schedule(action) + + return Observable.new(subscribe) diff --git a/addons/reactivex/engine/observable/godotinputaction.gd b/addons/reactivex/engine/observable/godotinputaction.gd new file mode 100644 index 0000000..5a89105 --- /dev/null +++ b/addons/reactivex/engine/observable/godotinputaction.gd @@ -0,0 +1,29 @@ +enum EInputState +{ + RELEASED = 0, JUST_PRESSED = 1, PRESSED = 2, JUST_RELEASED = 3 +} + +## Represents an input action as an observable sequence which emits input state +## each time [code]checks[/code] emits an item. +static func from_godot_input_action_(input_action : String, checks : Observable) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + + var prev_pressed = RefValue.Set(false) + var curr_pressed = RefValue.Set(false) + + var on_next = func(__): + prev_pressed.v = curr_pressed.v + curr_pressed.v = Input.is_action_pressed(input_action) + if prev_pressed.v != curr_pressed.v: + observer.on_next(EInputState.JUST_PRESSED if curr_pressed.v else EInputState.JUST_RELEASED) + observer.on_next(EInputState.PRESSED if curr_pressed.v else EInputState.RELEASED) + + return checks.subscribe( + on_next, observer.on_error, observer.on_completed, + scheduler + ) + + return Observable.new(subscribe) diff --git a/addons/reactivex/engine/observable/godotnodelifecycle.gd b/addons/reactivex/engine/observable/godotnodelifecycle.gd new file mode 100644 index 0000000..5df77c0 --- /dev/null +++ b/addons/reactivex/engine/observable/godotnodelifecycle.gd @@ -0,0 +1,75 @@ +class _NodeLifecycleListener extends Node: + signal on_event(data) + +class _ListenerOnProcess extends _NodeLifecycleListener: + func _process(delta : float): + on_event.emit(delta) + +class _ListenerOnPhysicsProcess extends _NodeLifecycleListener: + func _physics_process(delta : float): + on_event.emit(delta) + +class _ListenerOnInput extends _NodeLifecycleListener: + func _input(event : InputEvent): + on_event.emit(event) + +class _ListenerOnShortcutInput extends _NodeLifecycleListener: + func _shortcut_input(event : InputEvent): + on_event.emit(event) + +class _ListenerOnUnhandledInput extends _NodeLifecycleListener: + func _unhandled_input(event : InputEvent): + on_event.emit(event) + +class _ListenerOnUnhandledKeyInput extends _NodeLifecycleListener: + func _unhandled_key_input(event : InputEvent): + on_event.emit(event) + +## Represents [Node] lifecycle events like [method Node._process]. +## Observable emits argument from call as item on the stream. +## [br][br] +## [color=yellow]Warning![/color] This only creates a Node of type [b]_NodeLifecycleListener[/b] +## which is added as a child since it is not possible to get signals on lifecycle callbacks. +static func from_godot_node_lifecycle_event_(conn : Node, type : int) -> Observable: + var listener : RefValue = RefValue.Null() + var count : RefValue = RefValue.Set(0) + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + if count.v == 0: + match type: + 0: + listener.v = _ListenerOnProcess.new() + 1: + listener.v = _ListenerOnPhysicsProcess.new() + 2: + listener.v = _ListenerOnInput.new() + 3: + listener.v = _ListenerOnShortcutInput.new() + 4: + listener.v = _ListenerOnUnhandledInput.new() + 5: + listener.v = _ListenerOnUnhandledKeyInput.new() + listener.v.name = "__Listener$" + str(conn.get_instance_id()) + "__" + listener.v.process_priority = conn.process_priority + listener.v.process_mode = conn.process_mode + listener.v.process_physics_priority = conn.process_physics_priority + conn.call_deferred("add_child", listener.v) + count.v += 1 + + var dispose = func(): + count.v -= 1 + if count.v == 0 and listener.v != null: + listener.v.queue_free() + + var subscription = GDRx.gd.from_godot_signal(listener.v.on_event).subscribe( + observer, GDRx.basic.noop, GDRx.basic.noop, + scheduler + ) + + var cd : CompositeDisposable = CompositeDisposable.new([subscription, Disposable.new(dispose)]) + return cd + + return Observable.new(subscribe) diff --git a/addons/reactivex/engine/observable/godotsignal.gd b/addons/reactivex/engine/observable/godotsignal.gd new file mode 100644 index 0000000..24c0ba1 --- /dev/null +++ b/addons/reactivex/engine/observable/godotsignal.gd @@ -0,0 +1,69 @@ +## Represents a Godot [Signal] as an observable sequence +static func from_godot_signal_( + sig : Signal, + scheduler : SchedulerBase = null +) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler_ : SchedulerBase = null + ) -> DisposableBase: + var _scheduler : SchedulerBase = null + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = GodotSignalScheduler.singleton() + + var obj = instance_from_id(sig.get_object_id()) + if obj == null: + GDRx.raise(NullReferenceError.new()) + return Disposable.new() + var n_args = -1 + var sig_lst = obj.get_signal_list() + for dict in sig_lst: + if dict["name"] == sig.get_name(): + n_args = dict["args"].size() + break + + var action : Callable + match n_args: + 0: + action = func(): + observer.on_next(Tuple.new([])) + 1: + action = func(arg1): + if arg1 is Array: + observer.on_next(Tuple.new(arg1)) + else: + observer.on_next(arg1) + 2: + action = func(arg1, arg2): + observer.on_next(Tuple.new([arg1, arg2])) + 3: + action = func(arg1, arg2, arg3): + observer.on_next(Tuple.new([arg1, arg2, arg3])) + 4: + action = func(arg1, arg2, arg3, arg4): + observer.on_next(Tuple.new([arg1, arg2, arg3, arg4])) + 5: + action = func(arg1, arg2, arg3, arg4, arg5): + observer.on_next(Tuple.new([arg1, arg2, arg3, arg4, arg5])) + 6: + action = func(arg1, arg2, arg3, arg4, arg5, arg6): + observer.on_next(Tuple.new([arg1, arg2, arg3, arg4, arg5, arg6])) + 7: + action = func(arg1, arg2, arg3, arg4, arg5, arg6, arg7): + observer.on_next(Tuple.new([arg1, arg2, arg3, arg4, arg5, arg6, arg7])) + 8: + action = func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8): + observer.on_next(Tuple.new([arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8])) + _: + GDRx.raise(TooManyArgumentsError.new( + "Only up to 8 signal parameters supported! Use lists instead!")) + return Disposable.new() + + if not _scheduler is GodotSignalScheduler: + _scheduler = GodotSignalScheduler.singleton() + + var godot_signal_scheduler : GodotSignalScheduler = _scheduler + return godot_signal_scheduler.schedule_signal(sig, n_args, action) + + return Observable.new(subscribe) diff --git a/addons/reactivex/engine/observable/httprequest.gd b/addons/reactivex/engine/observable/httprequest.gd new file mode 100644 index 0000000..eca3941 --- /dev/null +++ b/addons/reactivex/engine/observable/httprequest.gd @@ -0,0 +1,77 @@ +## Struct representing the results of a [HTTPRequest] +class HttpRequestResult: + var result : int + var response_code : int + var headers : PackedStringArray + var body : PackedByteArray + var decoded : String = "" + +## Performs an [HTTPRequest] on subscribe +static func from_http_request_( + url : String, + request_data = "", + raw : bool = false, + encoding : String = "", + requester : HTTPRequest = null, + custom_headers : PackedStringArray = PackedStringArray(), +# tls_validate_domain : bool = true, + method : HTTPClient.Method = HTTPClient.METHOD_GET +) -> Observable: + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + + var gss : GodotSignalScheduler + if scheduler != null and scheduler is GodotSignalScheduler: + gss = scheduler as GodotSignalScheduler + else: gss = GodotSignalScheduler.singleton() + + var _requester : HTTPRequest = requester if requester != null else HTTPRequest.new() + if requester == null: GDRx.add_child(_requester) + var n_bytes = _requester.get_downloaded_bytes() + + var action = func(result : int, response_code : int, headers : PackedStringArray, body : PackedByteArray): + if requester == null: _requester.queue_free() + if _requester.get_downloaded_bytes() - n_bytes == 0: + observer.on_error(HttpRequestFailedError.new(url, 0, "No data received")) + return + var http_request_result : HttpRequestResult = HttpRequestResult.new() + http_request_result.result = result + http_request_result.response_code = response_code + http_request_result.headers = headers + http_request_result.body = body + if not encoding.is_empty(): + var encodings = { + "ascii" : func(): return body.get_string_from_ascii(), + "utf8" : func(): return body.get_string_from_utf8(), + "utf16" : func(): return body.get_string_from_utf16(), + "utf32" : func(): return body.get_string_from_utf32() + } + if not encoding in encodings: + observer.on_error(BadArgumentError.new("Unknown encoding: " + str(encoding))) + return + var get_string : Callable = encodings[encoding] + http_request_result.decoded = get_string.call() + observer.on_next(http_request_result) + observer.on_completed() + + var dispose = func(): + if requester == null: + _requester.cancel_request() + _requester.queue_free() + + var request_cb : Callable = _requester.request_raw if raw else _requester.request + var error_code = request_cb.call(url, custom_headers, method, request_data) + if error_code: + dispose.call() + observer.on_error(HttpRequestFailedError.new( + url, error_code, "Unable to create request")) + return Disposable.new() + + var subscription = gss.schedule_signal(_requester.request_completed, 4, action) + var cd = CompositeDisposable.new([Disposable.new(dispose), subscription]) + return cd + + return Observable.new(subscribe) diff --git a/addons/reactivex/engine/observable/reactiveproperty/reactivecollection.gd b/addons/reactivex/engine/observable/reactiveproperty/reactivecollection.gd new file mode 100644 index 0000000..2168c67 --- /dev/null +++ b/addons/reactivex/engine/observable/reactiveproperty/reactivecollection.gd @@ -0,0 +1,293 @@ +extends ReactiveCollectionBase +class_name ReactiveCollection + +var _count : int +var _data : Array +var _observers : Dictionary +var _rwlock : ReadWriteLock + +var is_disposed : bool + +func _get_subscription(event_class, notify_count = false) -> Callable: + var wself : WeakRef = weakref(self) + + return func(observer : ObserverBase, _scheduler : SchedulerBase = null) -> DisposableBase: + var prop : ReactiveCollection = wself.get_ref() + + if not prop or prop.is_disposed: + observer.on_completed() + return Disposable.new() + + if notify_count: + observer.on_next(prop.Count) + + if true: + var __ = ReadWriteLockGuard.new(prop._rwlock, false) + if not (event_class in prop._observers): + prop._observers[event_class] = [] + prop._observers[event_class].push_back(observer) + + var dispose_ = func(): + var _prop = wself.get_ref() + if not _prop: + return + var __ = ReadWriteLockGuard.new(prop._rwlock, false) + prop._observers[event_class].erase(observer) + + return Disposable.new(dispose_) + +func _notify_all(event_class, event): + var observers_ : Array + if true: + var __ = ReadWriteLockGuard.new(self._rwlock, true) + if event_class in self._observers: + observers_ = self._observers[event_class].duplicate() + for observer in observers_: + observer.on_next(event) + +func _disconnect_all(event_class): + var observers_ : Array + if true: + var __ = ReadWriteLockGuard.new(this._rwlock, true) + if event_class in this._observers: + observers_ = this._observers[event_class].duplicate() + for observer in observers_: + observer.on_completed() + +func _init(collection = []): + super._init() + + self._count = 0 + self._data = [] + self._observers = {} + self._rwlock = ReadWriteLock.new() + self.is_disposed = false + + var it : Iterator = GDRx.iter(collection) + while it.has_next(): + self._add_item(it.next()) + + self._observe_add = Observable.new(self._get_subscription(CollectionAddEvent)) + self._observe_move = Observable.new(self._get_subscription(CollectionMoveEvent)) + self._observe_remove = Observable.new(self._get_subscription(CollectionRemoveEvent)) + self._observe_replace = Observable.new(self._get_subscription(CollectionReplaceEvent)) + self._observe_reset = Observable.new(self._get_subscription("Reset")) + +func ObserveCountChanged(notify_current_count : bool = false) -> Observable: + return Observable.new(self._get_subscription("CountChanged", notify_current_count)) + +func _add_item(item) -> int: + self._data.append(item) + self._count += 1 + return self._count - 1 + +func add_item(item) -> int: + if self.is_disposed: + DisposedError.raise() + return -1 + var index = self._add_item(item) + var event_add = CollectionAddEvent.new(index, item) + self._notify_all(CollectionAddEvent, event_add) + self._notify_all("CountChanged", self._count) + return index + +func _remove_item(item) -> int: + var index = self._data.find(item) + if index >= 0: + self._data.remove_at(index) + self._count -= 1 + return index + +func remove_item(item) -> int: + if self.is_disposed: + DisposedError.raise() + return -1 + var index = self._remove_item(item) + if index >= 0: + var event_remove = CollectionRemoveEvent.new(index, item) + self._notify_all(CollectionRemoveEvent, event_remove) + self._notify_all("CountChanged", self._count) + return index + +func _remove_at(index : int) -> Variant: + if index >= self._count: + return null + var value = self._data[index] + self._data.remove_at(index) + self._count -= 1 + return value + +func remove_at(index : int) -> Variant: + if self.is_disposed: + return DisposedError.raise() + var value = self._remove_at(index) + if value != null: + var event_remove = CollectionRemoveEvent.new(index, value) + self._notify_all(CollectionRemoveEvent, event_remove) + self._notify_all("CountChanged", self._count) + return value + +func _replace_item(item, with) -> int: + var index = self._data.find(item) + if index >= 0: + self._data[index] = with + return index + +func replace_item(item, with) -> int: + if self.is_disposed: + return DisposedError.raise() + if GDRx.eq(item, with): + return self._data.find(item) + var index : int = self._replace_item(item, with) + if index >= 0: + var event_replace = CollectionReplaceEvent.new(index, item, with) + self._notify_all(CollectionReplaceEvent, event_replace) + return index + +func _replace_at(index : int, item) -> Variant: + if index >= self._count: + return null + var value = self._data[index] + self._data[index] = item + return value + +func replace_at(index : int, item) -> Variant: + if self.is_disposed: + return DisposedError.raise() + var value = self._replace_at(index, item) + if value != null and GDRx.neq(value, item): + var event_replace = CollectionReplaceEvent.new(index, value, item) + self._notify_all(CollectionReplaceEvent, event_replace) + return value + +func _swap(idx1 : int, idx2 : int) -> Tuple: + if idx1 >= self._count or idx2 >= self._count: + return null + var tmp = self._data[idx1] + self._data[idx1] = self._data[idx2] + self._data[idx2] = tmp + return Tuple.new([self._data[idx2], self._data[idx1]]) + +func swap(idx1 : int, idx2 : int) -> Tuple: + if self.is_disposed: + return DisposedError.raise() + if idx1 >= self._count or idx2 >= self._count: + return + var pair = self._swap(idx1, idx2) + if GDRx.eq(pair.at(0), pair.at(1)): + return pair + var event_move1 = CollectionMoveEvent.new(idx1, idx2, pair.at(0)) + var event_move2 = CollectionMoveEvent.new(idx2, idx1, pair.at(1)) + self._notify_all(CollectionMoveEvent, event_move1) + self._notify_all(CollectionMoveEvent, event_move2) + return pair + +func _move_to(curr_index : int, new_index : int): + if curr_index >= self._count or new_index >= self._count: + return + var tmp = self._data[curr_index] + self._data.remove_at(curr_index) + self._data.insert(new_index, tmp) + +func move_to(old_index : int, new_index : int): + if self.is_disposed: + return DisposedError.raise() + if old_index >= self._count or new_index >= self._count or old_index == new_index: + return + var moved = self._data[old_index] + self._move_to(old_index, new_index) + var event_move = CollectionMoveEvent.new(old_index, new_index, moved) + self._notify_all(CollectionMoveEvent, event_move) + +func _insert_at(index : int, elem): + if index > self._count: + return + self._data.insert(index, elem) + self._count += 1 + +func insert_at(index : int, elem): + if self.is_disposed: + return DisposedError.raise() + if index > self._count: + return + self._insert_at(index, elem) + var event_add = CollectionAddEvent.new(index, elem) + self._notify_all(CollectionAddEvent, event_add) + self._notify_all("CountChanged", self._count) + +func _at(index : int): + return self._data[index] + +func at(index : int): + if self.is_disposed: + return DisposedError.raise() + return self._at(index) + +func _find(item) -> int: + return self._data.find(item) + +func find(item) -> int: + if self.is_disposed: + DisposedError.raise() + return -1 + return self._find(item) + +func _reset(): + self._data.clear() + self._count = 0 + +func reset(): + if self.is_disposed: + return DisposedError.raise() + var c = self._count + self._reset() + self._notify_all("Reset", StreamItem.Unit()) + if self._count != c: + self._notify_all("CountChanged", self._count) + +func iter() -> Iterator: + if self.is_disposed: + return DisposedError.raise() + return GDRx.iter(self._data) + +func _to_list() -> Array: + return self._data.duplicate() + +func to_list() -> Array: + if self.is_disposed: + DisposedError.raise() + return [] + return self._data.duplicate() + +func _size() -> int: + return self._count + +func size() -> int: + if self.is_disposed: + DisposedError.raise() + return -1 + return self._count + +func dispose(): + if this.is_disposed: + return + this.is_disposed = true + + this._disconnect_all(CollectionAddEvent) + this._disconnect_all(CollectionMoveEvent) + this._disconnect_all(CollectionRemoveEvent) + this._disconnect_all(CollectionReplaceEvent) + this._disconnect_all("Reset") + this._disconnect_all("CountChanged") + + this._data = [] + this._count = -1 + this._observers = {} + +func to_readonly() -> ReadOnlyReactiveCollection: + return ReadOnlyReactiveCollection.new(self) + +func _to_string() -> String: + if self.is_disposed: + return "<>" + return str(self._data) diff --git a/addons/reactivex/engine/observable/reactiveproperty/reactivedictionary.gd b/addons/reactivex/engine/observable/reactiveproperty/reactivedictionary.gd new file mode 100644 index 0000000..e66ae35 --- /dev/null +++ b/addons/reactivex/engine/observable/reactiveproperty/reactivedictionary.gd @@ -0,0 +1,187 @@ +extends ReactiveDictionaryBase +class_name ReactiveDictionary + +var _count : int +var _data : Dictionary +var _observers : Dictionary +var _rwlock : ReadWriteLock + +var is_disposed : bool + +func _get_subscription(event_class, notify_count = false) -> Callable: + var wself : WeakRef = weakref(self) + + return func(observer : ObserverBase, _scheduler : SchedulerBase = null) -> DisposableBase: + var prop : ReactiveDictionary = wself.get_ref() + + if not prop or prop.is_disposed: + observer.on_completed() + return Disposable.new() + + if notify_count: + observer.on_next(prop.Count) + + if true: + var __ = ReadWriteLockGuard.new(prop._rwlock, false) + if not (event_class in prop._observers): + prop._observers[event_class] = [] + prop._observers[event_class].push_back(observer) + + var dispose_ = func(): + var _prop = wself.get_ref() + if not _prop: + return + var __ = ReadWriteLockGuard.new(prop._rwlock, false) + prop._observers[event_class].erase(observer) + + return Disposable.new(dispose_) + +func _notify_all(event_class, event): + var observers_ : Array + if true: + var __ = ReadWriteLockGuard.new(self._rwlock, true) + if event_class in self._observers: + observers_ = self._observers[event_class].duplicate() + for observer in observers_: + observer.on_next(event) + +func _disconnect_all(event_class): + var observers_ : Array + if true: + var __ = ReadWriteLockGuard.new(this._rwlock, true) + if event_class in this._observers: + observers_ = this._observers[event_class].duplicate() + for observer in observers_: + observer.on_completed() + +func _init(dict : Dictionary = {}): + super._init() + + self._count = 0 + self._data = dict.duplicate() + self._observers = {} + self._rwlock = ReadWriteLock.new() + self.is_disposed = false + + self._observe_add_key = Observable.new(self._get_subscription(DictionaryAddKeyEvent)) + self._observe_remove_key = Observable.new(self._get_subscription(DictionaryRemoveKeyEvent)) + self._observer_update_value = Observable.new(self._get_subscription(DictionaryUpdateValueEvent)) + +func ObserveCountChanged(notify_current_count : bool = false) -> Observable: + return Observable.new(self._get_subscription("CountChanged", notify_current_count)) + +func clear(): + var keys = self.keys() + for key in keys: + self.erase(key) + +func erase(key) -> bool: + if self.is_disposed: + DisposedError.raise() + return false + if self._data.has(key): + var value = self._data[key] + self._data.erase(key) + self._count -= 1 + self._notify_all("CountChanged", self._count) + self._notify_all(DictionaryRemoveKeyEvent, DictionaryRemoveKeyEvent.new(key, value)) + return true + return false + +func set_pair(key, value): + if self.is_disposed: + DisposedError.raise() + return null + if not self._data.has(key): + self._count += 1 + self._data[key] = value + self._notify_all("CountChanged", self._count) + self._notify_all(DictionaryAddKeyEvent, DictionaryAddKeyEvent.new(key, value)) + else: + self._data[key] = value + self._notify_all(DictionaryUpdateValueEvent, DictionaryUpdateValueEvent.new(key, value)) + +func to_dict() -> Dictionary: + if self.is_disposed: + DisposedError.raise() + return {} + return self._data.duplicate() + +func find_key(value) -> Variant: + if self.is_disposed: + DisposedError.raise() + return null + return self._data.find_key(value) + +func get_value(key, default = null) -> Variant: + if self.is_disposed: + DisposedError.raise() + return null + return self._data.get(key, default) + +func has_key(key) -> bool: + if self.is_disposed: + DisposedError.raise() + return false + return self._data.has(key) + +func has_all(keys : Array) -> bool: + if self.is_disposed: + DisposedError.raise() + return false + return self._data.has_all(keys) + +func hash() -> int: + if self.is_disposed: + DisposedError.raise() + return 0 + return self._data.hash() + +func is_empty() -> bool: + if self.is_disposed: + DisposedError.raise() + return false + return self._data.is_empty() + +func keys() -> Array: + if self.is_disposed: + DisposedError.raise() + return [] + return self._data.keys() + +func size() -> int: + if self.is_disposed: + DisposedError.raise() + return -1 + return self._count + +func values() -> Array: + if self.is_disposed: + DisposedError.raise() + return [] + return self._data.values() + +func dispose(): + if this.is_disposed: + return + this.is_disposed = true + + this._disconnect_all(DictionaryAddKeyEvent) + this._disconnect_all(DictionaryRemoveKeyEvent) + this._disconnect_all(DictionaryUpdateValueEvent) + this._disconnect_all("CountChanged") + + this._data = {} + this._count = -1 + this._observers = {} + +func to_readonly() -> ReadOnlyReactiveDictionary: + return ReadOnlyReactiveDictionary.new(self) + +func _to_string() -> String: + if self.is_disposed: + return "<>" + return str(self._data) + +func iter() -> Iterator: + return GDRx.iter(self._data) diff --git a/addons/reactivex/engine/observable/reactiveproperty/reactiveproperty.gd b/addons/reactivex/engine/observable/reactiveproperty/reactiveproperty.gd new file mode 100644 index 0000000..ce39d42 --- /dev/null +++ b/addons/reactivex/engine/observable/reactiveproperty/reactiveproperty.gd @@ -0,0 +1,248 @@ +extends ReactivePropertyBase +class_name ReactiveProperty + +## An observable property. +## +## Wraps a value and emits an item whenever it is changed. +## The emitted item is the new value of the [ReactiveProperty]. + +var _latest_value +var _source_subscription : DisposableBase +var _observers : Array[ObserverBase] +var _distinct_until_changed : bool +var _raise_latest_value_on_subscribe : bool + +var _rwlock : ReadWriteLock + +var is_disposed : bool + +func _get_value(): + return self._latest_value + +func _set_value(value): + if self._distinct_until_changed and self._latest_value == value: + return + self._latest_value = value + + var observers_ : Array[ObserverBase] + if true: + var __ = ReadWriteLockGuard.new(self._rwlock, true) + observers_ = self._observers.duplicate() + for obs in observers_: + obs.on_next(value) + +func _to_string() -> String: + if self.is_disposed: + return "<>" + return str(self.Value) + +func _init( + initial_value_, + distinct_until_changed_ : bool = true, + raise_latest_value_on_subscribe_ : bool = true, + source : Observable = null +): + var wself : WeakRef = weakref(self) + + self.is_disposed = false + + self._observers = [] + self._rwlock = ReadWriteLock.new() + + self._latest_value = initial_value_ + self._distinct_until_changed = distinct_until_changed_ + self._raise_latest_value_on_subscribe = raise_latest_value_on_subscribe_ + + if source != null: + self._source_subscription = source.subscribe(func(i): wself.get_ref().Value = i) + + @warning_ignore("shadowed_variable") + var subscribe = func( + observer : ObserverBase, + _scheduler : SchedulerBase = null + ) -> DisposableBase: + var prop : ReactiveProperty = wself.get_ref() + + if not prop or prop.is_disposed: + observer.on_completed() + return Disposable.new() + + if true: + var __ = ReadWriteLockGuard.new(prop._rwlock, false) + prop._observers.push_back(observer) + + if prop._raise_latest_value_on_subscribe: + observer.on_next(prop._latest_value) + + var dispose_ = func(): + var _prop : ReactiveProperty = wself.get_ref() + if not _prop: + return + var __ = ReadWriteLockGuard.new(_prop._rwlock, false) + _prop._observers.erase(observer) + + return Disposable.new(dispose_) + + super._init(subscribe) + +func dispose(): + if this.is_disposed: + return + + this.is_disposed = true + + var observers_ : Array[ObserverBase] + if true: + var __ = ReadWriteLockGuard.new(this._rwlock, true) + observers_ = this._observers.duplicate() + for obs in observers_: + obs.on_completed() + + if true: + var __ = ReadWriteLockGuard.new(this._rwlock, false) + this._observers.clear() + + if this._source_subscription != null: + this._source_subscription.dispose() + +func to_readonly() -> ReadOnlyReactiveProperty: + return ReadOnlyReactiveProperty.new( + self, Value, self._distinct_until_changed, + self._raise_latest_value_on_subscribe + ) + +static func FromGetSet( + getter : Callable, + setter : Callable, + distinct_until_changed_ : bool = true, + raise_latest_value_on_subscribe_ : bool = true +) -> ReactiveProperty: + var prop = ReactiveProperty.new( + getter.call(), + distinct_until_changed_, + raise_latest_value_on_subscribe_ + ) + + prop.subscribe(func(x): setter.call(x)).dispose_with(prop) + return prop + +static func FromMember( + target, + member_name : StringName, + convert_cb = GDRx.basic.identity, + convert_back_cb = GDRx.basic.identity, + distinct_until_changed_ : bool = true, + raise_latest_value_on_subscribe_ : bool = true +) -> ReactiveProperty: + + var getter = func(): + return target.get(member_name) + var setter = func(v): + target.set(member_name, v) + + return FromGetSet( + func(): return convert_cb.call(getter.call()), + func(x): setter.call(convert_back_cb.call(x)), + distinct_until_changed_, + raise_latest_value_on_subscribe_ + ) + +static func Derived(p : ReadOnlyReactiveProperty, fn : Callable) -> ReadOnlyReactiveProperty: + return ReadOnlyReactiveProperty.new( + p.map(fn), + fn.call(p.Value) + ) + +static func Computed1(p : ReadOnlyReactiveProperty, fn : Callable) -> ReadOnlyReactiveProperty: + return Derived(p, fn) + +static func Computed2( + p1 : ReadOnlyReactiveProperty, + p2 : ReadOnlyReactiveProperty, + fn : Callable +) -> ReadOnlyReactiveProperty: + return ReadOnlyReactiveProperty.new( + p1.combine_latest([p2 as Observable]).map(func(tup : Tuple): return fn.call(tup.at(0), tup.at(1))), + fn.call(p1.Value, p2.Value) + ) + +static func Computed3( + p1 : ReadOnlyReactiveProperty, + p2 : ReadOnlyReactiveProperty, + p3 : ReadOnlyReactiveProperty, + fn : Callable +) -> ReadOnlyReactiveProperty: + return ReadOnlyReactiveProperty.new( + p1.combine_latest([p2 as Observable, p3 as Observable]).map(func(tup : Tuple): return fn.call(tup.at(0), tup.at(1), tup.at(2))), + fn.call(p1.Value, p2.Value, p3.Value) + ) + +static func Computed4( + p1 : ReadOnlyReactiveProperty, + p2 : ReadOnlyReactiveProperty, + p3 : ReadOnlyReactiveProperty, + p4 : ReadOnlyReactiveProperty, + fn : Callable +) -> ReadOnlyReactiveProperty: + return ReadOnlyReactiveProperty.new( + p1.combine_latest([p2 as Observable, p3 as Observable, p4 as Observable]).map(func(tup : Tuple): return fn.call(tup.at(0), tup.at(1), tup.at(2), tup.at(3))), + fn.call(p1.Value, p2.Value, p3.Value, p4.Value) + ) + +static func Computed5( + p1 : ReadOnlyReactiveProperty, + p2 : ReadOnlyReactiveProperty, + p3 : ReadOnlyReactiveProperty, + p4 : ReadOnlyReactiveProperty, + p5 : ReadOnlyReactiveProperty, + fn : Callable +) -> ReadOnlyReactiveProperty: + return ReadOnlyReactiveProperty.new( + p1.combine_latest([p2 as Observable, p3 as Observable, p4 as Observable, p5 as Observable]).map(func(tup : Tuple): return fn.call(tup.at(0), tup.at(1), tup.at(2), tup.at(3), tup.at(4))), + fn.call(p1.Value, p2.Value, p3.Value, p4.Value, p5.Value) + ) + +static func Computed6( + p1 : ReadOnlyReactiveProperty, + p2 : ReadOnlyReactiveProperty, + p3 : ReadOnlyReactiveProperty, + p4 : ReadOnlyReactiveProperty, + p5 : ReadOnlyReactiveProperty, + p6 : ReadOnlyReactiveProperty, + fn : Callable +) -> ReadOnlyReactiveProperty: + return ReadOnlyReactiveProperty.new( + p1.combine_latest([p2 as Observable, p3 as Observable, p4 as Observable, p5 as Observable, p6 as Observable]).map(func(tup : Tuple): return fn.call(tup.at(0), tup.at(1), tup.at(2), tup.at(3), tup.at(4), tup.at(5))), + fn.call(p1.Value, p2.Value, p3.Value, p4.Value, p5.Value, p6.Value) + ) + +static func Computed7( + p1 : ReadOnlyReactiveProperty, + p2 : ReadOnlyReactiveProperty, + p3 : ReadOnlyReactiveProperty, + p4 : ReadOnlyReactiveProperty, + p5 : ReadOnlyReactiveProperty, + p6 : ReadOnlyReactiveProperty, + p7 : ReadOnlyReactiveProperty, + fn : Callable +) -> ReadOnlyReactiveProperty: + return ReadOnlyReactiveProperty.new( + p1.combine_latest([p2 as Observable, p3 as Observable, p4 as Observable, p5 as Observable, p6 as Observable, p7 as Observable]).map(func(tup : Tuple): return fn.call(tup.at(0), tup.at(1), tup.at(2), tup.at(3), tup.at(4), tup.at(5), tup.at(6))), + fn.call(p1.Value, p2.Value, p3.Value, p4.Value, p5.Value, p6.Value, p7.Value) + ) + +static func Computed8( + p1 : ReadOnlyReactiveProperty, + p2 : ReadOnlyReactiveProperty, + p3 : ReadOnlyReactiveProperty, + p4 : ReadOnlyReactiveProperty, + p5 : ReadOnlyReactiveProperty, + p6 : ReadOnlyReactiveProperty, + p7 : ReadOnlyReactiveProperty, + p8 : ReadOnlyReactiveProperty, + fn : Callable +) -> ReadOnlyReactiveProperty: + return ReadOnlyReactiveProperty.new( + p1.combine_latest([p2 as Observable, p3 as Observable, p4 as Observable, p5 as Observable, p6 as Observable, p7 as Observable, p8 as Observable]).map(func(tup : Tuple): return fn.call(tup.at(0), tup.at(1), tup.at(2), tup.at(3), tup.at(4), tup.at(5), tup.at(6), tup.at(7))), + fn.call(p1.Value, p2.Value, p3.Value, p4.Value, p5.Value, p6.Value, p7.Value, p8.Value) + ) diff --git a/addons/reactivex/engine/observable/reactiveproperty/reactivesignal.gd b/addons/reactivex/engine/observable/reactiveproperty/reactivesignal.gd new file mode 100644 index 0000000..c14c229 --- /dev/null +++ b/addons/reactivex/engine/observable/reactiveproperty/reactivesignal.gd @@ -0,0 +1,147 @@ +extends ReactiveSignalBase +class_name ReactiveSignal + +const MAX_ARGS : int = 8 + +signal _signal0 +signal _signal1(arg0) +signal _signal2(arg0, arg1) +signal _signal3(arg0, arg1, arg2) +signal _signal4(arg0, arg1, arg2, arg3) +signal _signal5(arg0, arg1, arg2, arg3, arg4) +signal _signal6(arg0, arg1, arg2, arg3, arg4, arg5) +signal _signal7(arg0, arg1, arg2, arg3, arg4, arg5, arg6) +signal _signal8(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) + +var is_disposed : bool + +var _signal : Signal +var _n_args : int +var _observers : Array[ObserverBase] +var _connections : Array[Callable] + +func _init(n_args : int = 1): + if n_args > MAX_ARGS: + GDRx.raise(TooManyArgumentsError.new( + "Only up to 8 signal parameters supported! Use lists instead!")) + super._init(func(__, ___ = null): return Disposable.new()) + return + + self.is_disposed = false + + self._n_args = n_args + self._observers = [] + match self._n_args: + 0: self._signal = self._signal0 + 1: self._signal = self._signal1 + 2: self._signal = self._signal2 + 3: self._signal = self._signal3 + 4: self._signal = self._signal4 + 5: self._signal = self._signal5 + 6: self._signal = self._signal6 + 7: self._signal = self._signal7 + 8: self._signal = self._signal8 + _: assert(false) # "should not happen" + + var wself = weakref(self) + + var subscribe_ = func(observer : ObserverBase, scheduler_ : SchedulerBase = null) -> DisposableBase: + var rself : ReactiveSignal = wself.get_ref() + if rself == null: + return Disposable.new() + + var scheduler : GodotSignalScheduler = scheduler_ if scheduler_ is GodotSignalScheduler else GodotSignalScheduler.singleton() + + rself._observers.push_back(observer) + var on_dispose = func(): + var _rself : ReactiveSignal = wself.get_ref() + if _rself == null: + return + _rself._observers.erase(observer) + + var action : Callable + match n_args: + 0: + action = func(): + observer.on_next(Tuple.new([])) + 1: + action = func(arg1): + if arg1 is Array: + observer.on_next(Tuple.new(arg1)) + else: + observer.on_next(arg1) + 2: + action = func(arg1, arg2): + observer.on_next(Tuple.new([arg1, arg2])) + 3: + action = func(arg1, arg2, arg3): + observer.on_next(Tuple.new([arg1, arg2, arg3])) + 4: + action = func(arg1, arg2, arg3, arg4): + observer.on_next(Tuple.new([arg1, arg2, arg3, arg4])) + 5: + action = func(arg1, arg2, arg3, arg4, arg5): + observer.on_next(Tuple.new([arg1, arg2, arg3, arg4, arg5])) + 6: + action = func(arg1, arg2, arg3, arg4, arg5, arg6): + observer.on_next(Tuple.new([arg1, arg2, arg3, arg4, arg5, arg6])) + 7: + action = func(arg1, arg2, arg3, arg4, arg5, arg6, arg7): + observer.on_next(Tuple.new([arg1, arg2, arg3, arg4, arg5, arg6, arg7])) + 8: + action = func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8): + observer.on_next(Tuple.new([arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8])) + _: + GDRx.raise(TooManyArgumentsError.new( + "Only up to 8 signal parameters supported! Use lists instead!")) + return Disposable.new() + + var sub = scheduler.schedule_signal(rself._signal, rself._n_args, action) + var cd = CompositeDisposable.new([sub, Disposable.new(on_dispose)]) + return cd + + super._init(subscribe_) + +func _emit(args = []): + if self.is_disposed: + return + + var args_ = [] + GDRx.iter(args).foreach(func(arg): + args_.push_back(arg)) + + match self._n_args: + 0: self._signal.emit() + 1: self._signal.emit(args_[0]) + 2: self._signal.emit(args_[0], args_[1]) + 3: self._signal.emit(args_[0], args_[1], args_[2]) + 4: self._signal.emit(args_[0], args_[1], args_[2], args_[3]) + 5: self._signal.emit(args_[0], args_[1], args_[2], args_[3], args_[4]) + 6: self._signal.emit(args_[0], args_[1], args_[2], args_[3], args_[4], args_[5]) + 7: self._signal.emit(args_[0], args_[1], args_[2], args_[3], args_[4], args_[5], args_[6]) + 8: self._signal.emit(args_[0], args_[1], args_[2], args_[3], args_[4], args_[5], args_[6], args_[7]) + _: assert(false) # "should not happen" + +func attach(cb : Callable): + if self.is_disposed: + return + if not cb in self._connections: + self._signal.connect(cb) + self._connections.push_back(cb) + +func detach(cb : Callable): + if self.is_disposed: + return + if cb in self._connections: + self._signal.disconnect(cb) + self._connections.erase(cb) + +func dispose(): + if this.is_disposed: + return + this.is_disposed = true + + for observer in this._observers: + observer.on_completed() + for conn in this._connections: + self._signal.disconnect(conn) diff --git a/addons/reactivex/engine/observable/reactiveproperty/readonlyreactivecollection.gd b/addons/reactivex/engine/observable/reactiveproperty/readonlyreactivecollection.gd new file mode 100644 index 0000000..4552e19 --- /dev/null +++ b/addons/reactivex/engine/observable/reactiveproperty/readonlyreactivecollection.gd @@ -0,0 +1,67 @@ +extends ReadOnlyReactiveCollectionBase +class_name ReadOnlyReactiveCollection + +var _collection : ReactiveCollectionBase +var _observers : Array[ObserverBase] + +var is_disposed : bool + +class _Observable extends Observable: + func _init(source : Observable, observers : Array[ObserverBase]): + var subscribe_ = func(observer, scheduler = null): + if not observer in observers: + observers.push_back(observer) + return source.subscribe1(observer, scheduler) + super._init(subscribe_) + +func _init(collection : ReactiveCollectionBase): + super._init() + + self.is_disposed = false + + self._collection = collection + self._observe_add = self._Observable.new(collection.ObserveAdd, self._observers) + self._observe_move = self._Observable.new(collection.ObserveMove, self._observers) + self._observe_remove = self._Observable.new(collection.ObserveRemove, self._observers) + self._observe_replace = self._Observable.new(collection.ObserveReplace, self._observers) + self._observe_reset = self._Observable.new(collection.ObserveReset, self._observers) + +func ObserveCountChanged(_notify_current_count : bool = false) -> Observable: + return self._collection.ObserveCountChanged(_notify_current_count) + +func at(index : int): + if self.is_disposed: + DisposedError.raise() + return null + return self._collection.at(index) + +func find(item) -> int: + if self.is_disposed: + DisposedError.raise() + return -1 + return self._collection.find(item) + +func to_list() -> Array: + if self.is_disposed: + DisposedError.raise() + return [] + return self._collection.to_list() + +func iter() -> Iterator: + if self.is_disposed: + DisposedError.raise() + return null + return self._collection.iter() + +func size() -> int: + if self.is_disposed: + DisposedError.raise() + return -1 + return self._collection.size() + +func dispose(): + if this.is_disposed: + return + this.is_disposed = true + for observer in this._observers: + observer.on_completed() diff --git a/addons/reactivex/engine/observable/reactiveproperty/readonlyreactivedictionary.gd b/addons/reactivex/engine/observable/reactiveproperty/readonlyreactivedictionary.gd new file mode 100644 index 0000000..71bf5d6 --- /dev/null +++ b/addons/reactivex/engine/observable/reactiveproperty/readonlyreactivedictionary.gd @@ -0,0 +1,98 @@ +extends ReadOnlyReactiveDictionaryBase +class_name ReadOnlyReactiveDictionary + +var _dict : ReactiveDictionaryBase +var _observers : Array[ObserverBase] + +var is_disposed : bool + +class _Observable extends Observable: + func _init(source : Observable, observers : Array[ObserverBase]): + var subscribe_ = func(observer, scheduler = null): + if not observer in observers: + observers.push_back(observer) + return source.subscribe1(observer, scheduler) + super._init(subscribe_) + +func _init(dict : ReactiveDictionary): + super._init() + + self.is_disposed = false + + self._dict = dict + self._observe_add_key = self._Observable.new(dict.ObserveAddKey, self._observers) + self._observe_remove_key = self._Observable.new(dict.ObserveRemoveKey, self._observers) + self._observer_update_value = self._Observable.new(dict.ObserveUpdateValue, self._observers) + +func ObserveCountChanged(notify_current_count : bool = false) -> Observable: + return self._dict.ObserveCountChanged(notify_current_count) + +func to_dict() -> Dictionary: + if self.is_disposed: + DisposedError.raise() + return {} + return self._dict.to_dict() + +func find_key(value) -> Variant: + if self.is_disposed: + DisposedError.raise() + return null + return self._dict.find_key(value) + +func get_value(key, default = null) -> Variant: + if self.is_disposed: + DisposedError.raise() + return null + return self._dict.get_value(key, default) + +func has_key(key) -> bool: + if self.is_disposed: + DisposedError.raise() + return false + return self._dict.has_key(key) + +func has_all(keys : Array) -> bool: + if self.is_disposed: + DisposedError.raise() + return false + return self._dict.has_all(keys) + +func hash() -> int: + if self.is_disposed: + DisposedError.raise() + return 0 + return self._dict.hash() + +func is_empty() -> bool: + if self.is_disposed: + DisposedError.raise() + return false + return self._dict.is_empty() + +func keys() -> Array: + if self.is_disposed: + DisposedError.raise() + return [] + return self._dict.keys() + +func size() -> int: + if self.is_disposed: + DisposedError.raise() + return -1 + return self._dict.size() + +func values() -> Array: + if self.is_disposed: + DisposedError.raise() + return [] + return self._dict.value() + +func dispose(): + if this.is_disposed: + return + this.is_disposed = true + for observer in this._observers: + observer.on_completed() + +func iter() -> Iterator: + return GDRx.iter(self._dict) diff --git a/addons/reactivex/engine/observable/reactiveproperty/readonlyreactiveproperty.gd b/addons/reactivex/engine/observable/reactiveproperty/readonlyreactiveproperty.gd new file mode 100644 index 0000000..d7d3f82 --- /dev/null +++ b/addons/reactivex/engine/observable/reactiveproperty/readonlyreactiveproperty.gd @@ -0,0 +1,118 @@ +extends ReadOnlyReactivePropertyBase +class_name ReadOnlyReactiveProperty + +## An observable property with read-only access. +## +## Wraps a value and emits an item whenever it is changed. +## The emitted item is the new value of the [ReactiveProperty]. + +var _latest_value +var _source_subscription : DisposableBase +var _observers : Array[ObserverBase] +var _distinct_until_changed : bool +var _raise_latest_value_on_subscribe : bool + +var _rwlock : ReadWriteLock + +var is_disposed : bool + +func _init( + source : ObservableBase, + initial_value_, + distinct_until_changed_ : bool = true, + raise_latest_value_on_subscribe_ : bool = true +): + var wself : WeakRef = weakref(self) + + self._observers = [] + self._rwlock = ReadWriteLock.new() + self.is_disposed = false + + self._source_subscription = source.subscribe( + func(i): wself.get_ref()._on_next(i), + func(e): wself.get_ref()._on_error(e), + func(): wself.get_ref()._on_completed() + ) + self._latest_value = initial_value_ + self._distinct_until_changed = distinct_until_changed_ + self._raise_latest_value_on_subscribe = raise_latest_value_on_subscribe_ + + @warning_ignore("shadowed_variable") + var subscribe = func( + observer : ObserverBase, + _scheduler : SchedulerBase = null + ) -> DisposableBase: + var prop : ReadOnlyReactiveProperty = wself.get_ref() + + if not prop or prop.is_disposed: + observer.on_completed() + return Disposable.new() + + if true: + var __ = ReadWriteLockGuard.new(prop._rwlock, false) + prop._observers.append(observer) + + if prop._raise_latest_value_on_subscribe: + observer.on_next(prop._latest_value) + + var dispose_ = func(): + var _prop = wself.get_ref() + if not _prop: + return + if true: + var __ = ReadWriteLockGuard.new(_prop._rwlock, false) + _prop._observers.erase(observer) + + return Disposable.new(dispose_) + + super._init(subscribe) + +func _get_value(): + return self._latest_value + +func dispose(): + if this.is_disposed: + return + + this.is_disposed = true + + var observers_ : Array[ObserverBase] + if true: + var __ = ReadWriteLockGuard.new(this._rwlock, true) + observers_ = this._observers.duplicate() + for obs in observers_: + obs.on_completed() + + if true: + var __ = ReadWriteLockGuard.new(this._rwlock, false) + this._observers.clear() + + this._source_subscription.dispose() + +func _on_next(value): + if self._distinct_until_changed and self._latest_value == value: + return + + self._latest_value = value + var observers_ : Array[ObserverBase] + if true: + var __ = ReadWriteLockGuard.new(self._rwlock, true) + observers_ = self._observers.duplicate() + for obs in observers_: + obs.on_next(value) + +func _on_error(error_): + var observers_ : Array[ObserverBase] + if true: + var __ = ReadWriteLockGuard.new(self._rwlock, true) + observers_ = self._observers.duplicate() + for obs in observers_: + obs.on_error(error_) + +func _on_completed(): + self.dispose() + +func _to_string() -> String: + if self.is_disposed: + return "<>" + return str(self.Value) diff --git a/addons/reactivex/engine/observable/reactiveproperty/typedreactivecollection.gd b/addons/reactivex/engine/observable/reactiveproperty/typedreactivecollection.gd new file mode 100644 index 0000000..2577495 --- /dev/null +++ b/addons/reactivex/engine/observable/reactiveproperty/typedreactivecollection.gd @@ -0,0 +1,64 @@ +extends ReactiveCollection +class_name ReactiveCollectionT + +var _type +var _type_equality : Callable + +var _push_type_err : bool + +var T: + get: return self._type + +func _init(collection = [], type_ = TYPE_MAX, push_type_err : bool = true, type_equality : Callable = GDRx.basic.default_type_equality): + self._type = type_ + self._push_type_err = push_type_err + self._type_equality = type_equality + super._init(collection) + +func _type_check(value) -> bool: + return not self._type_equality.call(self._type, value) + +func _type_check_fail(value, default = null): + var exc = TypeMismatchError.new(value) + if self._push_type_err: push_error(exc) + exc.throw(value) + return default + +func add_item(item) -> int: + if self._type_check(item): + return self._type_check_fail(item, -1) + return super.add_item(item) + +func remove_item(item) -> int: + if self._type_check(item): + return self._type_check_fail(item, -1) + return super.remove_item(item) + +func replace_item(item, with) -> int: + if self._type_check(item): + return self._type_check_fail(item, -1) + if self._type_check(with): + return self._type_check_fail(with, -1) + return super.replace_item(item, with) + +func replace_at(index : int, item) -> Variant: + if self._type_check(item): + return self._type_check_fail(item) + return super.replace_at(index, item) + +func insert_at(index : int, item): + if self._type_check(item): + self._type_check_fail(item) + return + super.insert_at(index, item) + +func find(item) -> int: + if self._type_check(item): + return self._type_check_fail(item, -1) + return super.find(item) + +func to_readonly() -> ReadOnlyReactiveCollection: + return ReadOnlyReactiveCollectionT.new(self) + +func to_typed_readonly() -> ReadOnlyReactiveCollectionT: + return self.to_readonly() as ReadOnlyReactiveCollectionT diff --git a/addons/reactivex/engine/observable/reactiveproperty/typedreactiveproperty.gd b/addons/reactivex/engine/observable/reactiveproperty/typedreactiveproperty.gd new file mode 100644 index 0000000..d5736a5 --- /dev/null +++ b/addons/reactivex/engine/observable/reactiveproperty/typedreactiveproperty.gd @@ -0,0 +1,45 @@ +extends ReactiveProperty +class_name ReactivePropertyT + +var _type +var _type_equality : Callable + +var _push_type_err : bool + +var T: + get: return self._type + +func _init( + initial_value_, + type_ = TYPE_MAX, + distinct_until_changed_ : bool = true, + raise_latest_value_on_subscribe_ : bool = true, + source : Observable = null, + push_type_err : bool = true, + type_equality : Callable = GDRx.basic.default_type_equality +): + self._type = type_ + self._push_type_err = push_type_err + self._type_equality = type_equality + super._init(initial_value_, distinct_until_changed_, raise_latest_value_on_subscribe_, source) + +func _type_check_fail(value, default = null): + var exc = TypeMismatchError.new(value) + if self._push_type_err: push_error(exc) + exc.throw(value) + return default + +func _set_value(value): + if not self._type_equality.call(self._type, value): + self._type_check_fail(value) + return + super._set_value(value) + +func to_readonly() -> ReadOnlyReactiveProperty: + return ReadOnlyReactivePropertyT.new( + self, Value, self._distinct_until_changed, + self._raise_latest_value_on_subscribe + ) + +func to_typed_readonly() -> ReadOnlyReactivePropertyT: + return self.to_readonly() as ReadOnlyReactivePropertyT diff --git a/addons/reactivex/engine/observable/reactiveproperty/typedreactivesignal.gd b/addons/reactivex/engine/observable/reactiveproperty/typedreactivesignal.gd new file mode 100644 index 0000000..970e65e --- /dev/null +++ b/addons/reactivex/engine/observable/reactiveproperty/typedreactivesignal.gd @@ -0,0 +1,57 @@ +extends ReactiveSignal +class_name ReactiveSignalT + +var _type_list : Array +var _type_equality : Callable + +var _arg_names : Array[StringName] + +var _push_type_err : bool + +var TList : Array: + get: return self._type_list + +var ArgNames : Array[StringName]: + get: return self._arg_names + +func _init( + n_args : int, + type_list_ : Array = [], + arg_names_ : Array[StringName] = [], + push_type_err : bool = true, + type_equality : Callable = GDRx.basic.default_type_equality): + var type_list = type_list_.duplicate() + while type_list.size() < n_args: + type_list.push_back(TYPE_MAX) + self._type_list = type_list + self._type_list.make_read_only() + + var c = 0 + var arg_names : Array[StringName] = arg_names_.duplicate() + while arg_names.size() < n_args: + arg_names.push_back("arg" + str(c)) + c += 1 + self._arg_names = arg_names + self._arg_names.make_read_only() + + self._push_type_err = push_type_err + self._type_equality = type_equality + + super._init(n_args) + +func _type_check_fail(value, default = null): + var exc = TypeMismatchError.new(value) + if self._push_type_err: push_error(exc) + exc.throw(value) + return default + +func _emit(args = []): + var args_valid = RefValue.Set(true) + GDRx.iter(args).enumerate(func(value, i : int): + if not self._type_equality.call(self._type_list[i], value): + args_valid.v = false + self._type_check_fail(value) + return false) + + if args_valid.v: + super._emit(args) diff --git a/addons/reactivex/engine/observable/reactiveproperty/typedreadonlyreactivecollection.gd b/addons/reactivex/engine/observable/reactiveproperty/typedreadonlyreactivecollection.gd new file mode 100644 index 0000000..0747c16 --- /dev/null +++ b/addons/reactivex/engine/observable/reactiveproperty/typedreadonlyreactivecollection.gd @@ -0,0 +1,10 @@ +extends ReadOnlyReactiveCollection +class_name ReadOnlyReactiveCollectionT + +var _type +var T: + get: return self._type + +func _init(collection : ReactiveCollectionT): + self._type = collection.T + super._init(collection) diff --git a/addons/reactivex/engine/observable/reactiveproperty/typedreadonlyreactiveproperty.gd b/addons/reactivex/engine/observable/reactiveproperty/typedreadonlyreactiveproperty.gd new file mode 100644 index 0000000..2299768 --- /dev/null +++ b/addons/reactivex/engine/observable/reactiveproperty/typedreadonlyreactiveproperty.gd @@ -0,0 +1,15 @@ +extends ReadOnlyReactiveProperty +class_name ReadOnlyReactivePropertyT + +var _type +var T: + get: return self._type + +func _init( + source : ReactivePropertyT, + initial_value_, + distinct_until_changed_ : bool = true, + raise_latest_value_on_subscribe_ : bool = true +): + self._type = source.T + super._init(source, initial_value_, distinct_until_changed_, raise_latest_value_on_subscribe_) diff --git a/addons/reactivex/engine/operators/_processtimeinterval.gd b/addons/reactivex/engine/operators/_processtimeinterval.gd new file mode 100644 index 0000000..b3d191b --- /dev/null +++ b/addons/reactivex/engine/operators/_processtimeinterval.gd @@ -0,0 +1,69 @@ +## Returns the amount of process time passed between two items using idle time. +## The emitted item is a [Tuple] containing the real item as first and the +## time delta as second component. +static func process_time_interval_(initial_time : float = 0.0) -> Callable: + var process_time_interval = func(source : Observable) -> Observable: + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var _scheduler : SchedulerBase + _scheduler = scheduler if scheduler != null else CurrentThreadScheduler.singleton() + + var dt = RefValue.Set(initial_time) + + var on_process = func(delta : float): + dt.v += delta + var process_sub : DisposableBase = GDRx.on_idle_frame().subscribe(on_process) + + var on_next = func(value): + var span : float = dt.v + dt.v = 0.0 + observer.on_next(Tuple.new([value, span])) + + var sub : DisposableBase = source.subscribe( + on_next, observer.on_error, observer.on_completed, + _scheduler + ) + + return CompositeDisposable.new([process_sub, sub]) + + return Observable.new(subscribe) + + return process_time_interval + +## Returns the amount of time passed between two items using physics steps. +## The emitted item is a [Tuple] containing the real item as first and the +## time delta as second component. +static func physics_time_interval_(initial_time : float = 0.0) -> Callable: + var physics_time_interval = func(source : Observable) -> Observable: + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var _scheduler : SchedulerBase + _scheduler = scheduler if scheduler != null else CurrentThreadScheduler.singleton() + + var dt = RefValue.Set(initial_time) + + var on_process = func(delta : float): + dt.v += delta + var process_sub : DisposableBase = GDRx.on_physics_step().subscribe(on_process) + + var on_next = func(value): + var span : float = dt.v + dt.v = 0.0 + observer.on_next(Tuple.new([value, span])) + + var sub : DisposableBase = source.subscribe( + on_next, observer.on_error, observer.on_completed, + _scheduler + ) + + return CompositeDisposable.new([process_sub, sub]) + + return Observable.new(subscribe) + + return physics_time_interval diff --git a/addons/reactivex/engine/scheduler/godotsignalscheduler.gd b/addons/reactivex/engine/scheduler/godotsignalscheduler.gd new file mode 100644 index 0000000..a105f2c --- /dev/null +++ b/addons/reactivex/engine/scheduler/godotsignalscheduler.gd @@ -0,0 +1,213 @@ +extends GodotSignalSchedulerBase +class_name GodotSignalScheduler + +const UTC_ZERO : float = Scheduler.UTC_ZERO +const DELTA_ZERO : float = Scheduler.DELTA_ZERO + +func _init(verify_ = null): + if not verify_ == "GDRx": + push_warning("Warning! Must only instance Scheduler from GDRx singleton!") + +static func singleton() -> GodotSignalScheduler: + return GDRx.GodotSignalScheduler_ + +## Represents a notion of time for this scheduler. Tasks being +## scheduled on a scheduler will adhere to the time denoted by this +## property. +## [br] +## [b]Returns:[/b] +## [br] +## The scheduler's current time, as a datetime instance. +func now() -> float: + return GDRx.basic.default_now() + +## Invoke the given given action. This is typically called by instances +## of [ScheduledItem]. +## [br] +## [b]Args:[/b] +## [br] +## [code]action[/code] Action to be executed. +## [br] +## [code]state[/code] [Optional] state to be given to the action function. +## [br][br] +## [b]Returns:[/b] +## [br] +## The disposable object returned by the action, if any; or a new +## (no-op) disposable otherwise. +func invoke_action(action : Callable, state = null) -> DisposableBase: + var ret = action.call(self, state) + if ret is DisposableBase: + return ret + return Disposable.new() + +## Schedules an action to be executed when the [Signal] is emitted. +## [br] +## [b]Args:[/b] +## [br] +## [code]sig[/code] A Godot [Signal] instance to schedule. +## [br] +## [code]n_args[/code] Number of signal arguments. This is the same number +## of parameters for [code]action[/code]. +## [br] +## [code]action[/code] Scheduled callback action for the signal. This is a +## function with [code]n_args[/code] parameters. +## [br] +## [code]state[/code] [Optional] state to be given to the action function. +## [br][br] +## +## [b]Returns:[/b] +## [br] +## The disposable object used to cancel the scheduled action. +func schedule_signal( + sig : Signal, + n_args : int, + action : Callable, + state = null +) -> DisposableBase: + + var disp : Disposable = Disposable.new() + + var signal_callback : Callable + match n_args: + 0: + signal_callback = func(): + var action_ = func(_scheduler : SchedulerBase, _state = null): + action.call() + disp = self.invoke_action(action_, state) + 1: + signal_callback = func(arg1): + var action_ = func(_scheduler : SchedulerBase, _state = null): + action.call(arg1) + disp = self.invoke_action(action_, state) + 2: + signal_callback = func(arg1, arg2): + var action_ = func(_scheduler : SchedulerBase, _state = null): + action.call(arg1, arg2) + disp = self.invoke_action(action_, state) + 3: + signal_callback = func(arg1, arg2, arg3): + var action_ = func(_scheduler : SchedulerBase, _state = null): + action.call(arg1, arg2, arg3) + disp = self.invoke_action(action_, state) + 4: + signal_callback = func(arg1, arg2, arg3, arg4): + var action_ = func(_scheduler : SchedulerBase, _state = null): + action.call(arg1, arg2, arg3, arg4) + disp = self.invoke_action(action_, state) + 5: + signal_callback = func(arg1, arg2, arg3, arg4, arg5): + var action_ = func(_scheduler : SchedulerBase, _state = null): + action.call(arg1, arg2, arg3, arg4, arg5) + disp = self.invoke_action(action_, state) + 6: + signal_callback = func(arg1, arg2, arg3, arg4, arg5, arg6): + var action_ = func(_scheduler : SchedulerBase, _state = null): + action.call(arg1, arg2, arg3, arg4, arg5, arg6) + disp = self.invoke_action(action_, state) + 7: + signal_callback = func(arg1, arg2, arg3, arg4, arg5, arg6, arg7): + var action_ = func(_scheduler : SchedulerBase, _state = null): + action.call(arg1, arg2, arg3, arg4, arg5, arg6, arg7) + disp = self.invoke_action(action_, state) + 8: + signal_callback = func(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8): + var action_ = func(_scheduler : SchedulerBase, _state = null): + action.call(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) + disp = self.invoke_action(action_, state) + _: + GDRx.raise(TooManyArgumentsError.new( + "Only up to 8 signal parameters supported! Use lists instead!")) + return null + + var obj = instance_from_id(sig.get_object_id()) + + var dispose = func(): + if obj != null: + sig.disconnect(signal_callback) + + sig.connect(signal_callback) + + var cd : CompositeDisposable = CompositeDisposable.new([disp, Disposable.new(dispose)]) + return cd + +## Schedules an action to be executed. +## [br] +## [b]Args:[/b] +## [br] +## [code]action[/code] Action to be executed. +## [br] +## [code]state[/code] [Optional] state to be given to the action function. +## [br][br] +## [b]Returns:[/b] +## [br] +## The disposable object used to cancel the scheduled action +## (best effort). +func schedule(action : Callable, state = null) -> DisposableBase: + var sad : SingleAssignmentDisposable = SingleAssignmentDisposable.new() + + var interval = func(): + sad.disposable = self.invoke_action(action, state) + + var timer : SceneTreeTimer = GDRx.get_tree().create_timer(0.0) + timer.connect("timeout", func(): interval.call() ; _cancel_timer(timer)) + + var dispose = func(): + _cancel_timer(timer) + + return CompositeDisposable.new([sad, Disposable.new(dispose)]) + +## Schedules an action to be executed after duetime. +## [br] +## [b]Args:[/b] +## [br] +## [code]duetime[/code] Relative time after which to execute the action. +## [br] +## [code]action[/code] Action to be executed. +## [br] +## [code]state[/code] [Optional] state to be given to the action function. +## [br][br] +## +## [b]Returns:[/b] +## [br] +## The disposable object used to cancel the scheduled action +## (best effort). +func schedule_relative(duetime, action : Callable, state = null) -> DisposableBase: + var seconds : float = duetime + if seconds <= 0.0: + return self.schedule(action, state) + + var sad : SingleAssignmentDisposable = SingleAssignmentDisposable.new() + + var interval = func(): + sad.disposable = self.invoke_action(action, state) + + var timer = GDRx.get_tree().create_timer(seconds) + timer.connect("timeout", func(): interval.call() ; _cancel_timer(timer)) + + var dispose = func(): + _cancel_timer(timer) + + return CompositeDisposable.new([sad, Disposable.new(dispose)]) + +## Schedules an action to be executed at duetime. +## [br] +## [b]Args:[/b] +## [br] +## [code]duetime[/code] Absolute time at which to execute the action. +## [br] +## [code]action[/code] Action to be executed. +## [br] +## [code]state[/code] [Optional] state to be given to the action function. +## [br][br] +## +## [b]Returns:[/b] +## [br] +## The disposable object used to cancel the scheduled action +## (best effort). +func schedule_absolute(duetime, action : Callable, state = null) -> DisposableBase: + return self.schedule_relative(duetime - self.now(), action, state) + +## Utility function to cancel a timer +func _cancel_timer(timer : SceneTreeTimer): + for conn in timer.timeout.get_connections(): + timer.timeout.disconnect(conn["callable"]) diff --git a/addons/reactivex/error/error.gd b/addons/reactivex/error/error.gd new file mode 100644 index 0000000..f81a8fa --- /dev/null +++ b/addons/reactivex/error/error.gd @@ -0,0 +1,31 @@ +extends ThrowableBase +class_name RxBaseError + +const BASE_ERROR_TAG = "Error" + +var _msg : String +var _tags : ArraySet + +func _init(msg : String, tag = "Error"): + self._msg = msg + self._tags = ArraySet.new() + self._tags.add(BASE_ERROR_TAG) + self._tags.add(tag) + +func _to_string() -> String: + return "[" + self.tags().back() + "::" + self._msg + "]" + +func get_message() -> String: + return self._msg + +func throw(default = null) -> Variant: + return ErrorHandler.singleton().raise(self, default) + +func tags() -> Array[String]: + var txs : Array[String] = [] + for t in self._tags.to_list(): + txs.append(t as String) + return txs + +static func raise(default = null, msg : String = "An error occured"): + return GDRx.raise(RxBaseError.new(msg), default) diff --git a/addons/reactivex/error/errorhandler.gd b/addons/reactivex/error/errorhandler.gd new file mode 100644 index 0000000..5d85811 --- /dev/null +++ b/addons/reactivex/error/errorhandler.gd @@ -0,0 +1,49 @@ +class_name ErrorHandler + +## Handles raised Errors +## +## Objects of type [ThrowableBase] are handled by this type's singleton + +var _try_catch_stack : Array[TryCatch] +var _has_failed : Dictionary + +func _init(): + self._try_catch_stack = [] + self._has_failed = {} + +static func singleton() -> ErrorHandler: + var thread = GDRx.get_current_thread() + if not GDRx.ErrorHandler_.has_key(thread): + GDRx.ErrorHandler_.set_pair(thread, ErrorHandler.new()) + return GDRx.ErrorHandler_.get_value(thread) + +func run(stmt : TryCatch) -> bool: + self._has_failed[stmt] = false + + self._try_catch_stack.push_back(stmt) + stmt.risky_code.call() + self._try_catch_stack.pop_back() + + var res = self._has_failed[stmt] + self._has_failed.erase(stmt) + return res + +func raise(exc : ThrowableBase, default = null) -> Variant: + var handler : Callable = GDRx.basic.default_crash + + if self._try_catch_stack == null or self._try_catch_stack.is_empty(): + handler.call(exc) + return default + + handler = GDRx.basic.noop + + var stmt : TryCatch = self._try_catch_stack.pop_back() + self._has_failed[stmt] = true + for type in exc.tags(): + if type in stmt.caught_types: + handler = stmt.caught_types[type] + break + + handler.call(exc) + self._try_catch_stack.push_back(stmt) + return default diff --git a/addons/reactivex/error/trycatch.gd b/addons/reactivex/error/trycatch.gd new file mode 100644 index 0000000..3953da3 --- /dev/null +++ b/addons/reactivex/error/trycatch.gd @@ -0,0 +1,29 @@ +class_name TryCatch + +var _risky_code : Callable +var _caught_types : Dictionary + +var caught_types : Dictionary: + get: return self._caught_types.duplicate() +var risky_code : Callable: + get: return self._risky_code + +func _init(fun : Callable = GDRx.basic.noop): + self._risky_code = fun + self._caught_types = {} + +func end_try_catch() -> bool: + return ErrorHandler.singleton().run(self) + +func catch(type : String, fun : Callable = GDRx.basic.noop) -> TryCatch: + if self._caught_types.has(type): + return + self._caught_types[type] = fun + return self + +func catch_all(types : Array[String], fun : Callable = GDRx.basic.noop) -> TryCatch: + for type in types: + if self._caught_types.has(type): + continue + self._caught_types[type] = fun + return self diff --git a/addons/reactivex/error/types/argumentoutofrangeerror.gd b/addons/reactivex/error/types/argumentoutofrangeerror.gd new file mode 100644 index 0000000..18dd93a --- /dev/null +++ b/addons/reactivex/error/types/argumentoutofrangeerror.gd @@ -0,0 +1,11 @@ +extends RxBaseError +class_name ArgumentOutOfRangeError + +const ERROR_TYPE = "ArgumentOutOfRangeError" +const ERROR_MESSAGE = "Argument out of range" + +func _init(msg : String = ERROR_MESSAGE): + super._init(msg, ERROR_TYPE) + +static func raise(default = null, msg : String = ERROR_MESSAGE): + return GDRx.raise(ArgumentOutOfRangeError.new(msg), default) diff --git a/addons/reactivex/error/types/assertionfailederror.gd b/addons/reactivex/error/types/assertionfailederror.gd new file mode 100644 index 0000000..7abc53f --- /dev/null +++ b/addons/reactivex/error/types/assertionfailederror.gd @@ -0,0 +1,11 @@ +extends RxBaseError +class_name AssertionFailedError + +const ERROR_TYPE = "AssertionFailedError" +const ERROR_MESSAGE = "Assertion failed" + +func _init(msg : String = ERROR_MESSAGE): + super._init(msg, ERROR_TYPE) + +static func raise(default = null, msg : String = ERROR_MESSAGE): + return GDRx.raise(AssertionFailedError.new(msg), default) diff --git a/addons/reactivex/error/types/badargumenterror.gd b/addons/reactivex/error/types/badargumenterror.gd new file mode 100644 index 0000000..ede2e2a --- /dev/null +++ b/addons/reactivex/error/types/badargumenterror.gd @@ -0,0 +1,11 @@ +extends RxBaseError +class_name BadArgumentError + +const ERROR_TYPE = "BadArgumentError" +const ERROR_MESSAGE = "An argument contained bad input" + +func _init(msg : String = ERROR_MESSAGE): + super._init(msg, ERROR_TYPE) + +static func raise(default = null, msg : String = ERROR_MESSAGE): + return GDRx.raise(BadArgumentError.new(msg), default) diff --git a/addons/reactivex/error/types/badmappingerror.gd b/addons/reactivex/error/types/badmappingerror.gd new file mode 100644 index 0000000..125bb34 --- /dev/null +++ b/addons/reactivex/error/types/badmappingerror.gd @@ -0,0 +1,11 @@ +extends RxBaseError +class_name BadMappingError + +const ERROR_TYPE = "BadMappingError" +const ERROR_MESSAGE = "A mapping did not succeed" + +func _init(msg : String = ERROR_MESSAGE): + super._init(msg, ERROR_TYPE) + +static func raise(default = null, msg : String = ERROR_MESSAGE): + return GDRx.raise(BadMappingError.new(msg), default) diff --git a/addons/reactivex/error/types/badpredicateerror.gd b/addons/reactivex/error/types/badpredicateerror.gd new file mode 100644 index 0000000..991151c --- /dev/null +++ b/addons/reactivex/error/types/badpredicateerror.gd @@ -0,0 +1,11 @@ +extends RxBaseError +class_name BadPredicateError + +const ERROR_TYPE = "BadPredicateError" +const ERROR_MESSAGE = "A predicate failed" + +func _init(msg : String = ERROR_MESSAGE): + super._init(msg, ERROR_TYPE) + +static func raise(default = null, msg : String = ERROR_MESSAGE): + return GDRx.raise(BadPredicateError.new(msg), default) diff --git a/addons/reactivex/error/types/disposederror.gd b/addons/reactivex/error/types/disposederror.gd new file mode 100644 index 0000000..d24992d --- /dev/null +++ b/addons/reactivex/error/types/disposederror.gd @@ -0,0 +1,11 @@ +extends RxBaseError +class_name DisposedError + +const ERROR_TYPE = "DisposedError" +const ERROR_MESSAGE = "The requested element was disposed before" + +func _init(msg : String = ERROR_MESSAGE): + super._init(msg, ERROR_TYPE) + +static func raise(default = null, msg : String = ERROR_MESSAGE): + return GDRx.raise(DisposedError.new(msg), default) diff --git a/addons/reactivex/error/types/dividedbyzeroerror.gd b/addons/reactivex/error/types/dividedbyzeroerror.gd new file mode 100644 index 0000000..89a04de --- /dev/null +++ b/addons/reactivex/error/types/dividedbyzeroerror.gd @@ -0,0 +1,11 @@ +extends RxBaseError +class_name DividedByZeroError + +const ERROR_TYPE = "DividedByZeroError" +const ERROR_MESSAGE = "Divided by zero" + +func _init(msg : String = ERROR_MESSAGE): + super._init(msg, ERROR_TYPE) + +static func raise(default = null, msg : String = ERROR_MESSAGE): + return GDRx.raise(DividedByZeroError.new(msg), default) diff --git a/addons/reactivex/error/types/factoryfailederror.gd b/addons/reactivex/error/types/factoryfailederror.gd new file mode 100644 index 0000000..90de776 --- /dev/null +++ b/addons/reactivex/error/types/factoryfailederror.gd @@ -0,0 +1,18 @@ +extends RxBaseError +class_name FactoryFailedError + +const ERROR_TYPE = "FactoryFailedError" +const ERROR_MESSAGE = "A factory failed" + +var _produced : Variant + +func _init(produced_ : Variant, msg_ : String = ERROR_MESSAGE): + self._produced = produced_ + var msg : String = msg_ + ":!" + str(self._produced) + super._init(msg, ERROR_TYPE) + +func get_produced_item() -> Variant: + return self._produced + +static func raise(default = null, msg : String = ERROR_MESSAGE, produced = null): + return GDRx.raise(FactoryFailedError.new(produced, msg), default) diff --git a/addons/reactivex/error/types/httprequestfailederror.gd b/addons/reactivex/error/types/httprequestfailederror.gd new file mode 100644 index 0000000..31bbd8a --- /dev/null +++ b/addons/reactivex/error/types/httprequestfailederror.gd @@ -0,0 +1,19 @@ +extends RxBaseError +class_name HttpRequestFailedError + +const ERROR_TYPE = "HttpRequestFailedError" + +var url : String +var error_code : int +var error_message : String + +func _init(url_ : String, error_code_ : int, error_message_ : String): + self.url = url_ + self.error_code = error_code_ + self.error_message = error_message_ + var msg = "HTTP Request to host '" + self.url + "' failed with code " + str(self.error_code) \ + + ("" if self.error_message.is_empty() else " (" + self.error_message + ")") + super._init(msg, ERROR_TYPE) + +static func raise(default = null, error_message : String = "", url : String = "", error_code : int = -1): + return GDRx.raise(HttpRequestFailedError.new(url, error_code, error_message), default) diff --git a/addons/reactivex/error/types/locknotaquirederror.gd b/addons/reactivex/error/types/locknotaquirederror.gd new file mode 100644 index 0000000..e8b8244 --- /dev/null +++ b/addons/reactivex/error/types/locknotaquirederror.gd @@ -0,0 +1,11 @@ +extends RxBaseError +class_name LockNotAquiredError + +const ERROR_TYPE = "LockNotAquiredError" +const ERROR_MESSAGE = "Lock has not been aquired" + +func _init(msg : String = ERROR_MESSAGE): + super._init(msg, ERROR_TYPE) + +static func raise(default = null, msg : String = ERROR_MESSAGE): + return GDRx.raise(LockNotAquiredError.new(msg), default) diff --git a/addons/reactivex/error/types/notimplementederror.gd b/addons/reactivex/error/types/notimplementederror.gd new file mode 100644 index 0000000..06abdaa --- /dev/null +++ b/addons/reactivex/error/types/notimplementederror.gd @@ -0,0 +1,11 @@ +extends RxBaseError +class_name NotImplementedError + +const ERROR_TYPE = "NotImplementedError" +const ERROR_MESSAGE = "There is no implementation" + +func _init(msg : String = ERROR_MESSAGE): + super._init(msg, ERROR_TYPE) + +static func raise(default = null, msg : String = ERROR_MESSAGE): + return GDRx.raise(NotImplementedError.new(msg), default) diff --git a/addons/reactivex/error/types/nullreferenceerror.gd b/addons/reactivex/error/types/nullreferenceerror.gd new file mode 100644 index 0000000..f6b0bcd --- /dev/null +++ b/addons/reactivex/error/types/nullreferenceerror.gd @@ -0,0 +1,11 @@ +extends RxBaseError +class_name NullReferenceError + +const ERROR_TYPE = "NullReferenceError" +const ERROR_MESSAGE = "Instance not set to a value" + +func _init(msg : String = ERROR_MESSAGE): + super._init(msg, ERROR_TYPE) + +static func raise(default = null, msg : String = ERROR_MESSAGE): + return GDRx.raise(NullReferenceError.new(msg), default) diff --git a/addons/reactivex/error/types/sequencecontainsnoelementserror.gd b/addons/reactivex/error/types/sequencecontainsnoelementserror.gd new file mode 100644 index 0000000..684c95e --- /dev/null +++ b/addons/reactivex/error/types/sequencecontainsnoelementserror.gd @@ -0,0 +1,11 @@ +extends RxBaseError +class_name SequenceContainsNoElementsError + +const ERROR_TYPE = "SequenceContainsNoElementsError" +const ERROR_MESSAGE = "The given sequence is empty" + +func _init(msg : String = ERROR_MESSAGE): + super._init(msg, ERROR_TYPE) + +static func raise(default = null, msg : String = ERROR_MESSAGE): + return GDRx.raise(SequenceContainsNoElementsError.new(msg), default) diff --git a/addons/reactivex/error/types/toomanyargumentserror.gd b/addons/reactivex/error/types/toomanyargumentserror.gd new file mode 100644 index 0000000..d2ec376 --- /dev/null +++ b/addons/reactivex/error/types/toomanyargumentserror.gd @@ -0,0 +1,11 @@ +extends RxBaseError +class_name TooManyArgumentsError + +const ERROR_TYPE = "TooManyArgumentsError" +const ERROR_MESSAGE = "Too many arguments for given function" + +func _init(msg : String = ERROR_MESSAGE): + super._init(msg, ERROR_TYPE) + +static func raise(default = null, msg : String = ERROR_MESSAGE): + return GDRx.raise(TooManyArgumentsError.new(msg), default) diff --git a/addons/reactivex/error/types/typemismatcherror.gd b/addons/reactivex/error/types/typemismatcherror.gd new file mode 100644 index 0000000..166e0b3 --- /dev/null +++ b/addons/reactivex/error/types/typemismatcherror.gd @@ -0,0 +1,18 @@ +extends RxBaseError +class_name TypeMismatchError + +const ERROR_TYPE = "TypeMismatchError" +const ERROR_MESSAGE = "Type mismatch" + +var _item + +func _init(item_, msg_ : String = ERROR_MESSAGE): + self._item = item_ + var msg = msg_ + ":!" + str(item_) + super._init(msg, ERROR_TYPE) + +func get_item(): + return self._item + +static func raise(default = null, msg : String = ERROR_MESSAGE, item = null): + return GDRx.raise(TypeMismatchError.new(item, msg), default) diff --git a/addons/reactivex/error/types/wouldblockerror.gd b/addons/reactivex/error/types/wouldblockerror.gd new file mode 100644 index 0000000..dc0955d --- /dev/null +++ b/addons/reactivex/error/types/wouldblockerror.gd @@ -0,0 +1,11 @@ +extends RxBaseError +class_name WouldBlockError + +const ERROR_TYPE = "WouldBlockError" +const ERROR_MESSAGE = "Would block thread" + +func _init(msg : String = ERROR_MESSAGE): + super._init(msg, ERROR_TYPE) + +static func raise(default = null, msg : String = ERROR_MESSAGE): + return GDRx.raise(WouldBlockError.new(msg), default) diff --git a/addons/reactivex/internal/basic.gd b/addons/reactivex/internal/basic.gd new file mode 100644 index 0000000..5f783da --- /dev/null +++ b/addons/reactivex/internal/basic.gd @@ -0,0 +1,40 @@ +func noop(__ = null, ___ = null): + pass + +func identity(x, __ = null): + return x + +func default_now() -> float: + return Time.get_unix_time_from_datetime_dict( + Time.get_datetime_dict_from_system(true)) + +func default_comparer(x, y) -> bool: + return x.eq(y) if (x is Object and x.has_method("eq")) else x == y + +func default_sub_comparer(x, y): + return x - y + +func default_key_serializer(x) -> String: + return str(x) + +func default_error(err): + if err is ThrowableBase: + err.throw() + GDRx.raise(RxBaseError.new(err)) + +func default_crash(e): + #OS.alert("Unhandled error: " + str(e)) + push_error("Unhandled error: " + str(e)) + print_stack() + GDRx.get_tree().quit(1) + +func default_condition(__ = null, ___ = null) -> bool: + return true + +func default_factory(__ : SchedulerBase): # -> Observable: + return GDRx.obs.empty() + +func default_type_equality(type, value) -> bool: + if type is Dictionary: + return typeof(value) == TYPE_INT and type.find_key(value) != null + return is_instance_of(value, type) diff --git a/addons/reactivex/internal/concurrency.gd b/addons/reactivex/internal/concurrency.gd new file mode 100644 index 0000000..dcc29dd --- /dev/null +++ b/addons/reactivex/internal/concurrency.gd @@ -0,0 +1,167 @@ +class ThreadManager extends Object: + + var _thread_registry_lock : ReadWriteLock + var _thread_registry_dict : Dictionary + var _thread_registry : Tuple + + var _mutex : RLock = RLock.new() + var _cond : ConditionalVariable = ConditionalVariable.new() + var _cleanup_thread : Thread = Thread.new() + var _shutdown : bool = false + var _finished_threads : Array[StartableBase] + + var THREAD_REGISTRY : Tuple: + get: return self._thread_registry + + func _init(): + self._thread_registry_lock = ReadWriteLock.new() + self._thread_registry_dict = {} + self._thread_registry = Tuple.new([ + self._thread_registry_lock, + self._thread_registry_dict + ]) + + self._cleanup_thread.start(self._cleanup) + + func stop_and_join(): + self._mutex.lock() + self._shutdown = true + self._mutex.unlock() + + self._cond.notify_all() + self._cleanup_thread.wait_to_finish() + + if not self._finished_threads.is_empty(): + assert(false and "should not happen!".length()) + + ## This method adds the thread object to the cleanup thread + ## allowing it to be joined and disposed properly. + ## + ## This is mainly done due to Godot's [method Thread.wait_to_finish] shinanigans. + func finish(thread : StartableBase): + var __ = LockGuard.new(self._mutex) + self._finished_threads.push_back(thread) + self._cond.notify_all() + + func _cleanup(): + while true: + var thread : StartableBase = null + if true: + var __ = LockGuard.new(self._mutex) + self._cond.wait_pred(self._mutex, func(): + return self._shutdown or !self._finished_threads.is_empty()) + + if (self._shutdown): + break + + thread = self._finished_threads.back() + self._finished_threads.pop_back() + + thread.wait_to_finish() + + +## Links the [Thread] instance to a [Callable] before starting it. +class StartableThread extends StartableBase: + var _thread : Thread + var _target : Callable + var _priority : int + var _started : bool + var _joined : AtomicFlag + + var thread : Thread: + get: return self._thread + + func _init(target : Callable, priority = Thread.PRIORITY_NORMAL): + self._thread = Thread.new() + self._target = target + self._priority = priority + self._started = false + self._joined = AtomicFlag.new() + + func _register_thread(): + var id = OS.get_thread_caller_id() + var l : ReadWriteLock = GDRx.THREAD_MANAGER.THREAD_REGISTRY.at(0) + l.w_lock() + GDRx.THREAD_MANAGER.THREAD_REGISTRY.at(1)[id] = self._thread + l.w_unlock() + + func _deregister_thread(): + var id = OS.get_thread_caller_id() + var l : ReadWriteLock = GDRx.THREAD_MANAGER.THREAD_REGISTRY.at(0) + l.w_lock() + GDRx.THREAD_MANAGER.THREAD_REGISTRY.at(1).erase(id) + l.w_unlock() + + func start(): + if self._started: + GDRx.raise_message("Thread already started!") + return + + var run = func(): + Thread.set_thread_safety_checks_enabled(false) + self._register_thread() + self._target.call() + self._deregister_thread() + + self._started = true + self._thread.start(run, self._priority) + + func wait_to_finish(): + if self._is_caller(): + GDRx.THREAD_MANAGER.finish(self) + return + if self._joined.test_and_set(): + return + self._thread.wait_to_finish() + self._thread = null + self._target = GDRx.basic.noop + + func _is_caller() -> bool: + return str(OS.get_thread_caller_id()) == self._thread.get_id() + +func default_thread_factory(target : Callable) -> StartableThread: + return StartableThread.new(target) + +## Dummy class to represent the main [Thread] instance +class MainThreadDummy_ extends Thread: + @warning_ignore("native_method_override") + func start(_callable : Callable, _priority : Priority = PRIORITY_NORMAL) -> Error: + GDRx.raise_message("Do not launch the Main Thread Dummy!") + return FAILED + @warning_ignore("native_method_override") + func wait_to_finish() -> Variant: + GDRx.raise_message("Do not join the Main Thread Dummy!") + return null + func _to_string(): + return "MAIN_THREAD::" + str(GDRx.MAIN_THREAD_ID) + @warning_ignore("native_method_override") + func get_id() -> String: + return str(GDRx.MAIN_THREAD_ID) + @warning_ignore("native_method_override") + func is_started() -> bool: + return true + @warning_ignore("native_method_override") + func is_alive() -> bool: + return true + +## A naive Atomic Flag +class AtomicFlag: + var _flag : bool + var _mutex : Mutex + + func _init(): + self._flag = false + self._mutex = Mutex.new() + + func test_and_set() -> bool: + var res : bool + self._mutex.lock() + res = self._flag + self._flag = true + self._mutex.unlock() + return res + + func clear(): + self._mutex.lock() + self._flag = false + self._mutex.unlock() diff --git a/addons/reactivex/internal/conditionalvariable.gd b/addons/reactivex/internal/conditionalvariable.gd new file mode 100644 index 0000000..cee1eeb --- /dev/null +++ b/addons/reactivex/internal/conditionalvariable.gd @@ -0,0 +1,72 @@ +class_name ConditionalVariable + +## Naive implementation of a conditional variable for multi-threading +var _mutex : Mutex +var _waiting_queue : Array[Lock] + +func _init(): + self._mutex = Mutex.new() + self._waiting_queue = [] + +func wait(lock : LockBase): + var thread_lock : Lock = Lock.new() + thread_lock.lock() + + self._mutex.lock() + self._waiting_queue.push_back(thread_lock) + self._mutex.unlock() + + lock._unlock_and_store_recursion_depth() + thread_lock.lock() + lock._lock_and_restore_recursion_depth() + + thread_lock.unlock() + +func wait_pred(lock : LockBase, stop_waiting : Callable): + while !stop_waiting.call(): + self.wait(lock) + +func wait_for(lock : LockBase, time_sec : float) -> bool: + var thread_lock : Lock = Lock.new() + thread_lock.lock() + + self._mutex.lock() + self._waiting_queue.push_back(thread_lock) + self._mutex.unlock() + + var notified = RefValue.Set(true) + var on_timeout = func(): + notified.v = false + thread_lock.unlock() + self._mutex.lock() + self._waiting_queue.erase(thread_lock) + self._mutex.unlock() + + var timer = GDRx.get_tree().create_timer(time_sec) + timer.connect("timeout", on_timeout) + + lock._unlock_and_store_recursion_depth() + thread_lock.lock() + lock._lock_and_restore_recursion_depth() + + if notified.v: + timer.disconnect("timeout", on_timeout) + + thread_lock.unlock() + return notified.v + +func notify(n = 1): + self._mutex.lock() + for __ in range(n): + if self._waiting_queue.is_empty(): + break + var next : Lock = self._waiting_queue.pop_front() + next.unlock() + self._mutex.unlock() + +func notify_all(): + self._mutex.lock() + while not self._waiting_queue.is_empty(): + var next : Lock = self._waiting_queue.pop_front() + next.unlock() + self._mutex.unlock() diff --git a/addons/reactivex/internal/heap.gd b/addons/reactivex/internal/heap.gd new file mode 100644 index 0000000..b0aa186 --- /dev/null +++ b/addons/reactivex/internal/heap.gd @@ -0,0 +1,61 @@ +## Heap queue algorithm (a.k.a. priority queue). +## See [url]https://github.com/python/cpython/blob/3.10/Lib/heapq.py[/url] + +func heappush(heap : Array, item): + heap.append(item) + _siftdown(heap, 0, len(heap) - 1) + +func heappop(heap : Array): + var lastelt = heap.pop_back() + if heap != null and not heap.is_empty(): + var returnitem = heap[0] + heap[0] = lastelt + _siftup(heap, 0) + return returnitem + return lastelt + +func heapreplace(heap : Array, item): + var returnitem = heap[0] + heap[0] = item + _siftup(heap, 0) + return returnitem + +func heappushpop(heap : Array, item): + if heap != null and not heap.is_empty() and GDRx.lt(heap[0], item): + var item_tmp = heap[0] + heap[0] = item + item = item_tmp + _siftup(heap, 0) + return item + +func heapify(x : Array): + var n = len(x) + var rang_ = range(int(n / 2.0)) ; rang_.reverse() + for i in rang_: + _siftup(x, i) + +func _siftdown(heap : Array, startpos, pos): + var newitem = heap[pos] + while pos > startpos: + var parentpos = (pos - 1) >> 1 + var parent = heap[parentpos] + if GDRx.lt(newitem, parent): + pos = parentpos + continue + break + heap[pos] = newitem + +func _siftup(heap : Array, pos): + var endpos = len(heap) + var startpos = pos + var newitem = heap[pos] + var childpos = 2 * pos + 1 + while childpos < endpos: + var rightpos = childpos + 1 + if rightpos < endpos and not GDRx.lt(heap[childpos], heap[rightpos]): + childpos = rightpos + heap[pos] = heap[childpos] + pos = childpos + childpos = 2 * pos + 1 + heap[pos] = newitem + _siftdown(heap, startpos, pos) diff --git a/addons/reactivex/internal/infinite.gd b/addons/reactivex/internal/infinite.gd new file mode 100644 index 0000000..9f403db --- /dev/null +++ b/addons/reactivex/internal/infinite.gd @@ -0,0 +1,43 @@ +extends IterableBase +class_name InfiniteIterable + +## Represents a never ending iterable sequence + +var _infval + +func _init(infval = GDRx.util.NOT_SET): + self._infval = infval + +class _Iterator extends Iterator: + var _itcounter : int + var _itinfval + + func _init(infval): + self._itinfval = infval + self._itcounter = 0 + + func has_next() -> bool: + return true + + func next() -> Variant: + self._itcounter += 1 + if not is_instance_of(self._itinfval, GDRx.util.NotSet): + return self._itinfval + return self._itcounter + + func empty() -> bool: + return false + + func front() -> Variant: + return self.next() + + func back() -> Variant: + return GDRx.raise_message("This pain persists, I can't resist... but that's what it takes to be INFINITE!") + + func at(n : int) -> Variant: + if not is_instance_of(self._itinfval, GDRx.util.NotSet): + return self._itinfval + return n + 1 + +func iter() -> Iterator: + return self._Iterator.new(self._infval) diff --git a/addons/reactivex/internal/iterator.gd b/addons/reactivex/internal/iterator.gd new file mode 100644 index 0000000..2de6d39 --- /dev/null +++ b/addons/reactivex/internal/iterator.gd @@ -0,0 +1,147 @@ +extends IteratorBase +class_name Iterator + +## A naive iterator with additional quality-of-life utility +## +## See [IteratorBase] and [IterableBase] for more info! + +## Loops over all elements within the iterable sequence +func foreach(what : Callable = func(__elem): pass): + var next_ = self.next() + while not next_ is ItEnd: + var continue_ = what.call(next_) + if continue_ == false: + break + next_ = self.next() + +## Loops and enumerates an iterable sequence +func enumerate(what : Callable = func(__elem, __idx : int): pass): + var idx_ = 0 + var next_ = self.next() + while not next_ is ItEnd: + var continue_ = what.call(next_, idx_) + if continue_ == false: + break + idx_ += 1 + next_ = self.next() + + +## An iterable working on an [Array] or a relative like [PackedByteArray] +class ArrayIterable extends IterableBase: + var _x + + var _start : int + var _end : int + + func _init(x, start : int = 0, end : int = -1): + self._x = x + self._start = start + self._end = x.size() if end < 0 else end + + if self._start < 0 or self._start > self._end or self._end > x.size(): + ArgumentOutOfRangeError.raise() + return + + class _Iterator extends Iterator: + var _itx + var _itstart : int + var _itend : int + + var _itindex : int + + func _init(x, start : int, end : int): + self._itx = x + self._itstart = start + self._itend = end + self._itindex = start + + func has_next() -> bool: + return not (self._itx.is_empty() or self._itindex == self._itend) + + func next() -> Variant: + if not has_next(): + return ItEnd.new() + self._itindex += 1 + return self._itx[self._itindex - 1] + + func front() -> Variant: + return ItEnd.new() if self._itx.is_empty() else self._itx[self._itstart] + + func back() -> Variant: + return ItEnd.new() if self._itx.is_empty() else self._itx[self._itend - 1] + + func empty() -> bool: + return self._itx.is_empty() + + func at(n : int) -> Variant: + var it = _Iterator.new(self._itx, self._itstart, self._itend) + var tmp = ItEnd.new() + for i in range(n): + tmp = it.next() + return tmp + + func iter() -> Iterator: + return self._Iterator.new(self._x, self._start, self._end) + + +## An iterable working on a [Dictionary] +class DictionaryIterable extends IterableBase: + var _dict : Dictionary + + func _init(dict : Dictionary): + self._dict = dict + + class _Iterator extends Iterator: + var _itdict : Dictionary + + var _itindex : int + var _itend : int + var _itkeys : Array + + func _init(dict : Dictionary): + self._itdict = dict + self._itindex = 0 + self._itend = dict.size() + self._itkeys = dict.keys() + + func has_next() -> bool: + return not (self._itdict.is_empty() or self._itindex == self._itend) + + func next() -> Variant: + if not has_next(): + return ItEnd.new() + self._itindex += 1 + return Tuple.new([ + self._itkeys[self._itindex - 1], + self._itdict[self._itkeys[self._itindex - 1]] + ]) + + func empty() -> bool: + return self._itdict.is_empty() + + func iter() -> Iterator: + return self._Iterator.new(self._dict) + +## Anonymous [IterableBase] +class _Iterable extends IterableBase: + var seq + func _init(seq_): + self.seq = seq_ + func iter() -> Iterator: + return seq.iter() + +## Constructs an iterable sequence of type [IterableBase] based on x. +static func to_iterable(x : Variant) -> IterableBase: + if x is Array or x is PackedByteArray or x is PackedColorArray or x is PackedFloat32Array or x is PackedFloat64Array or x is PackedInt32Array or x is PackedInt64Array or x is PackedStringArray or x is PackedVector2Array or x is PackedVector3Array: + return ArrayIterable.new(x) + if x is Dictionary: + return DictionaryIterable.new(x) + if x is IterableBase: + return x + if x.has_method("iter"): + return _Iterable.new(x) + return to_iterable([x]) + +## Returns an [Iterator] onto the given sequence. +static func iter(x) -> Iterator: + return to_iterable(x).iter() diff --git a/addons/reactivex/internal/lock.gd b/addons/reactivex/internal/lock.gd new file mode 100644 index 0000000..4034556 --- /dev/null +++ b/addons/reactivex/internal/lock.gd @@ -0,0 +1,37 @@ +extends LockBase +class_name Lock + +## A lock which can only be aquired and released once by any thread. + +var _aquired_thread +var _semaphore : Semaphore + +func _init(): + self._aquired_thread = null + self._semaphore = Semaphore.new() + self._semaphore.post() + +func lock(): + self._semaphore.wait() + self._aquired_thread = OS.get_thread_caller_id() + +func unlock(): + if self._aquired_thread == null: + LockNotAquiredError.new( + "Lock was released but nobody aquired it!").throw() + return + self._aquired_thread = null + self._semaphore.post() + +func try_lock() -> bool: + return self._semaphore.try_wait() + +func is_locking_thread() -> bool: + var id = OS.get_thread_caller_id() + return self._aquired_thread == id + +func _unlock_and_store_recursion_depth(): + self.unlock() + +func _lock_and_restore_recursion_depth(): + self.lock() diff --git a/addons/reactivex/internal/lockguard.gd b/addons/reactivex/internal/lockguard.gd new file mode 100644 index 0000000..3da69b4 --- /dev/null +++ b/addons/reactivex/internal/lockguard.gd @@ -0,0 +1,29 @@ +extends RefCounted +class_name LockGuard + +## A basic lock guard based on [b]RAII[/b] principle. +## +## I had been unable to pull this off. Because it frustrates me to replace all +## [method LockBase.lock] and [method LockBase.unlock] calls with this, I will +## explain why: When I started to develop [b]GodotRx[/b] I ran into a problem: +## The NOTIFICATION_PREDELETE is received, when the [b]self[/b] reference +## already results in a [b]null[/b]-value. I will now use the approach from +## [b]std::enable_shared_from_this[/b] using an uncounted reference as member. +## This appears to work as intended. +## +## Also, I learned a lot of C++ practices when creating NativeGodotRx ;) + +var this : LockGuard + +var _lock : LockBase + +func _init(lock : LockBase): + this = self + this.unreference() + + self._lock = lock + self._lock.lock() + +func _notification(what): + if what == NOTIFICATION_PREDELETE: + this._lock.unlock() diff --git a/addons/reactivex/internal/priorityqueue.gd b/addons/reactivex/internal/priorityqueue.gd new file mode 100644 index 0000000..020fa82 --- /dev/null +++ b/addons/reactivex/internal/priorityqueue.gd @@ -0,0 +1,49 @@ +class_name PriorityQueue + +## Implementation of a priority queue using a heap +## +## Priority queue using a heap data structure, see: [b]res://addons/reactivex/internal/heap.gd[/b] + +var _Heap = GDRx.heap + +const MIN_COUNT = 9223372036854775807 + +var _items : Array +var _count : int + +func _init(): + self._items = [] + self._count = MIN_COUNT + +func size(): + return _items.size() + +func is_empty() -> bool: + return self.size() == 0 + +func peek(): + return self._items[0].at(0) + +func dequeue() -> Variant: + var item = _Heap.heappop(self._items).at(0) + if self._items == null or self._items.is_empty(): + self._count = MIN_COUNT + return item + +func enqueue(item): + _Heap.heappush(self._items, Tuple.new([item, self._count])) + self._count += 1 + +func remove(item) -> bool: + for index in range(self._items.size()): + var _item = self._items[index] + if self._item.at(0) == item: + self._items.pop_at(index) + _Heap.heapify(self._items) + return true + + return false + +func clear(): + self._items = [] + self._count = MIN_COUNT diff --git a/addons/reactivex/internal/refvalue.gd b/addons/reactivex/internal/refvalue.gd new file mode 100644 index 0000000..db1c289 --- /dev/null +++ b/addons/reactivex/internal/refvalue.gd @@ -0,0 +1,28 @@ +## Wraps a value +## +## RefValue is used to account for the Python [code]nonlocal[/code]-keyword. +## A for a lambda-function global variable with basic type +## cannot be changed for its parent scopes, so it is wrapped +## and referenced via [code]refvalue.v[/code] + +class_name RefValue + +## Wrapped value +var v : Variant + +func _init(v_ : Variant = null): + self.v = v_ + +## Create a new RefValue with initial value +static func Set(v_ : Variant = null) -> RefValue: + return RefValue.new(v_) + +## Create a new RefValue with initial value [code]null[/code] +static func Null() -> RefValue: + return RefValue.new() + +func eq(other) -> bool: + return GDRx.eq(v, other.v) if other is RefValue else GDRx.eq(v, other) + +func _to_string(): + return str(v) diff --git a/addons/reactivex/internal/rlock.gd b/addons/reactivex/internal/rlock.gd new file mode 100644 index 0000000..0f1525d --- /dev/null +++ b/addons/reactivex/internal/rlock.gd @@ -0,0 +1,58 @@ +extends LockBase +class_name RLock + +## Re-entrant lock +## +## Lock can be aquired multiple times by the same thread + +var _aquired_thread +var _counter : int +var _mutex : Mutex + +func _init(): + self._aquired_thread = null + self._counter = 0 + self._mutex = Mutex.new() + + self._saved_recursion_depth = -1 + +func lock(): + self._mutex.lock() + self._aquired_thread = OS.get_thread_caller_id() + self._counter += 1 + +func unlock(): + if self._counter <= 0: + LockNotAquiredError.new( + "RLock was released but nobody aquired it!").throw() + return + if self._aquired_thread != OS.get_thread_caller_id(): + LockNotAquiredError.new( + "RLock was released by a thread which has not aquired it!").throw() + return + self._counter -= 1 + if self._counter == 0: + self._aquired_thread = null + self._mutex.unlock() + +func try_lock() -> bool: + return self._mutex.try_lock() + +func is_locking_thread() -> bool: + var id = OS.get_thread_caller_id() + return self._aquired_thread == id + +## Internal interface to free the lock even though it was aquired more than once +var _saved_recursion_depth : int + +## Unlocks the RLock and stores recursion depth +func _unlock_and_store_recursion_depth(): + self._saved_recursion_depth = self._counter + for __ in range(self._saved_recursion_depth): + self.unlock() + +## Locks the RLock and restores old recursion depth +func _lock_and_restore_recursion_depth(): + for __ in range(self._saved_recursion_depth): + self.lock() + self._saved_recursion_depth = -1 diff --git a/addons/reactivex/internal/rwlock.gd b/addons/reactivex/internal/rwlock.gd new file mode 100644 index 0000000..8f1ffdb --- /dev/null +++ b/addons/reactivex/internal/rwlock.gd @@ -0,0 +1,34 @@ +class_name ReadWriteLock + +var _w_lock : Lock +var _num_r_lock : Lock +var _num_r : int + +func _init(): + self._w_lock = Lock.new() + self._num_r_lock = Lock.new() + self._num_r = 0 + +func r_lock(): + self._num_r_lock.lock() + self._num_r += 1 + if self._num_r == 1: + self._w_lock.lock() + self._num_r_lock.unlock() + +func r_unlock(): + if self._num_r <= 0: + LockNotAquiredError.new( + "Read-Write-Lock was released but nobody aquired it!").throw() + return + self._num_r_lock.lock() + self._num_r -= 1 + if self._num_r == 0: + self._w_lock.unlock() + self._num_r_lock.unlock() + +func w_lock(): + self._w_lock.lock() + +func w_unlock(): + self._w_lock.unlock() diff --git a/addons/reactivex/internal/rwlockguard.gd b/addons/reactivex/internal/rwlockguard.gd new file mode 100644 index 0000000..181459c --- /dev/null +++ b/addons/reactivex/internal/rwlockguard.gd @@ -0,0 +1,27 @@ +extends RefCounted +class_name ReadWriteLockGuard + +## A shared lock guard based on [b]RAII[/b] principle. Uses [ReadWriteLock]. + +var this : ReadWriteLockGuard + +var _lock : ReadWriteLock +var _shared : bool + +func _init(lock : ReadWriteLock, shared : bool): + this = self + this.unreference() + + self._lock = lock + self._shared = shared + + if self._shared: + self._lock.r_lock() + else: + self._lock.w_lock() + +func _notification(what): + if what == NOTIFICATION_PREDELETE: + if this._shared: this._lock.r_unlock() + else: this._lock.w_unlock() + diff --git a/addons/reactivex/internal/set.gd b/addons/reactivex/internal/set.gd new file mode 100644 index 0000000..ecf7c3a --- /dev/null +++ b/addons/reactivex/internal/set.gd @@ -0,0 +1,28 @@ +extends IterableBase +class_name ArraySet + +## A Set data structure based on a list + +var _list : Array + +func _init(): + self._list = [] + +func to_list() -> Array: + return _list.duplicate() + +func add(item): + if not item in self._list: + self._list.append(item) + +func erase(item): + self._list.erase(item) + +func size() -> int: + return self._list.size() + +func at(index : int): + return self._list[index] + +func iter() -> Iterator: + return GDRx.iter(self._list) diff --git a/addons/reactivex/internal/threadingevent.gd b/addons/reactivex/internal/threadingevent.gd new file mode 100644 index 0000000..ad7eaa0 --- /dev/null +++ b/addons/reactivex/internal/threadingevent.gd @@ -0,0 +1,60 @@ +class_name ThreadingEvent + +## Class implementing event objects. +## +## Events manage a flag that can be set to [b]true[/b] with the [method set_flag] method and reset +## to [b]false[/b] with the [method clear] method. The [method wait] method blocks until the flag is +## [b]true[/b]. The flag is initially [b]false[/b]. + +var _cond : ConditionalVariable +var _lock : Lock +var _flag : bool + +func _init(): + self._cond = ConditionalVariable.new() + self._lock = Lock.new() + self._flag = false + +## Return [b]true[/b] if and only if the internal flag is [b]true[/b]. +func is_set() -> bool: + return self._flag + +## Set the internal flag to [b]true[/b]. +## +## All threads waiting for it to become [b]true[/b] are awakened. Threads +## that call [method wait] once the flag is [b]true[/b] will not block at all. +func set_flag(): + self._lock.lock() + self._flag = true + self._cond.notify_all() + self._lock.unlock() + +## Reset the internal flag to [b]false[/b]. +## +## Subsequently, threads calling [method wait] will block until [method set_flag] is called to +## set the internal flag to [b]true[/b] again. +func clear(): + self._lock.lock() + self._flag = false + self._lock.unlock() + +## Block until the internal flag is [b]true[/b]. +## +## If the internal flag is [b]true[/b] on entry, return immediately. Otherwise, +## block until another thread calls [method set_flag] to set the flag to [b]true[/b], or until +## the optional timeout occurs. +## +## When the [code]timeout[/code] argument is present and not [b]null[/b], it should be a +## floating point number specifying a timeout for the operation in seconds +## (or fractions thereof). +## +## This method returns the internal flag on exit, so it will always return +## [b]true[/b] except if a timeout is given and the operation times out. +## +func wait(timeout = null): + var __ = LockGuard.new(self._lock) + if not self._flag and timeout: + self._cond.wait_for(self._lock, timeout) + elif not self._flag: + self.wait(self._lock) + return self._flag diff --git a/addons/reactivex/internal/tuple.gd b/addons/reactivex/internal/tuple.gd new file mode 100644 index 0000000..fe9f545 --- /dev/null +++ b/addons/reactivex/internal/tuple.gd @@ -0,0 +1,86 @@ +extends Comparable +class_name Tuple + +## A Tuple based on a list + +var _x : Array + +var empty : bool: + get: return self.is_empty() +var first: + get: return _x[0] if _x.size() > 0 else GDRx.raise_message("Out of bounds") +var second: + get: return _x[1] if _x.size() > 1 else GDRx.raise_message("Out of bounds") +var third: + get: return _x[2] if _x.size() > 2 else GDRx.raise_message("Out of bounds") +var fourth: + get: return _x[3] if _x.size() > 3 else GDRx.raise_message("Out of bounds") +var fifth: + get: return _x[4] if _x.size() > 4 else GDRx.raise_message("Out of bounds") +var sixth: + get: return _x[5] if _x.size() > 5 else GDRx.raise_message("Out of bounds") +var seventh: + get: return _x[6] if _x.size() > 6 else GDRx.raise_message("Out of bounds") +var eighth: + get: return _x[7] if _x.size() > 7 else GDRx.raise_message("Out of bounds") + +static func Empty() -> Tuple: + return Tuple.new([]) + +func _init(x : Array): + self._x = x.duplicate() + self._x.make_read_only() + +func _to_string(): + var s = "( " + for elem in self._x: + s += str(elem) + " " + s += ")" + return s + +func at(i : int): + return _x[i] + +func as_list() -> Array: + return _x.duplicate() + +func iter() -> Iterator: + return Iterator.iter(self._x) + +func is_empty() -> bool: + return self._x.is_empty() + +func gt(other) -> bool: + if not (other is Tuple): + return false + return (self._x.is_empty() and other._x.is_empty()) \ + or GDRx.gt(self._x[0], other._x[0]) + +func eq(other) -> bool: + if not (other is Tuple): + return false + return (self._x.is_empty() and other._x.is_empty()) \ + or GDRx.eq(self._x[0], other._x[0]) + +func lt(other) -> bool: + if not (other is Tuple): + return false + return (self._x.is_empty() and other._x.is_empty()) \ + or GDRx.lt(self._x[0], other._x[0]) + +var __it__ : Iterator +var __curr__ : Variant + +func _iter_init(arg): + self.__it__ = iter() + var continue_ = self.__it__.has_next() + self.__curr__ = self.__it__.next() + return continue_ + +func _iter_next(arg): + var continue_ = self.__it__.has_next() + self.__curr__ = self.__it__.next() + return continue_ + +func _iter_get(arg): + return self.__curr__ diff --git a/addons/reactivex/internal/utils.gd b/addons/reactivex/internal/utils.gd new file mode 100644 index 0000000..a5f7ce6 --- /dev/null +++ b/addons/reactivex/internal/utils.gd @@ -0,0 +1,43 @@ +const MAX_SIZE = 9223372036854775807 + +## Represents an un-defined state +class NotSet extends Comparable: + func eq(other) -> bool: + return other is NotSet + +var NOT_SET : NotSet = NotSet.new() + +## Alias for [method to_iterable]. +func Iter(x) -> IterableBase: + return self.to_iterable(x) + +## Construct an [IterableBase] onto x. +func to_iterable(x) -> IterableBase: + return Iterator.to_iterable(x) + +## Construct an [Iterator] onto x. +func iter(x) -> Iterator: + return Iterator.iter(x) + +## Unpacks a [Variant] argument to a list of [Observable]s. +func unpack_arg(sources) -> Array[Observable]: + if sources is Array[Observable]: + return sources + + var res : Array[Observable] = [] + var it : Iterator = iter(sources) + while it.has_next(): + res.push_back(it.next()) + return res + +func add_ref(xs : Observable, r : RefCountDisposable) -> Observable: + var subscribe = func( + observer : ObserverBase, + _scheduler : SchedulerBase = null + ) -> DisposableBase: + return CompositeDisposable.new([ + r.disposable, + xs.subscribe(observer) + ]) + + return Observable.new(subscribe) diff --git a/addons/reactivex/internal/weakkeydictionary.gd b/addons/reactivex/internal/weakkeydictionary.gd new file mode 100644 index 0000000..07ffa42 --- /dev/null +++ b/addons/reactivex/internal/weakkeydictionary.gd @@ -0,0 +1,124 @@ +class_name WeakKeyDictionary + +## A dictionary with weak references to keys +## +## Pairs with non-referenced keys are automatically removed when deleted. +## Each [b]n[/b] insertions, a garbage collection step is performed. + +var _data : Dictionary +var _lock : ReadWriteLock + +var n : int +var _count : int + +func _init(from : Dictionary = {}, n_insertions : int = 100): + self._data = {} + self._lock = ReadWriteLock.new() + for key in from.keys(): + set_pair(key, from[key]) + + self.n = n_insertions + self._count = 0 + +func _hash_key(key) -> int: + return hash(key) + +func _collect_garbage(): + var dead_keys : Array = [] + for hkey in self._data.keys(): + if self._data[hkey].at(0).get_ref() == null: + dead_keys.push_back(hkey) + for hkey in dead_keys: + self._data.erase(hkey) + +func set_pair(key, value): + if GDRx.assert_(key != null, "Key is NULL!"): + return + if GDRx.assert_(key is Object, "Key needs to be of type 'Object'"): + return + var hkey = self._hash_key(key) + + var __ = ReadWriteLockGuard.new(self._lock, false) + self._data[hkey] = Tuple.new([weakref(key), value]) + self._count += 1 + if self._count >= n: + self._count = 0 + self._collect_garbage() + +func get_value(key, default = null) -> Variant: + if GDRx.assert_(key != null, "Key is NULL!"): + return + if GDRx.assert_(key is Object, "Key needs to be of type 'Object'"): + return + var hkey = self._hash_key(key) + + var __ = ReadWriteLockGuard.new(self._lock, true) + if self._data.has(hkey): + return self._data[hkey].at(1) + return default + +func find_key(value) -> Variant: + var __ = ReadWriteLockGuard.new(self._lock, true) + for tup in self._data.values(): + if GDRx.eq(tup.at(1), value): + return tup.at(0).get_ref() + return null + +func to_hash() -> int: + var __ = ReadWriteLockGuard.new(self._lock, true) + return self._data.hash() + +func is_empty() -> bool: + var __ = ReadWriteLockGuard.new(self._lock, true) + return self._data.is_empty() + +func clear(): + var __ = ReadWriteLockGuard.new(self._lock, false) + self._data.clear() + self._count = 0 + +func erase(key) -> bool: + if GDRx.assert_(key != null, "Key is NULL!"): + return false + if GDRx.assert_(key is Object, "Key needs to be of type 'Object'"): + return false + var hkey = self._hash_key(key) + + var __ = ReadWriteLockGuard.new(self._lock, false) + return self._data.erase(hkey) + +func values() -> Array: + var res : Array = [] + var __ = ReadWriteLockGuard.new(self._lock, true) + for tup in self._data.values(): + res.push_back(tup.at(1)) + return res + +func size() -> int: + var __ = ReadWriteLockGuard.new(self._lock, true) + return self._data.size() + +func keys() -> Array: + var res : Array = [] + var __ = ReadWriteLockGuard.new(self._lock, true) + for tup in self._data.values(): + var key = tup.at(0).get_ref() + if key: + res.push_back(key) + return res + +func has_key(key) -> bool: + if GDRx.assert_(key != null, "Key is NULL!"): + return false + if GDRx.assert_(key is Object, "Key needs to be of type 'Object'"): + return false + var hkey = self._hash_key(key) + + var __ = ReadWriteLockGuard.new(self._lock, true) + return self._data.has(hkey) + +func has_all(keys_ : Array) -> bool: + var _keys = keys_.duplicate() + _keys.all(func(elem): return self._hash_key(elem)) + var __ = ReadWriteLockGuard.new(self._lock, true) + return self._data.has_all(_keys) diff --git a/addons/reactivex/internal/while.gd b/addons/reactivex/internal/while.gd new file mode 100644 index 0000000..a2ed5d5 --- /dev/null +++ b/addons/reactivex/internal/while.gd @@ -0,0 +1,29 @@ +extends IterableBase +class_name WhileIterable + +## Represents an infinite or finite sequence which is terminated when a +## condition is not met anymore. + +var _it : IterableBase +var _cond : Callable + +func _init(it : IterableBase, cond : Callable = GDRx.basic.default_condition): + self._it = it + self._cond = cond + +class _Iterator extends Iterator: + var _itit : Iterator + var _itcond : Callable + + func _init(it : Iterator, cond : Callable): + self._itit = it + self._itcond = cond + + func has_next() -> bool: + return self._itit.has_next() and self._itcond.call() + + func next() -> Variant: + return ItEnd.new() if not self._itcond.call() else self._itit.next() + +func iter() -> Iterator: + return self._Iterator.new(self._it.iter(), self._cond) diff --git a/addons/reactivex/item/streamitem.gd b/addons/reactivex/item/streamitem.gd new file mode 100644 index 0000000..1b1ae8b --- /dev/null +++ b/addons/reactivex/item/streamitem.gd @@ -0,0 +1,42 @@ +extends Comparable +class_name StreamItem + +## GDRx-own item type + +var _payload + +## Represents empty [StreamItem] +class _Unit extends StreamItem: + func _init(): + self._payload = self + self._payload.unreference() + + func is_unit() -> bool: + return true + + func _to_string(): + return "__" + +func _init(payload = null): + self._payload = payload + +func get_payload(): + return self._payload + +func is_unit() -> bool: + return false + +func eq(other) -> bool: + if not (other is StreamItem): + return GDRx.eq(self._payload, other) + return (self.is_unit() and other.is_unit()) \ + or GDRx.eq(self._payload, other._payload) + +static func Unit() -> _Unit: + return _Unit.new() + +static func GetUnitType() -> GDScript: + return StreamItem._Unit + +func _to_string(): + return str(self._payload) diff --git a/addons/reactivex/notification/notification.gd b/addons/reactivex/notification/notification.gd new file mode 100644 index 0000000..5755a98 --- /dev/null +++ b/addons/reactivex/notification/notification.gd @@ -0,0 +1,111 @@ +extends Comparable +class_name Notification + +## Represents a notification to an observer. + +var has_value : bool +var value +var kind : String + +## Default constructor used by derived types. +func _init(): + self.has_value = false + self.value = null + self.kind = "" + +## Invokes the delegate corresponding to the notification or an +## observer and returns the produced result. +## [br] +## [b]Examples:[/b] +## [codeblock] +## notification.accept(observer) +## notification.accept(on_next, on_error, on_completed) +## [/codeblock] +## [br] +## [b]Args:[/b] +## [br] +## [code]on_next[/code] Delegate to invoke for an OnNext notification. +## [br] +## [code]on_error[/code] [Optional] Delegate to invoke for an OnError +## notification. +## [br] +## [code]on_completed[/code] [Optional] Delegate to invoke for an +## OnCompleted notification. +## [br][br] +## [b]Returns:[/b] +## [br] +## Result produced by the observation. +func accept( + on_next, # Callable or ObserverBase + on_error : Callable = GDRx.basic.noop, + on_completed : Callable = GDRx.basic.noop): + if on_next is ObserverBase: + return self._accept_observer(on_next) + + return self._accept(on_next, on_error, on_completed) + +func _accept( + _on_next : Callable, + _on_error : Callable = GDRx.basic.noop, + _on_completed : Callable = GDRx.basic.noop): + NotImplementedError.raise() + +func _accept_observer(_observer : ObserverBase): + NotImplementedError.raise() + +## Returns an observable sequence with a single notification, +## using the specified scheduler, else the immediate scheduler. +## [br] +## [b]Args:[/b] +## [br] +## [code]scheduler[/code] [Optional] Scheduler to send out the +## notification calls on. +## [br][br] +## [b]Returns:[/b] +## [br] +## An observable sequence that surfaces the behavior of the +## notification upon subscription. +func to_observable(scheduler : SchedulerBase = null) -> ObservableBase: + var _scheduler = scheduler if scheduler != null else ImmediateScheduler.singleton() + + var subscribe = func(observer : ObserverBase, scheduler : SchedulerBase = null) -> DisposableBase: + var action = func(_scheduler : SchedulerBase, _state): + self._accept_observer(observer) + if self.kind == "N": + observer.on_completed() + + var __scheduler = scheduler if scheduler != null else _scheduler + return __scheduler.schedule(action) + + return Observable.new(subscribe) + +## Indicates whether this instance and a specified object are equal. +func equals(other : Notification) -> bool: + var other_string = "" if other == null else str(other) + return str(self) == other_string + +## Inherited from [Comparable] +func eq(other) -> bool: + if not (other is Notification): + return false + return self.equals(other) + +## Creates an observer from a notification callback. +## [br] +## [b]Args:[/b] +## [br] +## [code]handler[/code] Action that handles a notification. +## [br][br] +## [b]Returns:[/b] +## [br] +## The observer object that invokes the specified handler using +## a notification corresponding to each message it receives. +static func from_notifier(handler : Callable) -> Observer: + var _on_next = func(value_): + return handler.call(GDRx.OnNext.new(value_)) + var _on_error = func(err_): + return handler.call(GDRx.OnError.new(err_)) + var _on_completed = func(): + return handler.call(GDRx.OnCompleted.new()) + + return Observer.new(_on_next, _on_error, _on_completed) diff --git a/addons/reactivex/notification/oncompleted.gd b/addons/reactivex/notification/oncompleted.gd new file mode 100644 index 0000000..8931c15 --- /dev/null +++ b/addons/reactivex/notification/oncompleted.gd @@ -0,0 +1,20 @@ +extends Notification +class_name OnCompletedNotification + +## Represents an OnCompleted notification to an observer. + +func _init(): + super._init() + self.kind = "C" + +func _accept( + _on_next : Callable, + _on_error : Callable = GDRx.basic.noop, + on_completed : Callable = GDRx.basic.noop): + return on_completed.call() + +func _accept_observer(observer : ObserverBase): + return observer.on_completed() + +func _to_string(): + return "OnCompleted()" diff --git a/addons/reactivex/notification/onerror.gd b/addons/reactivex/notification/onerror.gd new file mode 100644 index 0000000..603d301 --- /dev/null +++ b/addons/reactivex/notification/onerror.gd @@ -0,0 +1,23 @@ +extends Notification +class_name OnErrorNotification + +## Represents an OnError notification to an observer. + +var err + +func _init(err_): + super._init() + self.err = err_ + self.kind = "E" + +func _accept( + _on_next : Callable, + on_error : Callable = GDRx.basic.noop, + _on_completed : Callable = GDRx.basic.noop): + return on_error.call(self.err) + +func _accept_observer(observer : ObserverBase): + return observer.on_error(self.err) + +func _to_string(): + return "OnError(" + str(self.err) + ")" diff --git a/addons/reactivex/notification/onnext.gd b/addons/reactivex/notification/onnext.gd new file mode 100644 index 0000000..e941f29 --- /dev/null +++ b/addons/reactivex/notification/onnext.gd @@ -0,0 +1,22 @@ +extends Notification +class_name OnNextNotification + +## Represents an OnNext notification to an observer. + +func _init(value_): + super._init() + self.value = value_ + self.has_value = true + self.kind = "N" + +func _accept( + on_next : Callable, + _on_error : Callable = GDRx.basic.noop, + _on_completed : Callable = GDRx.basic.noop): + return on_next.call(self.value) + +func _accept_observer(observer : ObserverBase): + return observer.on_next(self.value) + +func _to_string(): + return "OnNext(" + str(self.value) + ")" diff --git a/addons/reactivex/observable/amb.gd b/addons/reactivex/observable/amb.gd new file mode 100644 index 0000000..53ec5ce --- /dev/null +++ b/addons/reactivex/observable/amb.gd @@ -0,0 +1,23 @@ +## Propagates the observable sequence that reacts first. +## [br] +## [b]Example:[/b] +## [codeblock] +## var winner = GDRx.obs.amb([xs, ys, zs]) +## [/codeblock] +## [br] +## [b]Returns:[/b] +## [br] +## An observable sequence that surfaces any of the given sequences, +## whichever reacted first. +static func amb_(sources_) -> Observable: + var sources : Array[Observable] = GDRx.util.unpack_arg(sources_) + + var acc : Observable = GDRx.obs.never() + + var fun = func(previous : Observable, current : Observable) -> Observable: + return GDRx.op.amb(previous).call(current) + + for source in sources: + acc = fun.call(acc, source) + + return acc diff --git a/addons/reactivex/observable/case.gd b/addons/reactivex/observable/case.gd new file mode 100644 index 0000000..3c24274 --- /dev/null +++ b/addons/reactivex/observable/case.gd @@ -0,0 +1,22 @@ +static func case_( + mapper : Callable, + sources : Dictionary, + default_source : Observable = null +) -> Observable: + + var default_source_ : Observable = default_source if default_source != null else GDRx.obs.empty() + + var factory = func(__ : SchedulerBase) -> Observable: + var key_ = mapper.call() + var result + if not sources.has(key_): + result = default_source_ + else: + result = sources[key_] + + if not result is Observable: + return BadMappingError.raise() + + return result + + return GDRx.obs.defer(factory) diff --git a/addons/reactivex/observable/catch.gd b/addons/reactivex/observable/catch.gd new file mode 100644 index 0000000..aa0c394 --- /dev/null +++ b/addons/reactivex/observable/catch.gd @@ -0,0 +1,70 @@ +## Continues an observable sequence that is terminated by an +## error with the next observable sequence. +## [br] +## [b]Examples:[/b] +## [codeblock] +## var res = GDRx.obs.catch_with_iterable(GDRx.util.Iter([xs, ys, zs])) +## [/codeblock] +## [br] +## [b]Args:[/b] +## [br] +## [code]sources[/code] an iterable of observables. Thus a generator is accepted. +## [br][br] +## [b]Returns:[/b] +## [br] +## An observable sequence containing elements from consecutive +## source sequences until a source sequence terminates +## successfully. +static func catch_with_iterable_(sources : IterableBase) -> Observable: + + var sources_ : Iterator = sources.iter() + + var subscribe = func(observer : ObserverBase, scheduler_ : SchedulerBase = null) -> DisposableBase: + var _scheduler = scheduler_ if scheduler_ != null else CurrentThreadScheduler.singleton() + + var subscription = SerialDisposable.new() + var cancelable = SerialDisposable.new() + var last_error = RefValue.Null() + var is_disposed = RefValue.Set(false) + + var action = func(_scheduler : SchedulerBase, _state = null, action_ : Callable = func(__, ___, ____): return): + var on_error = func(err): + last_error.v = err + cancelable.disposable = _scheduler.schedule(action_.bind(action_)) + + if is_disposed.v: + return + + var current = RefValue.Null() + if GDRx.try(func(): + current.v = sources_.next() + ) \ + .catch("Error", func(err): + observer.on_error(err) + ) \ + .end_try_catch(): + pass + elif current.v is ItEnd: + if last_error.v != null: + observer.on_error(last_error.v) + else: + observer.on_completed() + else: + var d = SingleAssignmentDisposable.new() + subscription.disposable = d + d.disposable = current.v.subscribe( + observer.on_next, + on_error, + observer.on_completed, + scheduler_ + ) + action = action.bind(action) + + cancelable.disposable = _scheduler.schedule(action) + + var dispose = func(): + is_disposed.v = true + + return CompositeDisposable.new([subscription, cancelable, Disposable.new(dispose)]) + + return Observable.new(subscribe) diff --git a/addons/reactivex/observable/combinelatest.gd b/addons/reactivex/observable/combinelatest.gd new file mode 100644 index 0000000..370e59a --- /dev/null +++ b/addons/reactivex/observable/combinelatest.gd @@ -0,0 +1,84 @@ +## Merges the specified observable sequences into one observable +## sequence by creating a tuple whenever any of the +## observable sequences produces an element. +## [br] +## [b]Examples:[/b] +## [codeblock] +## var obs = GDRx.obs.combine_latest([obs1, obs2, obs3]) +## [/codeblock] +## [br] +## [b]Returns:[/b] +## [br] +## An observable sequence containing the result of combining +## elements of the sources into a tuple. +static func combine_latest_(sources_) -> Observable: + var sources : Array[Observable] = GDRx.util.unpack_arg(sources_) + var parent = sources[0] + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> CompositeDisposable: + + var n = sources.size() + var has_value = [] ; for __ in range(n): has_value.append(false) + var has_value_all = [false] + var is_done = [] ; for __ in range(n): is_done.append(false) + var values = [] ; for __ in range(n): values.append(null) + + var _next = func(i): + has_value[i] = true + + if has_value_all[0] or has_value.all(func(elem): return elem): + var res = Tuple.new(values) + observer.on_next(res) + + # Would be way shorter with list arithmetic! + # elif all([x for j, x in enumerate(is_done) if j != i]): + else: + var completed = true + var j = 0 + for x in is_done: + if j == i: + j += 1 + continue + if not x: + completed = false + break + j += 1 + if completed: + observer.on_completed() + + has_value_all[0] = has_value.all(func(elem): return elem) + + var done = func(i): + is_done[i] = true + if is_done.all(func(elem): return elem): + observer.on_completed() + + var subscriptions = [] ; for __ in range(n): subscriptions.append(null) + + var fun = func(i): + subscriptions[i] = SingleAssignmentDisposable.new() + + var on_next = func(x): + var __ = LockGuard.new(parent.lock) + values[i] = x + _next.call(i) + + var on_completed = func(): + var __ = LockGuard.new(parent.lock) + done.call(i) + + var subscription = subscriptions[i] + if GDRx.assert_(subscription != null): return + subscription.disposable = sources[i].subscribe( + on_next, observer.on_error, + on_completed, scheduler + ) + + for idx in range(n): + fun.call(idx) + return CompositeDisposable.new(subscriptions) + + return Observable.new(subscribe) diff --git a/addons/reactivex/observable/concat.gd b/addons/reactivex/observable/concat.gd new file mode 100644 index 0000000..4a7f293 --- /dev/null +++ b/addons/reactivex/observable/concat.gd @@ -0,0 +1,48 @@ +static func concat_with_iterable_(sources : IterableBase) -> Observable: + + var subscribe = func(observer : ObserverBase, scheduler_ : SchedulerBase = null) -> DisposableBase: + var _scheduler = scheduler_ if scheduler_ != null else CurrentThreadScheduler.singleton() + + var sources_ : Iterator = sources.iter() + + var subscription = SerialDisposable.new() + var cancelable = SerialDisposable.new() + var is_disposed = RefValue.Set(false) + + var action = func(_scheduler : SchedulerBase, _state = null, action_ = func(__, ___, ____): return): + if is_disposed.v: + return + + var on_completed = func(): + cancelable.disposable = _scheduler.schedule(action_.bind(action_)) + + var current = RefValue.Null() + if GDRx.try(func(): + current.v = sources_.next() + ) \ + .catch("Error", func(err): + observer.on_error(err) + ) \ + .end_try_catch(): + pass + elif current.v is ItEnd: + observer.on_completed() + else: + var d = SingleAssignmentDisposable.new() + subscription.disposable = d + d.disposable = current.v.subscribe( + observer.on_next, + observer.on_error, + on_completed, + scheduler_ + ) + action = action.bind(action) + + cancelable.disposable = _scheduler.schedule(action) + + var dispose = func(): + is_disposed.v = true + + return CompositeDisposable.new([subscription, cancelable, Disposable.new(dispose)]) + + return Observable.new(subscribe) diff --git a/addons/reactivex/observable/connectableobservable.gd b/addons/reactivex/observable/connectableobservable.gd new file mode 100644 index 0000000..f65d0b8 --- /dev/null +++ b/addons/reactivex/observable/connectableobservable.gd @@ -0,0 +1,79 @@ +extends Observable +class_name ConnectableObservable + +## A connectable observable +## +## Represents an observable that can be connected and +## disconnected. + +var subject : SubjectBase +var has_subscription : bool +var subscription : DisposableBase +var source : ObservableBase + +func _init(source_ : ObservableBase, subject_ : SubjectBase): + self.subject = subject_ + self.has_subscription = false + self.subscription = null + self.source = source_ + + super._init() + +func _subscribe_core( + observer : ObserverBase, + scheduler : SchedulerBase = null +) -> DisposableBase: + return self.subject.subscribe(observer, GDRx.basic.noop, GDRx.basic.noop, scheduler) + +## Connects the observable +func connect_observable(scheduler : SchedulerBase = null) -> DisposableBase: + if not self.has_subscription: + self.has_subscription = true + + var dispose = func(): + self.has_subscription = false + + var subscription_ = self.source.subscribe(self.subject, GDRx.basic.noop, GDRx.basic.noop, scheduler) + self.subscription = CompositeDisposable.new([subscription_, Disposable.new(dispose)]) + + return self.subscription + +## Returns an observable sequence that stays connected to the +## source indefinitely to the observable sequence. +## Providing a [i]subscriber_count[/i] will cause it to [method connect_observable] after +## that many subscriptions occur. A [i]subscriber_count[/i] of 0 will +## result in emissions firing immediately without waiting for +## subscribers. +func auto_connect_observable(subscriber_count : int = 1) -> Observable: + var connectable_subscription : Array[DisposableBase] = [null] + @warning_ignore("shadowed_variable") + var count = [0] + @warning_ignore("shadowed_variable") + var source = self + @warning_ignore("shadowed_variable_base_class") + var is_connected = [false] + + if subscriber_count == 0: + connectable_subscription[0] = source.connect_observable() + is_connected[0] = true + + @warning_ignore("shadowed_variable") + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + count[0] += 1 + var should_connect = count[0] == subscriber_count and not is_connected[0] + var _subscription = source.subscribe(observer) + if should_connect: + connectable_subscription[0] = source.connect_observable(scheduler) + is_connected[0] = true + + var dispose = func(): + _subscription.dispose() + count[0] -= 1 + is_connected[0] = false + + return Disposable.new(dispose) + + return Observable.new(subscribe) diff --git a/addons/reactivex/observable/defer.gd b/addons/reactivex/observable/defer.gd new file mode 100644 index 0000000..029f620 --- /dev/null +++ b/addons/reactivex/observable/defer.gd @@ -0,0 +1,38 @@ +## Returns an observable sequence that invokes the specified factory +## function whenever a new observer subscribes. +## [br] +## [b]Example:[/b] +## [codeblock] +## var res = GDRx.obs.defer(func(scheduler): return GDRx.of([1, 2, 3])) +## [/codeblock] +## [br] +## [b]Args:[/b] +## [br] +## [code]observable_factory[/code] Observable factory function to invoke for +## each observer that subscribes to the resulting sequence. The +## factory takes a single argument, the scheduler used. +## [br][br] +## [b]Returns:[/b] +## [br] +## An observable sequence whose observers trigger an invocation +## of the given observable factory function. +static func defer_(factory : Callable = GDRx.basic.default_factory) -> Observable: + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + + var result = RefValue.Null() + var failed = RefValue.Null() + if GDRx.try(func(): + result.v = factory.call(scheduler if scheduler != null else ImmediateScheduler.singleton()) + ) \ + .catch("Error", func(err): + failed.v = GDRx.obs.throw(err).subscribe(observer) + ) \ + .end_try_catch(): + return failed.v + return result.v.subscribe(observer, GDRx.basic.noop, GDRx.basic.noop, scheduler) + + return Observable.new(subscribe) diff --git a/addons/reactivex/observable/empty.gd b/addons/reactivex/observable/empty.gd new file mode 100644 index 0000000..4fd2184 --- /dev/null +++ b/addons/reactivex/observable/empty.gd @@ -0,0 +1,14 @@ +static func empty_(scheduler : SchedulerBase = null) -> Observable: + + var subscribe = func(observer : ObserverBase, scheduler_ : SchedulerBase = null) -> DisposableBase: + var _scheduler : SchedulerBase + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = ImmediateScheduler.singleton() + + var action = func(_scheduler : SchedulerBase, _state): + observer.on_completed() + + return _scheduler.schedule(action) + + return Observable.new(subscribe) diff --git a/addons/reactivex/observable/forkjoin.gd b/addons/reactivex/observable/forkjoin.gd new file mode 100644 index 0000000..b8c900a --- /dev/null +++ b/addons/reactivex/observable/forkjoin.gd @@ -0,0 +1,64 @@ +## Wait for observables to complete and then combine last values +## they emitted into a tuple. Whenever any of that observables completes +## without emitting any value, result sequence will complete at that moment as well. +## [br][br] +## [b]Examples:[/b] +## [codeblock] +## var obs = GDRx.obs.fork_join([obs1, obs2, obs3]) +## [/codeblock] +## [br] +## [b]Returns:[/b] +## [br] +## An observable sequence containing the result of combining last element from +## each source in given sequence. +static func fork_join_(sources_) -> Observable: + var sources : Array[Observable] = GDRx.util.unpack_arg(sources_) + var parent = sources[0] + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var n = sources.size() + var values = [] ; for __ in range(n): values.append(null) + var is_done = [] ; for __ in range(n): values.append(false) + var has_value = [] ; for __ in range(n): values.append(false) + + var done = func(i : int): + is_done[i] = true + + if not has_value[i]: + observer.on_completed() + return + + if is_done.all(func(elem): return elem): + if has_value.all(func(elem): return elem): + observer.on_next(Tuple.new(values)) + observer.on_completed() + else: + observer.on_completed() + + var subscriptions : Array[SingleAssignmentDisposable] = [] + for __ in range(n): subscriptions.append(null) + + var _subscribe = func(i : int): + subscriptions[i] = SingleAssignmentDisposable.new() + + var on_next = func(value): + var __ = LockGuard.new(parent.lock) + values[i] = value + has_value[i] = true + + var on_completed = func(): + var __ = LockGuard.new(parent.lock) + done.call(i) + + subscriptions[i].disposable = sources[i].subscribe( + on_next, observer.on_error, on_completed, scheduler + ) + + for i in range(n): + _subscribe.call(i) + return CompositeDisposable.new(subscriptions) + + return Observable.new(subscribe) diff --git a/addons/reactivex/observable/fromcallback.gd b/addons/reactivex/observable/fromcallback.gd new file mode 100644 index 0000000..928174e --- /dev/null +++ b/addons/reactivex/observable/fromcallback.gd @@ -0,0 +1,48 @@ +## Converts a callback function to an observable sequence. +## [br] +## [b]Args:[/b] +## [br] +## [code]fun[/code] Function with a callback as the last argument to +## convert to an Observable sequence. +## [br] +## [code]mapper[/code] [Optional] A mapper which takes the arguments +## from the callback to produce a single item to yield on next. +## [br][br] +## [b]Returns:[/b] +## [br] +## A function, when executed with the required arguments minus +## the callback, produces an Observable sequence with a single value of +## the arguments to the callback as a list. +static func from_callback( + fun : Callable = func(_args : Array, _cb : Callable): return, + mapper = null +) -> Callable: + + var function = func(args : Array) -> Observable: + var arguments = args + + var subscribe = func( + observer : ObserverBase, + _scheduler : SchedulerBase = null + ) -> DisposableBase: + var handler = func(args : Array): + var results = RefValue.Set(args) + if mapper != null: + if GDRx.try(func(): + results.v = mapper.call(args) + ) \ + .catch("Error", func(e): + observer.on_error(e) + ).end_try_catch(): + return Disposable.new() + observer.on_next(results.v) + else: + observer.on_next(results.v) + observer.on_completed() + + fun.call(arguments, handler) + return Disposable.new() + + return Observable.new(subscribe) + + return function diff --git a/addons/reactivex/observable/fromiterable.gd b/addons/reactivex/observable/fromiterable.gd new file mode 100644 index 0000000..acffefa --- /dev/null +++ b/addons/reactivex/observable/fromiterable.gd @@ -0,0 +1,56 @@ +## Converts an iterable to an observable sequence. +## [br] +## [b]Example:[/b] +## [codeblock] +## var res = GDRx.obs.from_iterable(GDRx.iter([1,2,3])) +## var res = GDRx.obs.from_iterable(GDRx.util.Infinite()) +## [/codeblock] +## [br] +## [b]Args:[/b] +## [br] +## [code]iterable[/code] An instance of [IterableBase] +## [br] +## [code]scheduler[/code] An optional scheduler to schedule the values on. +## [br][br] +## [b]Returns:[/b] +## [br] +## The observable sequence whose elements are pulled from the +## given iterable sequence. +static func from_iterable_( + iterable : IterableBase, + scheduler : SchedulerBase = null +) -> Observable: + + var subscribe = func( + observer : ObserverBase, + scheduler_ : SchedulerBase = null + ) -> DisposableBase: + var _scheduler : SchedulerBase + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = CurrentThreadScheduler.singleton() + + var iterator = iterable.iter() + var disposed = RefValue.Set(false) + + var action = func(__ : SchedulerBase, ___ = null): + GDRx.try(func(): + while not disposed.v: + var value = iterator.next() + if value is ItEnd: + observer.on_completed() + else: + observer.on_next(value) + ) \ + .catch("Error", func(error): + observer.on_error(error) + ) \ + .end_try_catch() + + var dispose = func(): + disposed.v = true + + var disp = Disposable.new(dispose) + return CompositeDisposable.new([_scheduler.schedule(action), disp]) + + return Observable.new(subscribe) diff --git a/addons/reactivex/observable/generate.gd b/addons/reactivex/observable/generate.gd new file mode 100644 index 0000000..0f38a3b --- /dev/null +++ b/addons/reactivex/observable/generate.gd @@ -0,0 +1,44 @@ +static func generate_( + initial_state, + condition : Callable = GDRx.basic.default_condition, + iterate : Callable = GDRx.basic.identity +) -> Observable: + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var scheduler_ : SchedulerBase = scheduler if scheduler != null else CurrentThreadScheduler.singleton() + var first = RefValue.Set(true) + var state = RefValue.Set(initial_state) + var mad = MultipleAssignmentDisposable.new() + + var action = func(scheduler : SchedulerBase, _state, action_ : Callable): + var has_result = RefValue.Set(false) + var result = RefValue.Null() + + if GDRx.try(func(): + if first.v: + first.v = false + else: + state.v = iterate.call(state.v) + + has_result.v = condition.call(state.v) + if has_result.v: + result.v = state.v + ) \ + .catch("Error", func(err): + observer.on_error(err) + ) \ + .end_try_catch(): return + + if has_result.v: + observer.on_next(result.v) + mad.disposable = scheduler.schedule(action_.bind(action_)) + else: + observer.on_completed() + + mad.disposable = scheduler_.schedule(action.bind(action)) + return mad + + return Observable.new(subscribe) diff --git a/addons/reactivex/observable/generatewithrelativetime.gd b/addons/reactivex/observable/generatewithrelativetime.gd new file mode 100644 index 0000000..9024016 --- /dev/null +++ b/addons/reactivex/observable/generatewithrelativetime.gd @@ -0,0 +1,77 @@ +## Generates an observable sequence by iterating a state from an +## initial state until the condition fails. +## [br] +## [b]Example:[/b] +## [codeblock] +## var res = GDRx.obs.generate_with_relative_time( +## 0, func(x): return true, func(x): return x + 1, func(x): return 0.5 +## ) +## [/codeblock] +## [br] +## [b]Args:[/b] +## [br] +## [code]initial_state[/code] Initial state. +## [br] +## [code]condition[/code] Condition to terminate generation (upon returning +## false). +## [br] +## [code]iterate[/code] Iteration step function. +## [br] +## [code]time_mapper[/code] Time mapper function to control the speed of +## values being produced each iteration, returning relative +## times, i.e. either floats denoting seconds or instances of +## timedelta. +## [br][br] +## [b]Returns:[/b] +## [br] +## The generated sequence. +static func generate_with_relative_time_( + initial_state, + condition : Callable = GDRx.basic.default_condition, + iterate : Callable = GDRx.basic.identity, + time_mapper : Callable = func(_state) -> float: return 1.0 +) -> Observable: + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var _scheduler : SchedulerBase = scheduler if scheduler != null else SceneTreeTimeoutScheduler.singleton() + var mad = MultipleAssignmentDisposable.new() + var state = RefValue.Set(initial_state) + var has_result = RefValue.Set(false) + var result = RefValue.Null() + var first = RefValue.Set(true) + var time = RefValue.Null() + + var action = func(scheduler : SchedulerBase, __, action_ : Callable): + if has_result.v: + observer.on_next(result.v) + + if GDRx.try(func(): + if first.v: + first.v = false + else: + state.v = iterate.call(state.v) + + has_result.v = condition.call(state.v) + + if has_result.v: + result.v = state.v + time.v = time_mapper.call(state.v) + ) \ + .catch("Error", func(e): + observer.on_error(e) + ) \ + .end_try_catch(): return + + if has_result.v: + if GDRx.assert_(time.v != null): return + mad.disposable = scheduler.scheduler_relative(time.v, action_.bind(action_)) + else: + observer.on_completed() + + mad.disposable = _scheduler.schedule_relative(0, action.bind(action)) + return mad + + return Observable.new(subscribe) diff --git a/addons/reactivex/observable/groupedobservable.gd b/addons/reactivex/observable/groupedobservable.gd new file mode 100644 index 0000000..20ed197 --- /dev/null +++ b/addons/reactivex/observable/groupedobservable.gd @@ -0,0 +1,30 @@ +extends Observable +class_name GroupedObservable + +var key +var underlying_observable : Observable + +func _init( + key_, + underlying_observable_ : Observable, + merged_disposable : RefCountDisposable = null): + super._init() + self.key = key_ + + @warning_ignore("shadowed_variable") + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + return CompositeDisposable.new([ + merged_disposable.disposable if merged_disposable != null else Disposable.new(), + underlying_observable_.subscribe(observer, GDRx.basic.noop, GDRx.basic.noop, scheduler) + ]) + + self.underlying_observable = underlying_observable_ if merged_disposable == null else Observable.new(subscribe) + +func _subscribe_core( + observer : ObserverBase, + scheduler : SchedulerBase = null +) -> DisposableBase: + return self.underlying_observable.subscribe(observer, GDRx.basic.noop, GDRx.basic.noop, scheduler) diff --git a/addons/reactivex/observable/ifthen.gd b/addons/reactivex/observable/ifthen.gd new file mode 100644 index 0000000..11df35c --- /dev/null +++ b/addons/reactivex/observable/ifthen.gd @@ -0,0 +1,38 @@ +## Determines whether an observable collection contains values. +## [br] +## [b]Example:[/b] +## [codeblock] +## 1 - var res = GDRx.obs.if_then(condition, obs1) +## 2 - var res = GDRx.obs.if_then(condition, obs1, obs2) +## [/codeblock] +## [br] +## [b]Args:[/b] +## [br] +## [code]condition[/code] The condition which determines if the then_source or +## else_source will be run. +## [br] +## [code]then_source[/code] The observable sequence or Promise that +## will be run if the condition function returns true. +## [br] +## [code]else_source[/code] [Optional] The observable sequence or +## Promise that will be run if the condition function returns +## False. If this is not provided, it defaults to +## [code]GDRx.obs.empty()[/code] +## [br][br] +## [b]Returns:[/b] +## [br] +## An observable sequence which is either the [code]then_source[/code] or +## [code]else_source[/code]. +static func if_then_( + condition : Callable = GDRx.basic.default_condition, + then_source : Observable = null, + else_source : Observable = null +) -> Observable: + + var then_source_ = then_source if then_source != null else GDRx.obs.empty() + var else_source_ = else_source if else_source != null else GDRx.obs.empty() + + var factory = func(__ : SchedulerBase) -> Observable: + return then_source_ if condition.call() else else_source_ + + return GDRx.obs.defer(factory) diff --git a/addons/reactivex/observable/interval.gd b/addons/reactivex/observable/interval.gd new file mode 100644 index 0000000..ee3bc7b --- /dev/null +++ b/addons/reactivex/observable/interval.gd @@ -0,0 +1,6 @@ +static func interval_( + period : float, + scheduler : SchedulerBase = null +) -> ObservableBase: + + return GDRx.obs.timer(period, period, scheduler) diff --git a/addons/reactivex/observable/merge.gd b/addons/reactivex/observable/merge.gd new file mode 100644 index 0000000..bd03a7e --- /dev/null +++ b/addons/reactivex/observable/merge.gd @@ -0,0 +1,2 @@ +static func merge_(sources) -> Observable: + return GDRx.obs.from_iterable(GDRx.to_iterable(sources)).pipe1(GDRx.op.merge_all()) diff --git a/addons/reactivex/observable/never.gd b/addons/reactivex/observable/never.gd new file mode 100644 index 0000000..067b844 --- /dev/null +++ b/addons/reactivex/observable/never.gd @@ -0,0 +1,12 @@ +## Returns a non-terminating observable sequence, which can be used +## to denote an infinite duration (e.g. when using reactive joins). +## [br][br] +## [b]Returns:[/b] +## [br] +## An observable sequence whose observers will never get called. +static func never_() -> Observable: + + var subscribe = func(_observer : ObserverBase, _scheduler : SchedulerBase = null) -> DisposableBase: + return Disposable.new() + + return Observable.new(subscribe) diff --git a/addons/reactivex/observable/observable.gd b/addons/reactivex/observable/observable.gd new file mode 100644 index 0000000..114200f --- /dev/null +++ b/addons/reactivex/observable/observable.gd @@ -0,0 +1,820 @@ +extends ObservableBase +class_name Observable + +## Observable base class. + +var lock : RLock +var _subscribe : Callable + +func _init(subscribe_ : Callable = func(_observer : ObserverBase, _scheduler : SchedulerBase = null) -> DisposableBase: + return Disposable.new()): + super._init() + + self.lock = RLock.new() + self._subscribe = subscribe_ + +func _subscribe_core( + observer : ObserverBase, + scheduler : SchedulerBase = null) -> DisposableBase: + return self._subscribe.call(observer, scheduler) + +## Subscribe an observer to the observable sequence. +## [br] +## You may subscribe using an observer or callbacks, not both; if the first +## argument is an instance of [Observer] ([ObserverBase]) or if +## it has a [Callable] attribute named [code]on_next[/code], then any callback +## arguments will be ignored. +## [br][br] +## [b]Examples:[/b] +## [codeblock] +## source.subscribe(observer) +## source.subscribe(on_next) +## source.subscribe(on_next, on_error) +## source.subscribe(on_next, on_error, on_completed) +## source.subscribe(on_next, on_error, on_completed, scheduler) +## [/codeblock] +## [br] +## [b]Args:[/b] +## [br] +## [code]observer[/code] The object that is to receive +## notifications. +## [br] +## [code]on_error[/code] [Optional] Action to invoke upon exceptional termination +## of the observable sequence. +## [br] +## [code]on_completed[/code] [Optional] Action to invoke upon graceful termination +## of the observable sequence. +## [br] +## [code]on_next[/code] Action to invoke for each element in the +## observable sequence. +## [br] +## [code]scheduler[/code] [Optional] The default scheduler to use for this +## subscription. +## [br][br] +## [b]Returns:[/b] +## [br] +## Disposable object representing an observer's subscription to +## the observable sequence. +func subscribe( + on_next = null, # Callable or Observer or Object with callbacks + on_error : Callable = GDRx.basic.noop, + on_completed : Callable = GDRx.basic.noop, + scheduler : SchedulerBase = null) -> DisposableBase: + if on_next == null: + on_next = GDRx.basic.noop + + if on_next is ObserverBase: + var obv : ObserverBase = on_next + on_next = func(i): obv.on_next.call(i) + on_error = func(e): obv.on_error.call(e) + on_completed = func(): obv.on_completed.call() + elif on_next is Object and on_next.has_method("on_next"): + var obv : Object = on_next + if obv.has_method("on_next"): + on_next = func(i): obv.on_next.call(i) + if obv.has_method("on_error"): + on_error = func(e): obv.on_error.call(e) + if obv.has_method("on_completed"): + on_completed = func(): obv.on_completed.call() + + var auto_detach_observer : AutoDetachObserver = AutoDetachObserver.new( + on_next, on_error, on_completed + ) + + var fix_subscriber = func(subscriber) -> DisposableBase: + if subscriber is DisposableBase or subscriber.has_method("dispose"): + return subscriber + return Disposable.new(subscriber) + + var set_disposable = func(__ : SchedulerBase = null, ___ = null): + var subscriber = RefValue.Null() + if not GDRx.try(func(): + subscriber.v = self._subscribe_core(auto_detach_observer, scheduler) + ) \ + .catch("Error", func(ex): + if not auto_detach_observer.fail(ex): + GDRx.raise(ex) + ) \ + .end_try_catch(): + auto_detach_observer.subscription = fix_subscriber.call(subscriber.v) + + var current_thread_scheduler = CurrentThreadScheduler.singleton() + if current_thread_scheduler.schedule_required(): + current_thread_scheduler.schedule(set_disposable) + else: + set_disposable.call() + + return Disposable.new(func(): auto_detach_observer.dispose()) + +# ============================================================================ # +# AWAIT # +# ============================================================================ # + +## Coroutine which finishes when next item is emitted. +## [br][br] +## [code]var item = await obs.next()[/code] +func next() -> Variant: + return await ObservableAwait.new().on_next(self) + +## Coroutine which finishes when sequence terminates with error. +## [br][br] +## [code]var err = await obs.error()[/code] +func error() -> Variant: + return await ObservableAwait.new().on_error(self) + +## Coroutine which finishes when sequence terminates gracefully. +## [br][br] +## [code]await obs.completed()[/code] +func completed(): + return await ObservableAwait.new().on_completed(self) + +# ============================================================================ # +# PIPE # +# ============================================================================ # + +## Pipe operator +func pipe0() -> Variant: + return GDRx.pipe.pipe(self, GDRx.util.Iter([])) + +## Pipe operator +func pipe1(__fn1 : Callable) -> Variant: + return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1])) + +## Pipe operator +func pipe2( + __fn1 : Callable, + __fn2 : Callable +) -> Variant: + return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2])) + +## Pipe operator +func pipe3( + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable +) -> Variant: + return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2, __fn3])) + +## Pipe operator +func pipe4( + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable +) -> Variant: + return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4])) + +## Pipe operator +func pipe5( + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable, + __fn5 : Callable +) -> Variant: + return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5])) + +## Pipe operator +func pipe6( + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable, + __fn5 : Callable, + __fn6 : Callable +) -> Variant: + return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5, __fn6])) + +## Pipe operator +func pipe7( + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable, + __fn5 : Callable, + __fn6 : Callable, + __fn7 : Callable +) -> Variant: + return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5, __fn6, __fn7])) + +## Pipe operator +func pipe8( + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable, + __fn5 : Callable, + __fn6 : Callable, + __fn7 : Callable, + __fn8 : Callable +) -> Variant: + return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5, __fn6, __fn7, __fn8])) + +## Pipe operator +func pipe9( + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable, + __fn5 : Callable, + __fn6 : Callable, + __fn7 : Callable, + __fn8 : Callable, + __fn9 : Callable +) -> Variant: + return GDRx.pipe.pipe(self, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5, __fn6, __fn7, __fn8, __fn9])) + +## Pipe operator taking a list +func pipea(arr : Array): + return GDRx.pipe.pipe(self, GDRx.util.Iter(arr)) + +## Compose multiple operators left to right. +## [br] +## Composes zero or more operators into a functional composition. +## The operators are composed from left to right. A composition of zero +## operators gives back the original source. +## [br][br] +## [b]Examples:[/b] +## [codeblock] +## source.pipe0() == source +## source.pipe1(f) == f(source) +## source.pipe2(g, f) == f(g(source)) +## source.pipe3(h, g, f) == f(g(h(source))) +## [/codeblock] +## [br] +## [b]Args:[/b] +## [br] +## [code]operators[/code] Sequence of operators. +## [br][br] +## [b]Returns:[/b] +## [br] +## The composed observable. +func pipe(fns : IterableBase) -> Variant: + return GDRx.pipe.compose(fns).call(self) + +# ============================================================================ # +# OPERATORS # +# ============================================================================ # + +## See: [b]res://addons/reactivex/operators/connectable/_refcount.gd[/b] +func ref_count() -> Observable: + return GDRx.op.ref_count().call(self) + +## See: [b]res://addons/reactivex/operators/_all.gd[/b] +func all(predicate : Callable) -> Observable: + return GDRx.op.all(predicate).call(self) + +## See: [b]res://addons/reactivex/operators/_amb.gd[/b] +func amb(right_source : Observable) -> Observable: + return GDRx.op.amb(right_source).call(self) + +## See: [b]res://addons/reactivex/operators/_asobservable.gd[/b] +func as_observable() -> Observable: + return GDRx.op.as_observable().call(self) + +## See: [b]res://addons/reactivex/operators/_average.gd[/b] +func average(key_mapper = null) -> Observable: + return GDRx.op.average(key_mapper).call(self) + +## See: [b]res://addons/reactivex/operators/_buffer.gd[/b] +func buffer(boundaries : Observable) -> Observable: + return GDRx.op.buffer(boundaries).call(self) + +## See: [b]res://addons/reactivex/operators/_buffer.gd[/b] +func buffer_when(closing_mapper : Callable) -> Observable: + return GDRx.op.buffer_when(closing_mapper).call(self) + +## See: [b]res://addons/reactivex/operators/_buffer.gd[/b] +func buffer_toggle(openings : Observable, closing_mapper : Callable) -> Observable: + return GDRx.op.buffer_toggle(openings, closing_mapper).call(self) + +## See: [b]res://addons/reactivex/operators/_buffer.gd[/b] +func buffer_with_count(count_ : int, skip_ = null) -> Observable: + return GDRx.op.buffer_with_count(count_, skip_).call(self) + +## See: [b]res://addons/reactivex/operators/_bufferwithtime.gd[/b] +func buffer_with_time(timespan : float, timeshift = null, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.buffer_with_time(timespan, timeshift, scheduler).call(self) + +## See: [b]res://addons/reactivex/operators/_bufferwithtimeourcount.gd[/b] +func buffer_with_time_or_count(timespan : float, count_ : int, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.buffer_with_time_or_count(timespan, count_, scheduler).call(self) + +## See: [b]res://addons/reactivex/operators/_catch.gd[/b] +func catch_handler(handler : Callable) -> Observable: + return GDRx.op.catch_handler(self, handler) + +## See: [b]res://addons/reactivex/operators/_catch.gd[/b] +func catch(handler) -> Observable: + return GDRx.op.catch(handler).call(self) + +## See: [b]res://addons/reactivex/operators/_combinelatest.gd[/b] +func combine_latest(others) -> Observable: + return GDRx.op.combine_latest(others).call(self) + +## See: [b]res://addons/reactivex/operators/_concat.gd[/b] +func concat(sources) -> Observable: + return GDRx.op.concat(sources).call(self) + +## See: [b]res://addons/reactivex/operators/_contains.gd[/b] +func contains(value, comparer = GDRx.basic.default_comparer) -> Observable: + return GDRx.op.contains(value, comparer).call(self) + +## See: [b]res://addons/reactivex/operators/_count.gd[/b] +func count(predicate = null) -> Observable: + return GDRx.op.count(predicate).call(self) + +## See: [b]res://addons/reactivex/operators/_debounce.gd[/b] +func debounce(duetime : float, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.debounce(duetime, scheduler).call(self) + +## See: [b]res://addons/reactivex/operators/_debounce.gd[/b] +func throttle_with_mapper(throttle_duration_mapper : Callable) -> Observable: + return GDRx.op.throttle_with_mapper(throttle_duration_mapper).call(self) + +## See: [b]res://addons/reactivex/operators/_defaultifempty.gd[/b] +func default_if_empty(default_value = null) -> Observable: + return GDRx.op.default_if_empty(default_value).call(self) + +## See: [b]res://addons/reactivex/operators/_delay.gd[/b] +func observable_delay_timespan(duetime : float, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.observable_delay_timespan(self, duetime, scheduler) + +## See: [b]res://addons/reactivex/operators/_delay.gd[/b] +func delay(duetime : float, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.delay(duetime, scheduler).call(self) + +## See: [b]res://addons/reactivex/operators/_delaysubscription.gd[/b] +func delay_subscription(duetime : float, time_absolute : bool = false, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.delay_subscription(duetime, time_absolute, scheduler).call(self) + +## See: [b]res://addons/reactivex/operators/_delaywithmapper.gd[/b] +func delay_with_mapper(subscription_delay = null, delay_duration_mapper = null) -> Observable: + return GDRx.op.delay_with_mapper(subscription_delay, delay_duration_mapper).call(self) + +## See: [b]res://addons/reactivex/operators/_dematerialize.gd[/b] +func dematerialize() -> Observable: + return GDRx.op.dematerialize().call(self) + +## See: [b]res://addons/reactivex/operators/_distinct.gd[/b] +func distinct(key_mapper : Callable = GDRx.basic.identity, comparer : Callable = GDRx.basic.default_comparer) -> Observable: + return GDRx.op.distinct(key_mapper, comparer).call(self) + +## See: [b]res://addons/reactivex/operators/_distinctuntilchanged.gd[/b] +func distinct_until_changed(key_mapper : Callable = GDRx.basic.identity, comparer : Callable = GDRx.basic.default_comparer) -> Observable: + return GDRx.op.distinct_until_changed(key_mapper, comparer).call(self) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do_action(on_next = null, on_error = null, on_completed = null) -> Observable: + return GDRx.op.do_action(on_next, on_error, on_completed).call(self) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do(observer : ObserverBase) -> Observable: + return GDRx.op.do(observer).call(self) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do_after_next(after_next : Callable) -> Observable: + return GDRx.op.do_after_next(self, after_next) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do_on_subscribe(on_subscribe : Callable) -> Observable: + return GDRx.op.do_on_subscribe(self, on_subscribe) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do_on_dispose(on_dispose : Callable) -> Observable: + return GDRx.op.do_on_dispose(self, on_dispose) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do_on_terminate(on_terminate : Callable) -> Observable: + return GDRx.op.do_on_terminate(self, on_terminate) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do_after_terminate(after_terminate : Callable) -> Observable: + return GDRx.op.do_after_terminate(self, after_terminate) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do_finally(finally_action_ : Callable) -> Observable: + return GDRx.op.do_finally(finally_action_).call(self) + +## See: [b]res://addons/reactivex/operators/_dowhile.gd[/b] +func do_while(condition : Callable) -> Observable: + return GDRx.op.do_while(condition).call(self) + +## See: [b]res://addons/reactivex/operators/_elementordefault.gd[/b] +func element_at_or_default(index : int, has_default : bool = false, default_value = GDRx.util.GetNotSet()) -> Observable: + return GDRx.op.element_at_or_default(index, has_default, default_value).call(self) + +## See: [b]res://addons/reactivex/operators/_exclusive.gd[/b] +func exclusive() -> Observable: + return GDRx.op.exclusive().call(self) + +## See: [b]res://addons/reactivex/operators/_expand.gd[/b] +func expand(mapper : Callable) -> Observable: + return GDRx.op.expand(mapper).call(self) + +## See: [b]res://addons/reactivex/operators/_filter.gd[/b] +func filter(predicate : Callable = GDRx.basic.default_condition) -> Observable: + return GDRx.op.filter(predicate).call(self) + +## See: [b]res://addons/reactivex/operators/_filter.gd[/b] +func filter_indexed(predicate : Callable = GDRx.basic.default_condition) -> Observable: + return GDRx.op.filter_indexed(predicate).call(self) + +## See: [b]res://addons/reactivex/operators/_finallyaction.gd[/b] +func finally_action(action : Callable) -> Observable: + return GDRx.op.finally_action(action).call(self) + +## See: [b]res://addons/reactivex/operators/_find.gd[/b] +func find_value(predicate : Callable, yield_index : bool) -> Observable: + return GDRx.op.find_value(predicate, yield_index).call(self) + +## See: [b]res://addons/reactivex/operators/_first.gd[/b] +func first(predicate = null) -> Observable: + return GDRx.op.first(predicate).call(self) + +## See: [b]res://addons/reactivex/operators/_firstordefault.gd[/b] +func first_or_default_async(has_default : bool = false, default_value = null) -> Observable: + return GDRx.op.first_or_default_async(has_default, default_value).call(self) + +## See: [b]res://addons/reactivex/operators/_firstordefault.gd[/b] +func first_or_default(predicate = null, default_value = null) -> Observable: + return GDRx.op.first_or_default(predicate, default_value).call(self) + +## See: [b]res://addons/reactivex/operators/_flatmap.gd[/b] +func flat_map(mapper = null) -> Observable: + return GDRx.op.flat_map(mapper).call(self) + +## See: [b]res://addons/reactivex/operators/_flatmap.gd[/b] +func flat_map_indexed(mapper_indexed = null) -> Observable: + return GDRx.op.flat_map_indexed(mapper_indexed).call(self) + +## See: [b]res://addons/reactivex/operators/_flatmap.gd[/b] +func flat_map_latest(mapper = null) -> Observable: + return GDRx.op.flat_map_latest(mapper).call(self) + +## See: [b]res://addons/reactivex/operators/_forkjoin.gd[/b] +func fork_join(args) -> Observable: + return GDRx.op.fork_join(args).call(self) + +## See: [b]res://addons/reactivex/operators/_groupby.gd[/b] +func group_by(key_mapper : Callable, element_mapper = null, subject_mapper = null) -> Observable: + return GDRx.op.group_by(key_mapper, element_mapper, subject_mapper).call(self) + +## See: [b]res://addons/reactivex/operators/_groupbyuntil.gd[/b] +func group_by_until(key_mapper : Callable, duration_mapper : Callable, element_mapper = null, subject_mapper = null) -> Observable: + return GDRx.op.group_by_until(key_mapper, duration_mapper, element_mapper, subject_mapper).call(self) + +## See: [b]res://addons/reactivex/operators/_groupjoin.gd[/b] +func group_join(right : Observable, left_duration_mapper : Callable, right_duration_mapper : Callable) -> Observable: + return GDRx.op.group_join(right, left_duration_mapper, right_duration_mapper).call(self) + +## See: [b]res://addons/reactivex/operators/_ignoreelements.gd[/b] +func ignore_elements() -> Observable: + return GDRx.op.ignore_elements().call(self) + +## See: [b]res://addons/reactivex/operators/_isempty.gd[/b] +func is_empty() -> Observable: + return GDRx.op.is_empty().call(self) + +## See: [b]res://addons/reactivex/operators/_join.gd[/b] +func join(right : Observable, left_duration_mapper : Callable, right_duration_mapper : Callable) -> Observable: + return GDRx.op.join(right, left_duration_mapper, right_duration_mapper).call(self) + +## See: [b]res://addons/reactivex/operators/_last.gd[/b] +func last(predicate = null) -> Observable: + return GDRx.op.last(predicate).call(self) + +## See: [b]res://addons/reactivex/operators/_lastordefault.gd[/b] +func last_or_default_async(has_default : bool = false, default_value = null) -> Observable: + return GDRx.op.last_or_default_async(self, has_default, default_value) + +## See: [b]res://addons/reactivex/operators/_lastordefault.gd[/b] +func last_or_default(default_value = null, predicate = null) -> Observable: + return GDRx.op.last_or_default(default_value, predicate).call(self) + +## See: [b]res://addons/reactivex/operators/_map.gd[/b] +func map(mapper : Callable = GDRx.basic.identity) -> Observable: + return GDRx.op.map(mapper).call(self) + +## See: [b]res://addons/reactivex/operators/_map.gd[/b] +func map_indexed(mapper_indexed : Callable = GDRx.basic.identity) -> Observable: + return GDRx.op.map_indexed(mapper_indexed).call(self) + +## See: [b]res://addons/reactivex/operators/_materialize.gd[/b] +func materialize() -> Observable: + return GDRx.op.materialize().call(self) + +## See: [b]res://addons/reactivex/operators/_max.gd[/b] +@warning_ignore("shadowed_global_identifier") +func max(comparer = null) -> Observable: + return GDRx.op.max(comparer).call(self) + +## See: [b]res://addons/reactivex/operators/_maxby.gd[/b] +func max_by(key_mapper : Callable, comparer = null) -> Observable: + return GDRx.op.max_by(key_mapper, comparer).call(self) + +## See: [b]res://addons/reactivex/operators/_merge.gd[/b] +func merge(sources, max_concorrent : int = -1) -> Observable: + return GDRx.op.merge(sources, max_concorrent).call(self) + +## See: [b]res://addons/reactivex/operators/_merge.gd[/b] +func merge_all() -> Observable: + return GDRx.op.merge_all().call(self) + +## See: [b]res://addons/reactivex/operators/_min.gd[/b] +@warning_ignore("shadowed_global_identifier") +func min(comparer = null) -> Observable: + return GDRx.op.min(comparer).call(self) + +## See: [b]res://addons/reactivex/operators/_minby.gd[/b] +func extrema_by(key_mapper : Callable, comparer : Callable) -> Observable: + return GDRx.op.extrema_by(self, key_mapper, comparer) + +## See: [b]res://addons/reactivex/operators/_minby.gd[/b] +func min_by(key_mapper : Callable, comparer = null) -> Observable: + return GDRx.op.min_by(key_mapper, comparer).call(self) + +## See: [b]res://addons/reactivex/operators/_multicast.gd[/b] +func multicast(subject : SubjectBase = null, subject_factory = null, mapper = null) -> Observable: + return GDRx.op.multicast(subject, subject_factory, mapper).call(self) + +## See: [b]res://addons/reactivex/operators/_observeon.gd[/b] +func observe_on(scheduler : SchedulerBase) -> Observable: + return GDRx.op.observe_on(scheduler).call(self) + +## See: [b]res://addons/reactivex/operators/_oftype.gd[/b] +func oftype(type, push_err : bool = true, type_equality : Callable = GDRx.basic.default_type_equality) -> Observable: + return GDRx.op.oftype(type, push_err, type_equality).call(self) + +## See: [b]res://addons/reactivex/operators/_onerrorresumenext.gd[/b] +func on_error_resume_next(second : Observable) -> Observable: + return GDRx.op.on_error_resume_next(second).call(self) + +## See: [b]res://addons/reactivex/operators/_pairwise.gd[/b] +func pairwise() -> Observable: + return GDRx.op.pairwise().call(self) + +## See: [b]res://addons/reactivex/operators/_partiton.gd[/b] +func partition(predicate : Callable = GDRx.basic.default_condition) -> Array[Observable]: + return GDRx.op.partition(predicate).call(self) + +## Alternative to [method partition] but returning type an [IterableBase] containing. +## the partitioned [Observable]s. +func partitionit(predicate : Callable = GDRx.basic.default_condition) -> IterableBase: + return Iterator.to_iterable(self.partition(predicate)) + +## See: [b]res://addons/reactivex/operators/_partition.gd[/b] +func partition_indexed(predicate_indexed : Callable = GDRx.basic.default_condition) -> Observable: + return GDRx.op.partition_indexed(predicate_indexed).call(self) + +## See: [b]res://addons/reactivex/operators/_pluck.gd[/b] +func pluck(key) -> Observable: + return GDRx.op.pluck(key).call(self) + +## See: [b]res://addons/reactivex/operators/_pluck.gd[/b] +func pluck_attr(prop : String) -> Observable: + return GDRx.op.pluck_attr(prop).call(self) + +## See: [b]res://addons/reactivex/operators/_publish.gd[/b] +func publish(mapper = null) -> Observable: + return GDRx.op.publish(mapper).call(self) + +## See: [b]res://addons/reactivex/operators/_publish.gd[/b] +func share() -> Observable: + return GDRx.op.share().call(self) + +## See: [b]res://addons/reactivex/operators/_publishvalue.gd[/b] +func publish_value(initial_value, mapper = null) -> Observable: + return GDRx.op.publish_value(initial_value, mapper).call(self) + +## See: [b]res://addons/reactivex/operators/_reduce.gd[/b] +func reduce(accumulator : Callable, seed_ = GDRx.util.GetNotSet()) -> Observable: + return GDRx.op.reduce(accumulator, seed_).call(self) + +## See: [b]res://addons/reactivex/operators/_repeat.gd[/b] +func repeat(repeat_count = null) -> Observable: + return GDRx.op.repeat(repeat_count).call(self) + +## See: [b]res://addons/reactivex/operators/_replay.gd[/b] +func replay(mapper = null, buffer_size = null, window_ = null, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.replay(mapper, buffer_size, window_, scheduler).call(self) + +## See: [b]res://addons/reactivex/operators/_retry.gd[/b] +func retry(retry_count : int = -1) -> Observable: + return GDRx.op.retry(retry_count).call(self) + +## See: [b]res://addons/reactivex/operators/_sample.gd[/b] +func sample_observable(sampler : Observable) -> Observable: + return GDRx.op.sample_observable(self, sampler) + +## See: [b]res://addons/reactivex/operators/_sample.gd[/b] +func sample(sampler : Observable, sampler_time : float = NAN, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.sample(sampler, sampler_time, scheduler).call(self) + +## See: [b]res://addons/reactivex/operators/_scan.gd[/b] +func scan(accumulator : Callable, seed_ = GDRx.util.GetNotSet()) -> Observable: + return GDRx.op.scan(accumulator, seed_).call(self) + +## See: [b]res://addons/reactivex/operators/_sequenceequal.gd[/b] +func sequence_equal(second, comparer = null, second_it : IterableBase = null) -> Observable: + return GDRx.op.sequence_equal(second, comparer, second_it).call(self) + +## See: [b]res://addons/reactivex/operators/_single.gd[/b] +func single(predicate = null) -> Observable: + return GDRx.op.single(predicate).call(self) + +## See: [b]res://addons/reactivex/operators/_singleordefault.gd[/b] +func single_or_default_async(has_default : bool = false, default_value = null) -> Observable: + return GDRx.op.single_or_default_async(has_default, default_value).call(self) + +## See: [b]res://addons/reactivex/operators/_singleordefault.gd[/b] +func single_or_default(predicate = null, default_value = null) -> Observable: + return GDRx.op.single_or_default(predicate, default_value).call(self) + +## See: [b]res://addons/reactivex/operators/_skip.gd[/b] +func skip(count_ : int) -> Observable: + return GDRx.op.skip(count_).call(self) + +## See: [b]res://addons/reactivex/operators/_skiplast.gd[/b] +func skip_last(count_ : int) -> Observable: + return GDRx.op.skip_last(count_).call(self) + +## See: [b]res://addons/reactivex/operators/_skiplastwithtime.gd[/b] +func skip_last_with_time(duration : float, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.skip_last_with_time(duration, scheduler).call(self) + +## See: [b]res://addons/reactivex/operators/_skipuntil.gd[/b] +func skip_until(other : Observable) -> Observable: + return GDRx.op.skip_until(other).call(self) + +## See: [b]res://addons/reactivex/operators/_skipuntilwithtime.gd[/b] +func skip_until_with_time(start_time : float, time_absolute : bool = false, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.skip_until_with_time(start_time, time_absolute, scheduler).call(self) + +## See: [b]res://addons/reactivex/operators/_skipwhile.gd[/b] +func skip_while(predicate : Callable) -> Observable: + return GDRx.op.skip_while(predicate).call(self) + +## See: [b]res://addons/reactivex/operators/_skipwhile.gd[/b] +func skip_while_indexed(predicate : Callable) -> Observable: + return GDRx.op.skip_while_indexed(predicate).call(self) + +## See: [b]res://addons/reactivex/operators/_skipwithtime.gd[/b] +func skip_with_time(duration : float, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.skip_with_time(duration, scheduler).call(self) + +## See: [b]res://addons/reactivex/operators/_slice.gd[/b] +func slice(start : int = 0, stop : int = GDRx.util.MAX_SIZE, step : int = 1) -> Observable: + return GDRx.op.slice(start, stop, step).call(self) + +## See: [b]res://addons/reactivex/operators/_some.gd[/b] +func some(predicate = null) -> Observable: + return GDRx.op.some(predicate).call(self) + +## See: [b]res://addons/reactivex/operators/_startswith.gd[/b] +func start_with(args) -> Observable: + return GDRx.op.start_with(args).call(self) + +## See: [b]res://addons/reactivex/operators/_subscribeon.gd[/b] +func subscribe_on(scheduler : SchedulerBase) -> Observable: + return GDRx.op.subscribe_on(scheduler).call(self) + +## See: [b]res://addons/reactivex/operators/_sum.gd[/b] +func sum(key_mapper = null) -> Observable: + return GDRx.op.sum(key_mapper).call(self) + +## See: [b]res://addons/reactivex/operators/_switchlatest.gd[/b] +func switch_latest() -> Observable: + return GDRx.op.switch_latest().call(self) + +## See: [b]res://addons/reactivex/operators/_take.gd[/b] +func take(count_ : int) -> Observable: + return GDRx.op.take(count_).call(self) + +## See: [b]res://addons/reactivex/operators/_takelast.gd[/b] +func take_last(count_ : int) -> Observable: + return GDRx.op.take_last(count_).call(self) + +## See: [b]res://addons/reactivex/operators/_takelastbuffer.gd[/b] +func take_last_buffer(count_ : int) -> Observable: + return GDRx.op.take_last_buffer(count_).call(self) + +## See: [b]res://addons/reactivex/operators/_takelastwithtime.gd[/b] +func take_last_with_time(duration : float, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.take_last_with_time(duration, scheduler).call(self) + +## See: [b]res://addons/reactivex/operators/_takeuntil.gd[/b] +func take_until(other : Observable) -> Observable: + return GDRx.op.take_until(other).call(self) + +## See: [b]res://addons/reactivex/operators/_takeuntilwithtime.gd[/b] +func take_until_with_time(end_time : float, absolute : bool = false, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.take_until_with_time(end_time, absolute, scheduler).call(self) + +## See: [b]res://addons/reactivex/operators/_takewhile.gd[/b] +func take_while(predicate : Callable = GDRx.basic.default_condition, inclusive : bool = false) -> Observable: + return GDRx.op.take_while(predicate, inclusive).call(self) + +## See: [b]res://addons/reactivex/operators/_takewhile.gd[/b] +func take_while_indexed(predicate : Callable = GDRx.basic.default_condition, inclusive : bool = false) -> Observable: + return GDRx.op.take_while_indexed(predicate, inclusive).call(self) + +## See: [b]res://addons/reactivex/operators/_takewithtime.gd[/b] +func take_with_time(duration : float, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.take_with_time(duration, scheduler).call(self) + +## See: [b]res://addons/reactivex/operators/_throttlefirst.gd[/b] +func throttle_first(window_duration : float, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.throttle_first(window_duration, scheduler).call(self) + +## See: [b]res://addons/reactivex/operators/_timeinterval.gd[/b] +func time_interval(scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.time_interval(scheduler).call(self) + +## See: [b]res://addons/reactivex/operators/_timeout.gd[/b] +func timeout(duetime : float, absolute : bool = false, other : Observable = null, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.timeout(duetime, absolute, other, scheduler).call(self) + +## See: [b]res://addons/reactivex/operators/_timeoutwithmapper.gd[/b] +func timeout_with_mapper(first_timeout : Observable = null, timeout_duration_mapper : Callable = func(__) -> Observable: return GDRx.obs.never(), other : Observable = null) -> Observable: + return GDRx.op.timeout_with_mapper(first_timeout, timeout_duration_mapper, other).call(self) + +## See: [b]res://addons/reactivex/operators/_timestamp.gd[/b] +func timestamp(scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.timestamp(scheduler).call(self) + +## See: [b]res://addons/reactivex/operators/_todict.gd[/b] +func to_dict(key_mapper : Callable, element_mapper : Callable = GDRx.basic.identity) -> Observable: + return GDRx.op.to_dict(key_mapper, element_mapper).call(self) + +## See: [b]res://addons/reactivex/operators/_toiterable.gd[/b] +func to_iterable() -> Observable: + return GDRx.op.to_iterable().call(self) + +## See: [b]res://addons/reactivex/operators/_tolist.gd[/b] +func to_list() -> Observable: + return GDRx.op.to_list().call(self) + +## See: [b]res://addons/reactivex/operators/_tolist.gd[/b] +func to_array() -> Observable: + return GDRx.op.to_list().call(self) + +## See: [b]res://addons/reactivex/operators/_toset.gd[/b] +func to_set() -> Observable: + return GDRx.op.to_set().call(self) + +## See: [b]res://addons/reactivex/operators/_whiledo.gd[/b] +func while_do(condition : Callable = GDRx.basic.default_condition) -> Observable: + return GDRx.op.while_do(condition).call(self) + +## See: [b]res://addons/reactivex/operators/_window.gd[/b] +func window_toggle(openings : Observable, closing_mapper : Callable) -> Observable: + return GDRx.op.window_toggle(openings, closing_mapper).call(self) + +## See: [b]res://addons/reactivex/operators/_window.gd[/b] +func window(boundaries : Observable) -> Observable: + return GDRx.op.window(boundaries).call(self) + +## See: [b]res://addons/reactivex/operators/_window.gd[/b] +func window_when(closing_mapper : Callable) -> Observable: + return GDRx.op.window_when(closing_mapper).call(self) + +## See: [b]res://addons/reactivex/operators/_windowwithcount.gd[/b] +func window_with_count(count_ : int, skip_ = null) -> Observable: + return GDRx.op.window_with_count(count_, skip_).call(self) + +## See: [b]res://addons/reactivex/operators/_windowwithtime.gd[/b] +func window_with_time(timespan : float, timeshift = null, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.window_with_time(timespan, timeshift, scheduler).call(self) + +## See: [b]res://addons/reactivex/operators/_windowwithtimeorcount.gd[/b] +func window_with_time_or_count(timespan : float, count_ : int, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.window_with_time_or_count(timespan, count_, scheduler).call(self) + +## See: [b]res://addons/reactivex/operators/_withlatestfrom.gd[/b] +func with_latest_from(sources) -> Observable: + return GDRx.op.with_latest_from(sources).call(self) + +## See: [b]res://addons/reactivex/operators/_zip.gd[/b] +func zip(args) -> Observable: + return GDRx.op.zip(args).call(self) + +## See: [b]res://addons/reactivex/operators/_zip.gd[/b] +func zip_with_iterable(seq : IterableBase) -> Observable: + return GDRx.op.zip_with_iterable(seq).call(self) + +# =========================================================================== # +# Godot-specific Operators +# =========================================================================== # + +## See: [b]"res://addons/reactivex/engine/operators/_processtimeinterval.gd"[/b] +func process_time_interval(initial_time : float = 0.0) -> Observable: + return GDRx.gd.process_time_interval(initial_time).call(self) + +## See: [b]"res://addons/reactivex/engine/operators/_processtimeinterval.gd"[/b] +func physics_time_interval(initial_time : float = 0.0) -> Observable: + return GDRx.gd.physics_time_interval(initial_time).call(self) + +## Create a [ReadOnlyReactiveProperty] from the given sequence +func to_reactive_property(initial_value, distinct_until_changed : bool = true, raise_latest_value_on_subscribe = true) -> ReadOnlyReactiveProperty: + return ReadOnlyReactiveProperty.new(self, initial_value, distinct_until_changed, raise_latest_value_on_subscribe) diff --git a/addons/reactivex/observable/onerrorresumenext.gd b/addons/reactivex/observable/onerrorresumenext.gd new file mode 100644 index 0000000..64b122b --- /dev/null +++ b/addons/reactivex/observable/onerrorresumenext.gd @@ -0,0 +1,53 @@ +## Continues an observable sequence that is terminated normally or +## by an error with the next observable sequence. +## [br][br] +## [b]Examples:[/b] +## [codeblock] +## var res = GDRx.obs.on_error_resume_next([xs, ys, zs]) +## [/codeblock] +## [br][br] +## [b]Returns:[/b] +## [br] +## An observable sequence that concatenates the source sequences, +## even if a sequence terminates exceptionally. +static func on_error_resume_next_(sources) -> Observable: + + var sources_ : Iterator = GDRx.iter(sources) + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var scheduler_ : SchedulerBase = scheduler if scheduler != null else CurrentThreadScheduler.singleton() + + var subscription = SerialDisposable.new() + var cancelable = SerialDisposable.new() + + var action = func( + scheduler : SchedulerBase, + state = null, + action_ : Callable = func(__, ___, ____): return + ): + var source = sources_.next() + if source is ItEnd: + observer.on_completed() + return + + source = source.call() if source is Callable else source + var current : Observable = source + + var d = SingleAssignmentDisposable.new() + subscription.disposable = d + + var on_resume = func(state = null): + scheduler.schedule(action_.bind(action_), state) + + d.disposable = current.subscribe( + observer.on_next, on_resume, + on_resume, scheduler + ) + + cancelable.disposable = scheduler_.schedule(action.bind(action)) + return CompositeDisposable.new([subscription, cancelable]) + + return Observable.new(subscribe) diff --git a/addons/reactivex/observable/range.gd b/addons/reactivex/observable/range.gd new file mode 100644 index 0000000..708d469 --- /dev/null +++ b/addons/reactivex/observable/range.gd @@ -0,0 +1,77 @@ +const MAX_SIZE = 9223372036854775807 + +## Generates an observable sequence of integral numbers within a +## specified range, using the specified scheduler to send out observer +## messages. +## [br] +## [b]Examples:[/b] +## [codeblock] +## var res = GDRx.obs.range(10) +## var res = GDRx.obs.range(0, 10) +## var res = GDRx.obs.range(0, 10, 1) +## [/codeblock] +## [br] +## [b]Args:[/b] +## [br] +## [code]start[/code] The value of the first integer in the sequence. +## [br] +## [code]stop[/code] [Optional] Generate number up to (exclusive) the stop +## value. Default is `sys.maxsize`. +## [br] +## [code]step[/code] [Optional] The step to be used (default is 1). +## [br] +## [code]scheduler[/code] The scheduler to schedule the values on. +## [br][br] +## [b]Returns:[/b] +## [br] +## An observable sequence that contains a range of sequential +## integral numbers. +static func range_( + start : int, + stop = null, + step = null, + scheduler : SchedulerBase = null +) -> Observable: + + @warning_ignore("incompatible_ternary") + var _stop : int = MAX_SIZE if stop == null else stop + @warning_ignore("incompatible_ternary") + var _step : int = 1 if step == null else step + + var range_t : Array + if step == null and stop == null: + range_t = range(start) + elif step == null: + range_t = range(start, _stop) + else: + range_t = range(start, _stop, _step) + + var subscribe = func( + observer : ObserverBase, + scheduler_ : SchedulerBase = null + ) -> DisposableBase: + var _scheduler : SchedulerBase = null + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = CurrentThreadScheduler.singleton() + var sd = MultipleAssignmentDisposable.new() + + var action = func( + _scheduler : SchedulerBase, + iterator : Iterator, + action_ : Callable + ): + if GDRx.assert_(iterator != null): return + var item = iterator.next() + if item is ItEnd: + observer.on_completed() + else: + observer.on_next(item) + sd.disposable = _scheduler.schedule( + action_.bind(action_), iterator + ) + + sd.disposable = _scheduler.schedule(action.bind(action), GDRx.util.Iter(range_t).iter()) + return sd + + return Observable.new(subscribe) diff --git a/addons/reactivex/observable/repeat.gd b/addons/reactivex/observable/repeat.gd new file mode 100644 index 0000000..c1d2e7c --- /dev/null +++ b/addons/reactivex/observable/repeat.gd @@ -0,0 +1,27 @@ +## Generates an observable sequence that repeats the given element +## the specified number of times. +## [br] +## [b]Examples:[/b] +## [codeblock] +## 1 - var res = GDRx.obs.repeat_value(42) +## 2 - var res = GDRx.obs.repeat_value(42, 4) +## [/codeblock] +## [br] +## [b]Args:[/b] +## [br] +## [code]value[/code] Element to repeat. +## [br] +## [code]repeat_count[/code] [Optional] Number of times to repeat the element. +## If not specified, repeats indefinitely. +## [br][br] +## [b]Returns:[/b] +## [br] +## An observable sequence that repeats the given element the +## specified number of times. +static func repeat_value_(value, repeat_count = null) -> Observable: + + if repeat_count == -1: + repeat_count = null + + var xs : Observable = GDRx.obs.return_value(value) + return xs.pipe1(GDRx.op.repeat(repeat_count)) diff --git a/addons/reactivex/observable/returnvalue.gd b/addons/reactivex/observable/returnvalue.gd new file mode 100644 index 0000000..9597e0b --- /dev/null +++ b/addons/reactivex/observable/returnvalue.gd @@ -0,0 +1,54 @@ +## Returns an observable sequence that contains a single element, +## using the specified scheduler to send out observer messages. +## There is an alias called [method __GDRx_Singleton__.just]. +## [br] +## [b]Examples:[/b] +## [codeblock] +## var res = GDRx.obs.return_value(42) +## var res = GDRx.obs.return_value(42, GDRx.TimeoutScheduler_) +## var res = GDRx.just(42) +## [/codeblock] +## [br] +## [b]Args:[/b] +## [br] +## [code]value[/code] Single element in the resulting observable sequence. +## [br][br] +## [b]Returns:[/b] +## [br] +## An observable sequence containing the single specified +## element. +static func return_value_(value, scheduler : SchedulerBase = null) -> Observable: + var subscribe = func(observer : ObserverBase, scheduler_ : SchedulerBase = null) -> DisposableBase: + var _scheduler + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = CurrentThreadScheduler.singleton() + + var action = func(_scheduler : SchedulerBase, _state = null): + observer.on_next(value) + observer.on_completed() + + return _scheduler.schedule(action) + + return Observable.new(subscribe) + +static func from_callable_(supplier : Callable, scheduler : SchedulerBase = null) -> Observable: + var subscribe = func(observer : ObserverBase, scheduler_ : SchedulerBase = null) -> DisposableBase: + var _scheduler + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = CurrentThreadScheduler.singleton() + + var action = func(__ : SchedulerBase, ___ = null): + GDRx.try(func(): + observer.on_next(supplier.call()) + observer.on_completed() + ) \ + .catch("Error", func(e): + observer.on_error(e) + ) \ + .end_try_catch() + + return _scheduler.schedule(action) + + return Observable.new(subscribe) diff --git a/addons/reactivex/observable/throw.gd b/addons/reactivex/observable/throw.gd new file mode 100644 index 0000000..7b1fe3e --- /dev/null +++ b/addons/reactivex/observable/throw.gd @@ -0,0 +1,14 @@ +static func throw_(err, scheduler : SchedulerBase = null) -> Observable: + + var subscribe = func(observer : ObserverBase, scheduler_ : SchedulerBase = null) -> DisposableBase: + var _scheduler : SchedulerBase + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = ImmediateScheduler.singleton() + + var action = func(_scheduler : SchedulerBase, _state): + observer.on_error(err) + + return _scheduler.schedule(action) + + return Observable.new(subscribe) diff --git a/addons/reactivex/observable/timer.gd b/addons/reactivex/observable/timer.gd new file mode 100644 index 0000000..4d0cee9 --- /dev/null +++ b/addons/reactivex/observable/timer.gd @@ -0,0 +1,104 @@ +static func observable_timer_date(duetime : float, scheduler : SchedulerBase = null) -> Observable: + var subscribe = func(observer : ObserverBase, scheduler_ : SchedulerBase = null) -> DisposableBase: + var _scheduler : SchedulerBase = null + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = SceneTreeTimeoutScheduler.singleton() + + var action = func(_scheduler : SchedulerBase, _state): + observer.on_next(0) + observer.on_completed() + + return _scheduler.schedule_absolute(duetime, action) + + return Observable.new(subscribe) + +static func observable_timer_duetime_and_period(duetime : float, time_absolute : bool, period : float, scheduler : SchedulerBase = null) -> Observable: + var duetime_ref = RefValue.Set(duetime) + var subscribe = func(observer : ObserverBase, scheduler_ : SchedulerBase = null) -> DisposableBase: + var _scheduler : SchedulerBase = null + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = SceneTreeTimeoutScheduler.singleton() + + if not time_absolute: + duetime_ref.v = _scheduler.now() + duetime_ref.v + + var p = max(0.0, period) + var mad = MultipleAssignmentDisposable.new() + var dt = RefValue.Set(duetime_ref.v) + var count = RefValue.Set(0) + + var action = func(scheduler : SchedulerBase, _state, action_): + if p > 0.0: + var now = scheduler.now() + dt.v = dt.v + p + if dt.v <= now: + dt.v = now + p + + observer.on_next(count.v) + count.v += 1 + mad.disposable = scheduler.schedule_absolute(dt.v, action_.bind(action_)) + action = action.bind(action) + + mad.disposable = _scheduler.schedule_absolute(dt.v, action) + return mad + + return Observable.new(subscribe) + +static func observable_timer_timespan(duetime : float, scheduler : SchedulerBase = null) -> Observable: + var subscribe = func(observer : ObserverBase, scheduler_ : SchedulerBase = null) -> DisposableBase: + var _scheduler : SchedulerBase = null + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = SceneTreeTimeoutScheduler.singleton() + var d : float = duetime + + var action = func(_scheduler : SchedulerBase, _state): + observer.on_next(0) + observer.on_completed() + + if d <= 0.0: + return _scheduler.schedule(action) + return _scheduler.schedule_relative(d, action) + + return Observable.new(subscribe) + +static func observable_timer_timespan_and_period(duetime : float, period : float, scheduler : SchedulerBase = null) -> Observable: + if duetime == period: + + var subscribe = func(observer : ObserverBase, scheduler_ : SchedulerBase = null) -> DisposableBase: + var _scheduler : SchedulerBase = null + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = SceneTreeTimeoutScheduler.singleton() + + var action = func(count = null): + if count != null: + observer.on_next(count) + return count + 1 + return null + + if not _scheduler is PeriodicScheduler: + BadArgumentError.new( + "Scheduler must be PeriodicScheduler").throw() + return Disposable.new() + var periodic_scheduler : PeriodicScheduler = _scheduler + return periodic_scheduler.schedule_periodic(period, action, 0) + + return Observable.new(subscribe) + return observable_timer_duetime_and_period(duetime, false, period, scheduler) + +static func timer_(duetime : float, time_absolute : bool, period = null, scheduler : SchedulerBase = null) -> Observable: + if time_absolute: + if period == null: + return observable_timer_date(duetime, scheduler) + else: + var fperiod : float = period + return observable_timer_duetime_and_period(duetime, true, fperiod, scheduler) + + if period == null: + return observable_timer_timespan(duetime, scheduler) + + var fperiod : float = period + return observable_timer_timespan_and_period(duetime, fperiod, scheduler) diff --git a/addons/reactivex/observable/toasync.gd b/addons/reactivex/observable/toasync.gd new file mode 100644 index 0000000..9a2a9fa --- /dev/null +++ b/addons/reactivex/observable/toasync.gd @@ -0,0 +1,48 @@ +## Converts the function into an asynchronous function. Each +## invocation of the resulting asynchronous function causes an +## invocation of the original synchronous function on the specified +## scheduler. +## [br] +## [b]Examples:[/b] +## [codeblock] +## var res = GDRx.obs.to_async(func(x, y): return x + y).call(4, 3) +## var res = GDRx.obs.to_async(func(x, y): return x + y, GDRx.TimeoutScheduler_).call(4, 3) +## var res = GDRx.obs.to_async(func(x): print(x), GDRx.TimeoutScheduler_).call("hello") +## [/codeblock] +## [br] +## [b]Args:[/b] +## [br] +## [code]func[/code] Function to convert to an asynchronous function. +## [br] +## [code]scheduler[/code] [Optional] Scheduler to run the function on. If not +## specified, defaults to GDRx.TimeoutScheduler_. +## [br][br] +## [b]Returns:[/b] +## [br] +## Aynchronous function. +static func to_async_( + fun : Callable, scheduler : SchedulerBase = null +) -> Callable: + + var _scheduler = scheduler if scheduler != null else SceneTreeTimeoutScheduler.singleton() + + var wrapper = func(args : Array) -> Observable: + var subject = AsyncSubject.new() + + var action = func(_scheduler : SchedulerBase, _state = null): + var result = RefValue.Null() + if GDRx.try(func(): + result.v = fun.call(args) + ) \ + .catch("Error", func(err): + subject.on_error(err) + ) \ + .end_try_catch(): return + + subject.on_next(result.v) + subject.on_completed() + + _scheduler.schedule(action) + return subject.as_observable() + + return wrapper diff --git a/addons/reactivex/observable/using.gd b/addons/reactivex/observable/using.gd new file mode 100644 index 0000000..7a0f506 --- /dev/null +++ b/addons/reactivex/observable/using.gd @@ -0,0 +1,53 @@ +## Constructs an observable sequence that depends on a resource +## object, whose lifetime is tied to the resulting observable +## sequence's lifetime. +## [br] +## [b]Example:[/b] +## [codeblock] +## var res = GDRx.obs.using(func(): return AsyncSubject.new(), func(s): return s) +## [/codeblock] +## [br] +## [b]Args:[/b] +## [br] +## [code]resource_factory[/code] Factory function to obtain a resource object. +## [br] +## [code]observable_factory[/code] Factory function to obtain an observable +## sequence that depends on the obtained resource. +## [br][br] +## +## [b]Returns:[/b] +## [br] +## An observable sequence whose lifetime controls the lifetime +## of the dependent resource object. +static func using_( + resource_factory : Callable, + observable_factory : Callable, +) -> Observable: + + var subscribe = func( + observer : ObservableBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var disp = RefValue.Set(Disposable.new()) + + var d = RefValue.Null() + var source = RefValue.Null() + if GDRx.try(func(): + var resource = resource_factory.call() + if resource is DisposableBase: + disp.v = resource + + source.v = observable_factory.call(resource) + ) \ + .catch("Error", func(err): + d.v = GDRx.obs.throw(err).subscribe(observer, GDRx.basic.noop, GDRx.basic.noop, scheduler) + ) \ + .end_try_catch(): + return CompositeDisposable.new([d.v, disp.v]) + + return CompositeDisposable.new([ + source.v.subscribe(observer, GDRx.basic.noop, GDRx.basic.noop, scheduler), + disp.v + ]) + + return Observable.new(subscribe) diff --git a/addons/reactivex/observable/withlatestfrom.gd b/addons/reactivex/observable/withlatestfrom.gd new file mode 100644 index 0000000..d587fc1 --- /dev/null +++ b/addons/reactivex/observable/withlatestfrom.gd @@ -0,0 +1,60 @@ +static func with_latest_from_( + parent : Observable, sources_ +) -> Observable: + var sources : Array[Observable] = GDRx.util.unpack_arg(sources_) + var NO_VALUE = GDRx.util.NotSet.new() + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + + var subscribeall = func( + parent : Observable, children : Array[Observable] + ) -> Array[SingleAssignmentDisposable]: + + var values = [] ; for __ in children: values.append(NO_VALUE) + + var subscribechild = func( + i : int, child : Observable + ) -> SingleAssignmentDisposable: + var subscription = SingleAssignmentDisposable.new() + + var on_next = func(value): + var __ = LockGuard.new(parent.lock) + values[i] = value + + subscription.disposable = child.subscribe( + on_next, observer.on_error, func(): return, scheduler + ) + return subscription + + var parent_subscription = SingleAssignmentDisposable.new() + + var on_next = func(value): + var __ = LockGuard.new(parent.lock) + if NO_VALUE not in values: + var res_ = values.duplicate() + res_.push_front(value) + var result = Tuple.new(res_) + observer.on_next(result) + + var children_subscription = [] + for i in range(children.size()): + var child = children[i] + children_subscription.append(subscribechild.call(i, child)) + var disp = parent.subscribe( + on_next, observer.on_error, + observer.on_completed, scheduler + ) + parent_subscription.disposable = disp + + var ret : Array[SingleAssignmentDisposable] = [] + ret.assign(children_subscription.duplicate()) + ret.push_front(parent_subscription) + return ret + + return CompositeDisposable.new(subscribeall.call( + parent, sources)) + + return Observable.new(subscribe) diff --git a/addons/reactivex/observable/zip.gd b/addons/reactivex/observable/zip.gd new file mode 100644 index 0000000..b06e7a8 --- /dev/null +++ b/addons/reactivex/observable/zip.gd @@ -0,0 +1,78 @@ +## Merges the specified observable sequences into one observable +## sequence by creating a tuple whenever all of the +## observable sequences have produced an element at a corresponding +## index. +## [br] +## [b]Example:[/b] +## [codeblock] +## var res = GDRx.obs.zip([obs1, obs2]) +## [/codeblock] +## [br] +## [b]Args:[/b] +## [br] +## [code]args[/code] Observable sources to zip. +## [br][br] +## [b]Returns:[/b] +## [br] +## An observable sequence containing the result of combining +## elements of the sources as tuple. +static func zip_(sources_) -> Observable: + var sources : Array[Observable] = GDRx.util.unpack_arg(sources_) + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> CompositeDisposable: + var n = sources.size() + var queues = [] ; for __ in range(n): queues.append([]) + var lock = RLock.new() + var is_completed = [] ; for __ in range(n): is_completed.append(false) + + var next_ = func(__ : int): + var _guard = LockGuard.new(lock) + if queues.all(func(x): return x.size() > 0): + var res = RefValue.Null() + if GDRx.try(func(): + var queued_values = [] ; for x in queues: queued_values.append(x.pop_front()) + res.v = Tuple.new(queued_values) + ) \ + .catch("Error", func(err): + observer.on_error(err) + ) \ + .end_try_catch(): return + + observer.on_next(res.v) + + for idx in range(n): + var queue = queues[idx] + var done = is_completed[idx] + if done and queue.size() == 0: + observer.on_completed() + break + + var completed = func(i : int): + is_completed[i] = true + if queues[i].size() == 0: + observer.on_completed() + + var subscriptions : Array = [] ; for __ in range(n): subscriptions.append(null) + + var fun = func(i : int): + var source : Observable = sources[i] + + var sad = SingleAssignmentDisposable.new() + + var on_next = func(x): + queues[i].append(x) + next_.call(i) + + sad.disposable = source.subscribe( + on_next, observer.on_error, func(): completed.call(i), scheduler + ) + subscriptions[i] = sad + + for idx in range(n): + fun.call(idx) + return CompositeDisposable.new(subscriptions) + + return Observable.new(subscribe) diff --git a/addons/reactivex/observer/autodetachobserver.gd b/addons/reactivex/observer/autodetachobserver.gd new file mode 100644 index 0000000..8f089c7 --- /dev/null +++ b/addons/reactivex/observer/autodetachobserver.gd @@ -0,0 +1,72 @@ +extends ObserverBase +class_name AutoDetachObserver + +var _on_next : Callable +var _on_error : Callable +var _on_completed : Callable + +var _subscription : SingleAssignmentDisposable + +var is_stopped : bool + +var this : AutoDetachObserver + +func _init( + on_next_ : Callable = GDRx.basic.noop, + on_error_ : Callable = GDRx.basic.noop, + on_completed_ : Callable = GDRx.basic.noop): + this = self + this.unreference() + + self._on_next = on_next_ + self._on_error = on_error_ + self._on_completed = on_completed_ + + self._subscription = SingleAssignmentDisposable.new() + self.is_stopped = false + +func on_next(i): + if self.is_stopped: + return + self._on_next.call(i) + +func on_error(e): + if self.is_stopped: + return + self.is_stopped = true + + GDRx.try(func(): + self._on_error.call(e) + ).end_try_catch() + self.dispose() + +func on_completed(): + if self.is_stopped: + return + self.is_stopped = true + + GDRx.try(func(): + self._on_completed.call() + ).end_try_catch() + self.dispose() + +func set_disposable(value : DisposableBase): + self._subscription.disposable = value + +var subscription: set = set_disposable + +func dispose(): + this.is_stopped = true + this._subscription.dispose() + +func fail(err) -> bool: + if self.is_stopped: + return false + + self.is_stopped = true + self._on_error.call(err) + return true + +func _notification(what): + if what == NOTIFICATION_PREDELETE: + this.dispose() diff --git a/addons/reactivex/observer/observeonobserver.gd b/addons/reactivex/observer/observeonobserver.gd new file mode 100644 index 0000000..a5e4f60 --- /dev/null +++ b/addons/reactivex/observer/observeonobserver.gd @@ -0,0 +1,14 @@ +extends ScheduledObserver +class_name ObserveOnObserver + +func _on_next_core(i): + super._on_next_core(i) + self.ensure_active() + +func _on_error_core(e): + super._on_error_core(e) + self.ensure_active() + +func _on_completed_core(): + super._on_completed_core() + self.ensure_active() diff --git a/addons/reactivex/observer/observer.gd b/addons/reactivex/observer/observer.gd new file mode 100644 index 0000000..8115669 --- /dev/null +++ b/addons/reactivex/observer/observer.gd @@ -0,0 +1,69 @@ +extends ObserverBase +class_name Observer + +var _handler_on_next : Callable +var _handler_on_error : Callable +var _handler_on_completed : Callable +var is_stopped : bool + +var this : Observer + +func _init( + on_next_ : Callable = GDRx.basic.noop, + on_error_ : Callable = GDRx.basic.noop, + on_completed_ : Callable = GDRx.basic.noop): + this = self + this.unreference() + + self.is_stopped = false + self._handler_on_next = on_next_ + self._handler_on_error = on_error_ + self._handler_on_completed = on_completed_ + +func on_next(i): + if not self.is_stopped: + self._on_next_core(i) + +func _on_next_core(i): + self._handler_on_next.call(i) + +func on_error(e): + if not self.is_stopped: + self.is_stopped = true + self._on_error_core(e) + +func _on_error_core(e): + self._handler_on_error.call(e) + +func on_completed(): + if not self.is_stopped: + self.is_stopped = true + self._on_completed_core() + +func _on_completed_core(): + self._handler_on_completed.call() + +func dispose(): + this.is_stopped = true + +func fail(e): + if not self.is_stopped: + self.is_stopped = true + self._on_error_core(e) + return true + return false + +func throw(error : ThrowableBase): + print_stack() + GDRx.raise(error) + +func to_notifier() -> Callable: + return func(notifier : Notification): + return notifier.accept(self) + +func as_observer() -> ObserverBase: + return Observer.new(self.on_next, self.on_error, self.on_completed) + +func _notification(what): + if what == NOTIFICATION_PREDELETE: + this.dispose() diff --git a/addons/reactivex/observer/scheduledobserver.gd b/addons/reactivex/observer/scheduledobserver.gd new file mode 100644 index 0000000..9babe00 --- /dev/null +++ b/addons/reactivex/observer/scheduledobserver.gd @@ -0,0 +1,76 @@ +extends Observer +class_name ScheduledObserver + +var scheduler : SchedulerBase +var observer : ObserverBase +var lock : RLock +var is_acquired : bool +var has_faulted : bool +var queue : Array[Callable] +var disposable : SerialDisposable + +func _init(scheduler_ : SchedulerBase, observer_ : ObserverBase): + super._init() + + self.scheduler = scheduler_ + self.observer = observer_ + + self.lock = RLock.new() + self.is_acquired = false + self.has_faulted = false + self.queue = [] + self.disposable = SerialDisposable.new() + +func _on_next_core(i): + var action : Callable = func(): + self.observer.on_next(i) + self.queue.append(action) + +func _on_error_core(e): + var action : Callable = func(): + self.observer.on_error(e) + self.queue.append(action) + +func _on_completed_core(): + var action : Callable = func(): + self.observer.on_completed() + self.queue.append(action) + +func ensure_active(): + var is_owner = false + + if true: + var __ = LockGuard.new(self.lock) + if not self.has_faulted and not self.queue.is_empty(): + is_owner = not self.is_acquired + self.is_acquired = true + + if is_owner: + self.disposable.disposable = self.scheduler.schedule(self.run) + +func run(_scheduler : SchedulerBase, _state = null): + var parent = self + + var work + if true: + var __ = LockGuard.new(self.lock) + if not parent.queue.is_empty(): + work = parent.queue.pop_at(0) + else: + parent.is_acquired = false + return + + GDRx.try(work) \ + .catch("Error", func(e): + if true: + var __ = LockGuard.new(self.lock) + parent.queue = [] + parent.has_faulted = true + GDRx.raise(e)) \ + .end_try_catch() + + self.scheduler.schedule(self.run) + +func dispose(): + super.dispose() + this.disposable.dispose() diff --git a/addons/reactivex/operators/_all.gd b/addons/reactivex/operators/_all.gd new file mode 100644 index 0000000..9afa257 --- /dev/null +++ b/addons/reactivex/operators/_all.gd @@ -0,0 +1,12 @@ +static func all_(predicate : Callable) -> Callable: + var filter = func(v): + return not predicate.call(v) + + var mapping = func(b : bool) -> bool: + return not b + + return GDRx.pipe.compose3( + GDRx.op.filter(filter), + GDRx.op.some(), + GDRx.op.map(mapping) + ) diff --git a/addons/reactivex/operators/_amb.gd b/addons/reactivex/operators/_amb.gd new file mode 100644 index 0000000..1b0e119 --- /dev/null +++ b/addons/reactivex/operators/_amb.gd @@ -0,0 +1,82 @@ +static func amb_(right_source : Observable) -> Callable: + var obs : Observable = right_source + + var amb = func(left_source : Observable) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var choice = [null] + var left_choice = "L" + var right_choice = "R" + var left_subscription : SingleAssignmentDisposable = SingleAssignmentDisposable.new() + var right_subscription : SingleAssignmentDisposable = SingleAssignmentDisposable.new() + + var choice_left = func(): + if choice[0] == null: + choice[0] = left_choice + right_subscription.dispose() + + var choice_right = func(): + if choice[0] == null: + choice[0] = right_choice + left_subscription.dispose() + + var on_next_left = func(value): + if true: + var __ = LockGuard.new(left_source.lock) + choice_left.call() + if choice[0] == left_choice: + observer.on_next(value) + + var on_error_left = func(err): + if true: + var __ = LockGuard.new(left_source.lock) + choice_left.call() + if choice[0] == left_choice: + observer.on_error(err) + + var on_completed_left = func(): + if true: + var __ = LockGuard.new(left_source.lock) + choice_left.call() + if choice[0] == left_choice: + observer.on_completed() + + var left_d = left_source.subscribe( + on_next_left, on_error_left, on_completed_left, + scheduler + ) + left_subscription.disposable = left_d + + var on_next_right = func(value): + if true: + var __ = LockGuard.new(left_source.lock) + choice_right.call() + if choice[0] == right_choice: + observer.on_next(value) + + var on_error_right = func(err): + if true: + var __ = LockGuard.new(left_source.lock) + choice_right.call() + if choice[0] == right_choice: + observer.on_error(err) + + var on_completed_right = func(): + if true: + var __ = LockGuard.new(left_source.lock) + choice_right.call() + if choice[0] == right_choice: + observer.on_completed() + + var right_d = obs.subscribe( + on_next_right, on_error_right, on_completed_right, + scheduler + ) + right_subscription.disposable = right_d + return CompositeDisposable.new([left_subscription, right_subscription]) + + return Observable.new(subscribe) + + return amb diff --git a/addons/reactivex/operators/_asobservable.gd b/addons/reactivex/operators/_asobservable.gd new file mode 100644 index 0000000..96c7846 --- /dev/null +++ b/addons/reactivex/operators/_asobservable.gd @@ -0,0 +1,20 @@ +static func as_observable_() -> Callable: + var as_observable = func(source : Observable) -> Observable: +# """Hides the identity of an observable sequence. +# +# Args: +# source: Observable source to hide identity from. +# +# Returns: +# An observable sequence that hides the identity of the +# source sequence. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + return source.subscribe(observer, GDRx.basic.noop, GDRx.basic.noop, scheduler) + + return Observable.new(subscribe) + + return as_observable diff --git a/addons/reactivex/operators/_average.gd b/addons/reactivex/operators/_average.gd new file mode 100644 index 0000000..14f8a8e --- /dev/null +++ b/addons/reactivex/operators/_average.gd @@ -0,0 +1,51 @@ +class AverageValue: + var sum : float + var count : int + func _init(sum_, count_): + self.sum = sum_ + self.count = count_ + +static func average_( + key_mapper = null +) -> Callable: + var average = func(source : Observable) -> Observable: +# """Partially applied average operator. +# +# Computes the average of an observable sequence of values that +# are in the sequence or obtained by invoking a transform +# function on each element of the input sequence if present. +# +# Examples: +# >>> var res = average.call(source) +# +# Args: +# source: Source observable to average. +# +# Returns: +# An observable sequence containing a single element with the +# average of the sequence of values. +# """ + @warning_ignore("incompatible_ternary") + var key_mapper_ : Callable = key_mapper if key_mapper != null else func(x): return float(x) + + var accumulator = func(prev : AverageValue, curr : float) -> AverageValue: + return AverageValue.new(prev.sum + curr, prev.count + 1) + + var mapper = func(s : AverageValue) -> float: + if s.count == 0: + BadMappingError.new("Input Sequence was empty!").throw() + return NAN + + return s.sum / float(s.count) + + var seed_ = AverageValue.new(0, 0) + + var ret = source.pipe4( + GDRx.op.map(key_mapper_), + GDRx.op.scan(accumulator, seed_), + GDRx.op.last(), + GDRx.op.map(mapper) + ) + return ret + + return average diff --git a/addons/reactivex/operators/_buffer.gd b/addons/reactivex/operators/_buffer.gd new file mode 100644 index 0000000..5c3794a --- /dev/null +++ b/addons/reactivex/operators/_buffer.gd @@ -0,0 +1,56 @@ +static func buffer_(boundaries : Observable) -> Callable: + return GDRx.pipe.compose2( + GDRx.op.window(boundaries), + GDRx.op.flat_map(GDRx.op.to_list()), + ) + +static func buffer_when_(closing_mapper : Callable) -> Callable: + return GDRx.pipe.compose2( + GDRx.op.window_when(closing_mapper), + GDRx.op.flat_map(GDRx.op.to_list()), + ) + +static func buffer_toggle_(openings : Observable, closing_mapper : Callable) -> Callable: + return GDRx.pipe.compose2( + GDRx.op.window_toggle(openings, closing_mapper), + GDRx.op.flat_map(GDRx.op.to_list()), + ) + +static func buffer_with_count_( + count : int, skip = null +) -> Callable: + var _skip = RefValue.Set(skip) +# """Projects each element of an observable sequence into zero or more +# buffers which are produced based on element count information. +# +# Examples: +# >>> var res = GDRx.op.buffer_with_count(10).call(xs) +# >>> var res = GDRx.op.buffer_with_count(10, 1).call(xs) +# +# Args: +# count: Length of each buffer. +# skip: [Optional] Number of elements to skip between +# creation of consecutive buffers. If not provided, defaults to +# the count. +# +# Returns: +# A function that takes an observable source and returns an +# observable sequence of buffers. +# """ + var buffer_with_count = func(source : Observable) -> Observable: + if _skip.v == null: + _skip.v = count + + var mapper = func(value : Observable) -> Observable: + return value.pipe1(GDRx.op.to_list()) + + var predicate = func(value : Array) -> bool: + return value.size() > 0 + + return source.pipe3( + GDRx.op.window_with_count(count, _skip.v), + GDRx.op.flat_map(mapper), + GDRx.op.filter(predicate) + ) + + return buffer_with_count diff --git a/addons/reactivex/operators/_bufferwithtime.gd b/addons/reactivex/operators/_bufferwithtime.gd new file mode 100644 index 0000000..aebac40 --- /dev/null +++ b/addons/reactivex/operators/_bufferwithtime.gd @@ -0,0 +1,12 @@ +static func buffer_with_time_( + timespan : float, + timeshift = null, + scheduler : SchedulerBase = null +) -> Callable: + if timeshift == null: + timeshift = timespan + + return GDRx.pipe.compose2( + GDRx.op.window_with_time(timespan, timeshift, scheduler), + GDRx.op.flat_map(GDRx.op.to_list()) + ) diff --git a/addons/reactivex/operators/_bufferwithtimeorcount.gd b/addons/reactivex/operators/_bufferwithtimeorcount.gd new file mode 100644 index 0000000..ff62179 --- /dev/null +++ b/addons/reactivex/operators/_bufferwithtimeorcount.gd @@ -0,0 +1,9 @@ +static func buffer_with_time_or_count_( + timespan : float, + count : int, + scheduler : SchedulerBase = null +) -> Callable: + return GDRx.pipe.compose2( + GDRx.op.window_with_time_or_count(timespan, count, scheduler), + GDRx.op.flat_map(GDRx.op.to_iterable()), + ) diff --git a/addons/reactivex/operators/_catch.gd b/addons/reactivex/operators/_catch.gd new file mode 100644 index 0000000..14a5911 --- /dev/null +++ b/addons/reactivex/operators/_catch.gd @@ -0,0 +1,64 @@ +static func catch_handler( + source : Observable, + handler : Callable +) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var d1 = SingleAssignmentDisposable.new() + var subscription = SerialDisposable.new() + + subscription.disposable = d1 + + var on_error = func(err): + var result = RefValue.Null() + if GDRx.try(func(): + result.v = handler.call(err, source) + ) \ + .catch("Error", func(e): + observer.on_error(e) + ) \ + .end_try_catch(): return + + var d = SingleAssignmentDisposable.new() + subscription.disposable = d + d.disposable = result.v.subscribe( + observer, GDRx.basic.noop, GDRx.basic.noop, scheduler + ) + + d1.disposable = source.subscribe( + observer.on_next, on_error, observer.on_completed, + scheduler + ) + return subscription + + return Observable.new(subscribe) + +static func catch_(handler) -> Callable: + var catch = func(source : Observable) -> Observable: +# """Continues an observable sequence that is terminated by an +# error with the next observable sequence. +# +# Examples: +# >>> var op = catch.call(ys) +# >>> var op = catch.call(func(ex, src): return ys(ex)) +# +# Args: +# handler: Second observable sequence used to produce +# results when an error occurred in the first sequence, or an +# error handler function that returns an observable sequence +# given the error and source observable that occurred in the +# first sequence. +# +# Returns: +# An observable sequence containing the first sequence's +# elements, followed by the elements of the handler sequence +# in case an error occurred. +# """ + if handler is Callable: + return catch_handler(source, handler) + else: + return GDRx.obs.catch_with_iterable(GDRx.util.Iter([source, handler])) + + return catch diff --git a/addons/reactivex/operators/_combinelatest.gd b/addons/reactivex/operators/_combinelatest.gd new file mode 100644 index 0000000..9497cd0 --- /dev/null +++ b/addons/reactivex/operators/_combinelatest.gd @@ -0,0 +1,21 @@ +static func combine_latest_(others_) -> Callable: + var others : Array[Observable] = GDRx.util.unpack_arg(others_) + + var combine_latest = func(source : Observable) -> Observable: +# """Merges the specified observable sequences into one +# observable sequence by creating a tuple whenever any +# of the observable sequences produces an element. +# +# Examples: +# >>> var obs = combine_latest.call(source) +# +# Returns: +# An observable sequence containing the result of combining +# elements of the sources into a tuple. +# """ + + var sources : Array[Observable] = others.duplicate() + sources.push_front(source) + return GDRx.obs.combine_latest(sources) + + return combine_latest diff --git a/addons/reactivex/operators/_concat.gd b/addons/reactivex/operators/_concat.gd new file mode 100644 index 0000000..ac2a202 --- /dev/null +++ b/addons/reactivex/operators/_concat.gd @@ -0,0 +1,18 @@ +static func concat_(sources_) -> Callable: + var sources : Array[Observable] = GDRx.util.unpack_arg(sources_) + var concat = func(source : Observable) -> Observable: +# """Concatenates all the observable sequences. +# +# Examples: +# >>> var op = concat.call([xs, ys, zs]) +# +# Returns: +# An operator function that takes one or more observable sources and +# returns an observable sequence that contains the elements of +# each given sequence, in sequential order. +# """ + var _sources : Array[Observable] = sources.duplicate() + _sources.push_front(source) + return GDRx.obs.concat_with_iterable(GDRx.to_iterable(_sources)) + + return concat diff --git a/addons/reactivex/operators/_contains.gd b/addons/reactivex/operators/_contains.gd new file mode 100644 index 0000000..b1d6b6b --- /dev/null +++ b/addons/reactivex/operators/_contains.gd @@ -0,0 +1,12 @@ +static func contains_( + value, + comparer = GDRx.basic.default_comparer +) -> Callable: + + var predicate = func(v) -> bool: + return comparer.call(v, value) + + return GDRx.pipe.compose2( + GDRx.op.filter(predicate), + GDRx.op.some() + ) diff --git a/addons/reactivex/operators/_count.gd b/addons/reactivex/operators/_count.gd new file mode 100644 index 0000000..b632985 --- /dev/null +++ b/addons/reactivex/operators/_count.gd @@ -0,0 +1,16 @@ +static func count_( + predicate = null +) -> Callable: + + if predicate != null: + var predicate_ : Callable = predicate + return GDRx.pipe.compose2( + GDRx.op.filter(predicate_), + GDRx.op.count() + ) + + var reducer = func(n, __) -> int: + return n + 1 + + var counter = GDRx.op.reduce(reducer, 0) + return counter diff --git a/addons/reactivex/operators/_debounce.gd b/addons/reactivex/operators/_debounce.gd new file mode 100644 index 0000000..af5bdb2 --- /dev/null +++ b/addons/reactivex/operators/_debounce.gd @@ -0,0 +1,156 @@ +static func debounce_( + duetime : float, + scheduler : SchedulerBase = null +) -> Callable: + var debounce = func(source : Observable) -> Observable: +# """Ignores values from an observable sequence which are followed by +# another value before duetime. +# +# Example: +# >>> var res = debounce.call(source) +# +# Args: +# source: Source observable to debounce. +# +# Returns: +# An operator function that takes the source observable and +# returns the debounced observable sequence. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler_ : SchedulerBase = null + ) -> DisposableBase: + var _scheduler : SchedulerBase + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = SceneTreeTimeoutScheduler.singleton() + var cancelable = SerialDisposable.new() + var has_value = [false] + var value : Array = [null] + var _id : Array[int] = [0] + + var on_next = func(x): + has_value[0] = true + value[0] = x + _id[0] += 1 + var current_id = _id[0] + var d = SingleAssignmentDisposable.new() + cancelable.disposable = d + + var action = func(_scheduler : SchedulerBase, _state = null): + if has_value[0] and _id[0] == current_id: + observer.on_next(value[0]) + has_value[0] = false + + d.disposable = _scheduler.schedule_relative(duetime, action) + + var on_error = func(error): + cancelable.dispose() + observer.on_error(error) + has_value[0] = false + _id[0] += 1 + + var on_completed = func(): + cancelable.dispose() + if has_value[0]: + observer.on_next(value[0]) + + observer.on_completed() + has_value[0] = false + _id[0] += 1 + + var subscription = source.subscribe( + on_next, on_error, on_completed, + scheduler_ + ) + return CompositeDisposable.new([subscription, cancelable]) + + return Observable.new(subscribe) + + return debounce + +func throttle_with_mapper_( + throttle_duration_mapper : Callable +) -> Callable: + var throttle_with_mapper = func(source : Observable) -> Observable: +# """Partially applied throttle_with_mapper operator. +# +# Ignores values from an observable sequence which are followed by +# another value within a computed throttle duration. +# +# Example: +# >>> var obs = throttle_with_mapper.call(source) +# +# Args: +# source: The observable source to throttle. +# +# Returns: +# The throttled observable sequence. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var cancelable = SerialDisposable.new() + var has_value : RefValue = RefValue.Set(false) + var value : RefValue = RefValue.Null() + var _id = [0] + + var on_next = func(x): + var throttle = RefValue.Null() + if GDRx.try(func(): + throttle.v = throttle_duration_mapper.call(x) + ) \ + .catch("Error", func(e): + observer.on_error(e) + ) \ + .end_try_catch(): return + + has_value.v = true + value.v = x + _id[0] += 1 + var current_id = _id[0] + var d = SingleAssignmentDisposable.new() + cancelable.disposable = d + + var on_next = func(__): + if has_value.v and _id[0] == current_id: + observer.on_next(value.v) + has_value.v = false + d.dispose() + + var on_completed = func(): + if has_value.v and _id[0] == current_id: + observer.on_next(value.v) + has_value.v = false + d.dispose() + + d.disposable = throttle.v.subscribe( + on_next, observer.on_error, on_completed, + scheduler + ) + + var on_error = func(e): + cancelable.dispose() + observer.on_error(e) + has_value.v = false + _id[0] += 1 + + var on_completed = func(): + cancelable.dispose() + if has_value.v: + observer.on_next(value.v) + + observer.on_completed() + has_value.v = false + _id[0] += 1 + + var subscription = source.subscribe( + on_next, on_error, on_completed. + scheduler + ) + return CompositeDisposable.new([subscription, cancelable]) + + return Observable.new(subscribe) + + return throttle_with_mapper diff --git a/addons/reactivex/operators/_defaultifempty.gd b/addons/reactivex/operators/_defaultifempty.gd new file mode 100644 index 0000000..cecd0d1 --- /dev/null +++ b/addons/reactivex/operators/_defaultifempty.gd @@ -0,0 +1,42 @@ +static func default_if_empty_( + default_value = null +) -> Callable: + var default_if_empty = func(source : Observable) -> Observable: +# """Returns the elements of the specified sequence or the +# specified value in a singleton sequence if the sequence is +# empty. +# +# Examples: +# >>> var obs = default_if_empty.call(source) +# +# Args: +# source: Source observable. +# +# Returns: +# An observable sequence that contains the specified default +# value if the source is empty otherwise, the elements of the +# source. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var found = [false] + + var on_next = func(x): + found[0] = true + observer.on_next(x) + + var on_completed = func(): + if not found[0]: + observer.on_next(default_value) + observer.on_completed() + + return source.subscribe( + on_next, observer.on_error, on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return default_if_empty diff --git a/addons/reactivex/operators/_delay.gd b/addons/reactivex/operators/_delay.gd new file mode 100644 index 0000000..8bc248b --- /dev/null +++ b/addons/reactivex/operators/_delay.gd @@ -0,0 +1,123 @@ +static func observable_delay_timespan( + source : Observable, + duetime : float, + scheduler : SchedulerBase = null +) -> Observable: + var _duetime : RefValue = RefValue.Set(duetime) + + var subscribe = func( + observer : ObserverBase, + scheduler_ : SchedulerBase = null + ) -> DisposableBase: + var _scheduler : SchedulerBase + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = SceneTreeTimeoutScheduler.singleton() + + var duetime_ = _duetime.v + + var cancelable = SerialDisposable.new() + var error : RefValue = RefValue.Null() + var active = [false] + var running = [false] + var queue : Array[Tuple] = [] + + @warning_ignore("shadowed_variable") + var on_next = func(notification : Tuple): + var should_run = false + + if true: + var __ = LockGuard.new(source.lock) + if notification.at(0) is OnErrorNotification: + queue.clear() + queue.append(notification) + error.v = notification.at(0).err + should_run = not running[0] + else: + queue.append( + Tuple.new([ + notification.at(0), + notification.at(1) + duetime_ + ]) + ) + should_run = not active[0] + active[0] = true + + if should_run: + if error.v != null: + observer.on_error(error.v) + else: + var mad = MultipleAssignmentDisposable.new() + cancelable.disposable = mad + + var action = func(scheduler : SchedulerBase, _state = null, __action_rec : Callable = func(__, ___, ____): return null): + if error.v != null: + return + + var err + var should_continue : bool + var recurse_duetime : float + if true: + var __ = LockGuard.new(source.lock) + running[0] = true + while true: + var result : Notification = null + if not queue.is_empty() and queue[0].at(1) <= scheduler.now(): + result = queue.pop_front().at(0) + + if result != null: + result.accept(observer) + + if result == null: + break + + should_continue = false + recurse_duetime = 0 + if not queue.is_empty(): + should_continue = true + var diff = queue[0].at(1) - scheduler.now() + recurse_duetime = max(0, diff) + else: + active[0] = false + + err = error.v + running[0] = false + + if err != null: + observer.on_error(err) + elif should_continue: + mad.disposable = scheduler.schedule_relative( + recurse_duetime, __action_rec.bind(__action_rec) + ) + + mad.disposable = _scheduler.schedule_relative(duetime_, action.bind(action)) + + var subscription = source.pipe2( + GDRx.op.materialize(), + GDRx.op.timestamp() + ).subscribe(on_next, GDRx.basic.noop, GDRx.basic.noop, _scheduler) + + return CompositeDisposable.new([subscription, cancelable]) + + return Observable.new(subscribe) + +static func delay_( + duetime : float, scheduler : SchedulerBase = null +) -> Callable: + var delay = func(source : Observable) -> Observable: +# """Time shifts the observable sequence. +# +# A partially applied delay operator function. +# +# Examples: +# >>> var res = delay.call(source) +# +# Args: +# source: The observable sequence to delay. +# +# Returns: +# A time-shifted observable sequence. +# """ + return observable_delay_timespan(source, duetime, scheduler) + + return delay diff --git a/addons/reactivex/operators/_delaysubscription.gd b/addons/reactivex/operators/_delaysubscription.gd new file mode 100644 index 0000000..3833520 --- /dev/null +++ b/addons/reactivex/operators/_delaysubscription.gd @@ -0,0 +1,25 @@ +static func delay_subscription_( + duetime : float, + time_absolute : bool = false, + scheduler : SchedulerBase = null +) -> Callable: + var delay_subscription = func(source : Observable) -> Observable: +# """Time shifts the observable sequence by delaying the subscription. +# +# Examples. +# >>> var res = source.pipe1(GDRx.op.delay_subscription(5)) +# +# Args: +# source: Source subscription to delay. +# +# Returns: +# Time-shifted sequence. +# """ + var mapper = func(__) -> Observable: + return GDRx.obs.empty() + + return source.pipe1( + GDRx.op.delay_with_mapper(GDRx.obs.timer(duetime, time_absolute, null, scheduler), mapper) + ) + + return delay_subscription diff --git a/addons/reactivex/operators/_delaywithmapper.gd b/addons/reactivex/operators/_delaywithmapper.gd new file mode 100644 index 0000000..83c9e85 --- /dev/null +++ b/addons/reactivex/operators/_delaywithmapper.gd @@ -0,0 +1,95 @@ +static func delay_with_mapper_( + subscription_delay = null, + delay_duration_mapper = null +) -> Callable: + var delay_with_mapper = func(source : Observable) -> Observable: +# """Time shifts the observable sequence based on a subscription +# delay and a delay mapper function for each element. +# +# Examples: +# >>> var obs = delay_with_selector.call(source) +# +# Args: +# subscription_delay: [Optional] Sequence indicating the +# delay for the subscription to the source. +# delay_duration_mapper: [Optional] Selector function to +# retrieve a sequence indicating the delay for each given +# element. +# +# Returns: +# Time-shifted observable sequence. +# """ + var sub_delay : Observable = null + var mapper = null + + if subscription_delay is ObservableBase: + mapper = delay_duration_mapper + sub_delay = subscription_delay + else: + mapper = subscription_delay + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var delays = CompositeDisposable.new() + var at_end = [false] + + var done = func(): + if at_end[0] and delays.length == 0: + observer.on_completed() + + var subscription = SerialDisposable.new() + + var start = func(): + var on_next = func(x): + var delay = RefValue.Null() + if GDRx.try(func(): + if GDRx.assert_(mapper != null): return + delay.v = mapper.call(x) + ) \ + .catch("Error", func(error): + observer.on_error(error) + ) \ + .end_try_catch(): return + + var d = SingleAssignmentDisposable.new() + delays.add(d) + + var on_next = func(__): + observer.on_next(x) + delays.remove(d) + done.call() + + var on_completed = func(): + observer.on_next(x) + delays.remove(d) + done.call() + + d.disposable = delay.v.subscribe( + on_next, observer.on_error, on_completed, + scheduler + ) + + var on_completed = func(): + at_end[0] = true + subscription.dispose() + done.call() + + subscription.set_disposable(source.subscribe( + on_next, observer.on_error, on_completed, + scheduler + )) + + if sub_delay == null: + start.call() + else: + subscription.set_disposable(sub_delay.subscribe( + func(__): start.call(), observer.on_error, start + )) + + return CompositeDisposable.new([subscription, delays]) + + return Observable.new(subscribe) + + return delay_with_mapper diff --git a/addons/reactivex/operators/_dematerialize.gd b/addons/reactivex/operators/_dematerialize.gd new file mode 100644 index 0000000..2d812c5 --- /dev/null +++ b/addons/reactivex/operators/_dematerialize.gd @@ -0,0 +1,26 @@ +static func dematerialize_() -> Callable: + var dematerialize = func(source : Observable) -> Observable: +# """Partially applied dematerialize operator. +# +# Dematerializes the explicit notification values of an +# observable sequence as implicit notifications. +# +# Returns: +# An observable sequence exhibiting the behavior +# corresponding to the source sequence's notification values. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var on_next = func(value : Notification): + return value.accept(observer) + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return dematerialize diff --git a/addons/reactivex/operators/_distinct.gd b/addons/reactivex/operators/_distinct.gd new file mode 100644 index 0000000..0cd1ece --- /dev/null +++ b/addons/reactivex/operators/_distinct.gd @@ -0,0 +1,74 @@ +class HashSet: + var _comparer : Callable + var _set : Array + + func _init(comparer : Callable): + self._comparer = comparer + self._set = [] + + static func array_index_of_comparer( + array : Array, item, comparer : Callable + ): + for i in range(array.size()): + var a = array[i] + if comparer.call(a, item): + return i + return -1 + + func push(value): + var ret_value = HashSet.array_index_of_comparer(self._set, value, self._comparer) == -1 + if ret_value: + self._set.append(value) + return ret_value + +static func distinct_( + key_mapper : Callable = GDRx.basic.identity, + comparer : Callable = GDRx.basic.default_comparer +) -> Callable: + var comparer_ = comparer + + var distinct = func(source : Observable) -> Observable: +# """Returns an observable sequence that contains only distinct +# elements according to the key_mapper and the comparer. Usage of +# this operator should be considered carefully due to the +# maintenance of an internal lookup structure which can grow +# large. +# +# Examples: +# >>> var obs = distinct.call(source) +# +# Args: +# source: Source observable to return distinct items from. +# +# Returns: +# An observable sequence only containing the distinct +# elements, based on a computed key value, from the source +# sequence. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var hashset = HashSet.new(comparer_) + + var on_next = func(x): + var key = RefValue.Set(x) + if GDRx.try(func(): + key.v = key_mapper.call(key.v) + ) \ + .catch("Error", func(err): + observer.on_error(err) + ) \ + .end_try_catch(): return + + if hashset.push(key.v): + observer.on_next(x) + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return distinct diff --git a/addons/reactivex/operators/_distinctuntilchanged.gd b/addons/reactivex/operators/_distinctuntilchanged.gd new file mode 100644 index 0000000..5cfaa8a --- /dev/null +++ b/addons/reactivex/operators/_distinctuntilchanged.gd @@ -0,0 +1,71 @@ +static func distinct_until_changed_( + key_mapper : Callable = GDRx.basic.identity, + comparer : Callable = GDRx.basic.default_comparer +) -> Callable: + + var key_mapper_ = key_mapper + var comparer_ = comparer + + var distinct_until_changed = func(source : Observable) -> Observable: +# """Returns an observable sequence that contains only distinct +# contiguous elements according to the key_mapper and the +# comparer. +# +# Examples: +# >>> op = GDRx.op.distinct_until_changed() +# >>> op = GDRx.op.distinct_until_changed(func(x): return x.id) +# >>> op = GDRx.op.distinct_until_changed(func(x): return x.id, func(x, y): return x == y) +# +# Args: +# key_mapper: [Optional] A function to compute the comparison +# key for each element. If not provided, it projects the +# value. +# comparer: [Optional] Equality comparer for computed key +# values. If not provided, defaults to an equality +# comparer function. +# +# Returns: +# An observable sequence only containing the distinct +# contiguous elements, based on a computed key value, from +# the source sequence. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var has_current_key = RefValue.Set(false) + var current_key = RefValue.Null() + + var on_next = func(value): + var comparer_equals = RefValue.Set(false) + var key = RefValue.Null() + if GDRx.try(func(): + key.v = key_mapper_.call(value) + ) \ + .catch("Error", func(error): + observer.on_error(error) + ) \ + .end_try_catch(): return + + if has_current_key.v: + if GDRx.try(func(): + comparer_equals.v = comparer_.call(current_key.v, key.v) + ) \ + .catch("Error", func(error): + observer.on_error(error) + ) \ + .end_try_catch(): return + + if not has_current_key.v or not comparer_equals.v: + has_current_key.v = true + current_key.v = key.v + observer.on_next(value) + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return distinct_until_changed diff --git a/addons/reactivex/operators/_do.gd b/addons/reactivex/operators/_do.gd new file mode 100644 index 0000000..00de3e2 --- /dev/null +++ b/addons/reactivex/operators/_do.gd @@ -0,0 +1,313 @@ +static func do_action_( + on_next = null, on_error = null, on_completed = null +) -> Callable: + + var do_action = func(source : Observable) -> Observable: +# """Invokes an action for each element in the observable +# sequence and invokes an action on graceful or exceptional +# termination of the observable sequence. This method can be used +# for debugging, logging, etc. of query behavior by intercepting +# the message stream to run arbitrary actions for messages on the +# pipeline. +# +# Examples: +# >>> do_action(send).call(observable) +# >>> do_action(on_next, on_error).call(observable) +# >>> do_action(on_next, on_error, on_completed).call(observable) +# +# Args: +# on_next: [Optional] Action to invoke for each element in +# the observable sequence. +# on_error: [Optional] Action to invoke on exceptional +# termination of the observable sequence. +# on_completed: [Optional] Action to invoke on graceful +# termination of the observable sequence. +# +# Returns: +# An observable source sequence with the side-effecting +# behavior applied. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var _on_next = func(x): + if on_next == null: + observer.on_next(x) + else: + GDRx.try(func(): + on_next.call(x) + ).catch("Error", func(e): + observer.on_error(e) + ).end_try_catch() + + observer.on_next(x) + + var _on_error = func(error): + if on_error == null: + observer.on_error(error) + else: + GDRx.try(func(): + on_error.call(error) + ).catch("Error", func(e): + observer.on_error(e) + ).end_try_catch() + + observer.on_error(error) + + var _on_completed = func(): + if on_completed == null: + observer.on_completed() + else: + GDRx.try(func(): + on_completed.call() + ).catch("Error", func(e): + observer.on_error(e) + ).end_try_catch() + + observer.on_completed() + + return source.subscribe( + _on_next, _on_error, _on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return do_action + +static func do_(observer : ObserverBase) -> Callable: +# """Invokes an action for each element in the observable sequence and +# invokes an action on graceful or exceptional termination of the +# observable sequence. This method can be used for debugging, logging, +# etc. of query behavior by intercepting the message stream to run +# arbitrary actions for messages on the pipeline. +# +# >>> do(observer) +# +# Args: +# observer: Observer +# +# Returns: +# An operator function that takes the source observable and +# returns the source sequence with the side-effecting behavior +# applied. +# """ + return do_action_(observer.on_next, observer.on_error, observer.on_completed) + +static func do_after_next(source : Observable, after_next : Callable) -> Observable: +# """Invokes an action with each element after it has been emitted downstream. +# This can be helpful for debugging, logging, and other side effects. +# +# after_next -- Action to invoke on each element after it has been emitted +# """ + var subscribe = func( + observer : ObserverBase, _scheduler : SchedulerBase = null + ) -> DisposableBase: + var on_next = func(value): + GDRx.try(func(): + observer.on_next(value) + after_next.call(value) + ).catch("Error", func(e): + observer.on_error(e) + ).end_try_catch() + + return source.subscribe(on_next, observer.on_error, observer.on_completed) + + return Observable.new(subscribe) + +static func do_on_subscribe(source : Observable, on_subscribe : Callable) -> Observable: +# """Invokes an action on subscription. +# +# This can be helpful for debugging, logging, and other side effects +# on the start of an operation. +# +# Args: +# on_subscribe: Action to invoke on subscription +# """ + var subscribe = func( + observer : ObserverBase, scheduler : SchedulerBase = null + ) -> DisposableBase: + on_subscribe.call() + return source.subscribe( + observer.on_next, + observer.on_error, + observer.on_completed, + scheduler + ) + + return Observable.new(subscribe) + +class OnDispose extends DisposableBase: + var on_dispose : Callable + func _init(on_dispose_ : Callable): + super._init() + self.on_dispose = on_dispose_ + func dispose(): + this.on_dispose.call() + +static func do_on_dispose(source : Observable, on_dispose : Callable) -> Observable: +# """Invokes an action on disposal. +# +# This can be helpful for debugging, logging, and other side effects +# on the disposal of an operation. +# +# Args: +# on_dispose: Action to invoke on disposal +# """ + var subscribe = func( + observer : ObserverBase, scheduler : SchedulerBase = null + ) -> DisposableBase: + var composite_disposable = CompositeDisposable.new() + composite_disposable.add(OnDispose.new(on_dispose)) + var subscription = source.subscribe( + observer.on_next, + observer.on_error, + observer.on_completed, + scheduler + ) + composite_disposable.add(subscription) + return composite_disposable + + return Observable.new(subscribe) + +static func do_on_terminate(source : Observable, on_terminate : Callable) -> Observable: +# """Invokes an action on an on_complete() or on_error() event. +# This can be helpful for debugging, logging, and other side effects +# when completion or an error terminates an operation. +# +# +# on_terminate -- Action to invoke when on_complete or throw is called +# """ + var subscribe = func( + observer : ObserverBase, scheduler : SchedulerBase = null + ) -> DisposableBase: + var on_completed = func(): + if not GDRx.try(func(): + on_terminate.call() + ).catch("Error", func(err): + observer.on_error(err) + ).end_try_catch(): + observer.on_completed() + + var on_error = func(error): + if not GDRx.try(func(): + on_terminate.call() + ).catch("Error", func(err): + observer.on_error(err) + ).end_try_catch(): + observer.on_error(error) + + return source.subscribe( + observer.on_next, on_error, on_completed, + scheduler + ) + + return Observable.new(subscribe) + +static func do_after_terminate(source : Observable, after_terminate : Callable) -> Observable: +# """Invokes an action after an on_complete() or on_error() event. +# This can be helpful for debugging, logging, and other side effects +# when completion or an error terminates an operation +# +# +# on_terminate -- Action to invoke after on_complete or throw is called +# """ + var subscribe = func( + observer : ObserverBase, scheduler : SchedulerBase = null + ) -> DisposableBase: + var on_completed = func(): + observer.on_completed() + GDRx.try(func(): + after_terminate.call() + ).catch("Error", func(err): + observer.on_error(err) + ).end_try_catch() + + var on_error = func(error): + observer.on_error(error) + GDRx.try(func(): + after_terminate.call() + ).catch("Error", func(err): + observer.on_error(err) + ).end_try_catch() + + return source.subscribe( + observer.on_next, on_error, on_completed, + scheduler + ) + + return Observable.new(subscribe) + +class FinallyOnDispose extends DisposableBase: + var finally_action : Callable + var was_invoked : Array[bool] + + func _init(finally_action_ : Callable, was_invoked_ : Array[bool]): + super._init() + self.finally_action = finally_action_ + self.was_invoked = was_invoked_ + + func dispose(): + if not this.was_invoked[0]: + finally_action.call() + this.was_invoked[0] = true + +static func do_finally(finally_action : Callable) -> Callable: +# """Invokes an action after an on_complete(), on_error(), or disposal +# event occurs. +# +# This can be helpful for debugging, logging, and other side effects +# when completion, an error, or disposal terminates an operation. +# +# Note this operator will strive to execute the finally_action once, +# and prevent any redudant calls +# +# Args: +# finally_action -- Action to invoke after on_complete, on_error, +# or disposal is called +# """ + var partial = func(source : Observable) -> Observable: + var subscribe = func( + observer : ObserverBase, scheduler : SchedulerBase = null + ) -> DisposableBase: + + var was_invoked : Array[bool] = [false] + + var on_completed = func(): + observer.on_completed() + GDRx.try(func(): + if not was_invoked[0]: + finally_action.call() + was_invoked[0] = true + ) \ + .catch("Error", func(err): + observer.on_error(err) + ) \ + .end_try_catch() + + var on_error = func(error): + observer.on_error(error) + GDRx.try(func(): + if not was_invoked[0]: + finally_action.call() + was_invoked[0] = true + ) \ + .catch("Error", func(err): + observer.on_error(err) + ) \ + .end_try_catch() + + var composite_disposable = CompositeDisposable.new() + composite_disposable.add(FinallyOnDispose.new(finally_action, was_invoked)) + var subscription = source.subscribe( + observer.on_next, on_error, on_completed, + scheduler + ) + composite_disposable.add(subscription) + + return composite_disposable + + return Observable.new(subscribe) + + return partial diff --git a/addons/reactivex/operators/_dowhile.gd b/addons/reactivex/operators/_dowhile.gd new file mode 100644 index 0000000..fd6413a --- /dev/null +++ b/addons/reactivex/operators/_dowhile.gd @@ -0,0 +1,24 @@ +static func do_while_( + condition : Callable +) -> Callable: +# """Repeats source as long as condition holds emulating a do while +# loop. +# +# Args: +# condition: The condition which determines if the source will be +# repeated. +# +# Returns: +# An observable sequence which is repeated as long +# as the condition holds. +# """ + var do_while = func(source : Observable) -> Observable: + return source.pipe1( + GDRx.op.concat([ + source.pipe1( + GDRx.op.while_do(condition) + ) as Observable + ]) + ) + + return do_while diff --git a/addons/reactivex/operators/_elementatordefault.gd b/addons/reactivex/operators/_elementatordefault.gd new file mode 100644 index 0000000..c028dea --- /dev/null +++ b/addons/reactivex/operators/_elementatordefault.gd @@ -0,0 +1,43 @@ +static func element_at_or_default_( + index : int, has_default : bool = false, default_value = GDRx.util.GetNotSet() +) -> Callable: + + if index < 0: + ArgumentOutOfRangeError.new( + "Argument cannot be lower than zero!").throw() + index = 0 + + var element_at_or_default = func(source : Observable) -> Observable: + var subscribe = func( + observer : ObserverBase, scheduler : SchedulerBase = null + ) -> DisposableBase: + var index_ = RefValue.Set(index) + + var on_next = func(x): + var found = false + if true: + var __ = LockGuard.new(source.lock) + if index_.v > 0: + index_.v -= 1 + else: + found = true + + if found: + observer.on_next(x) + observer.on_completed() + + var on_completed = func(): + if not has_default: + observer.on_error(ArgumentOutOfRangeError.new()) + else: + observer.on_next(default_value) + observer.on_completed() + + return source.subscribe( + on_next, observer.on_error, on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return element_at_or_default diff --git a/addons/reactivex/operators/_exclusive.gd b/addons/reactivex/operators/_exclusive.gd new file mode 100644 index 0000000..c798b1c --- /dev/null +++ b/addons/reactivex/operators/_exclusive.gd @@ -0,0 +1,54 @@ +static func exclusive_() -> Callable: +# """Performs a exclusive waiting for the first to finish before +# subscribing to another observable. Observables that come in between +# subscriptions will be dropped on the floor. +# +# Returns: +# An exclusive observable with only the results that +# happen when subscribed. +# """ + var exclusive = func(source : Observable) -> Observable: + var subscribe = func( + observer : ObserverBase, scheduler : SchedulerBase = null + ) -> DisposableBase: + var has_current = [false] + var is_stopped = [false] + var m = SingleAssignmentDisposable.new() + var g = CompositeDisposable.new() + + g.add(m) + + var on_next = func(inner_source : Observable): + if not has_current[0]: + has_current[0] = true + + var inner_subscription = SingleAssignmentDisposable.new() + g.add(inner_subscription) + + var on_completed_inner = func(): + g.remove(inner_subscription) + has_current[0] = false + if is_stopped[0] and g.length == 1: + observer.on_completed() + + inner_subscription.disposable = inner_source.subscribe( + observer.on_next, + observer.on_error, + on_completed_inner, + scheduler + ) + + var on_completed = func(): + is_stopped[0] = true + if not has_current[0] and g.length == 1: + observer.on_completed() + + m.disposable = source.subscribe( + on_next, observer.on_error, on_completed, + scheduler + ) + return g + + return Observable.new(subscribe) + + return exclusive diff --git a/addons/reactivex/operators/_expand.gd b/addons/reactivex/operators/_expand.gd new file mode 100644 index 0000000..07b6af7 --- /dev/null +++ b/addons/reactivex/operators/_expand.gd @@ -0,0 +1,80 @@ +static func expand_( + mapper : Callable +) -> Callable: + var expand = func(source : Observable) -> Observable: +# """Expands an observable sequence by recursively invoking +# mapper. +# +# Args: +# source: Source obserable to expand. +# +# Returns: +# An observable sequence containing all the elements produced +# by the recursive expansion. +# """ + var subscribe = func( + observer : ObserverBase, scheduler : SchedulerBase = null + ) -> DisposableBase: + var scheduler_ = scheduler if scheduler == null else ImmediateScheduler.singleton() + + var queue : Array[Observable] = [] + var m = SerialDisposable.new() + var d = CompositeDisposable.new([m]) + var active_count = RefValue.Set(0) + var is_aquired = RefValue.Set(false) + + var ensure_active = func(__ensure_active_rec : Callable): + var is_owner = false + if not queue.is_empty(): + is_owner = not is_aquired.v + is_aquired.v = true + + var action = func(scheduler : SchedulerBase, _state = null, __action_rec : Callable = func(__, ___, ____): return): + var work : Observable + if not queue.is_empty(): + work = queue.pop_front() + else: + is_aquired.v = false + return + + var sad = SingleAssignmentDisposable.new() + d.add(sad) + + var on_next = func(value): + observer.on_next(value) + var result = RefValue.Null() + if GDRx.try(func(): + result.v = mapper.call(value) + ) \ + .catch("Error", func(err): + observer.on_error(err) + ) \ + .end_try_catch(): return + + queue.append(result.v) + active_count.v += 1 + __ensure_active_rec.bind(__ensure_active_rec).call() + + var on_complete = func(): + d.remove(sad) + active_count.v -= 1 + if active_count.v == 0: + observer.on_completed() + + sad.disposable = work.subscribe( + on_next, observer.on_error, on_complete, + scheduler + ) + m.disposable = scheduler.schedule(__action_rec.bind(__action_rec)) + + if is_owner: + m.disposable = scheduler_.schedule(action.bind(action)) + + queue.append(source) + active_count.v += 1 + ensure_active.bind(ensure_active).call() + return d + + return Observable.new(subscribe) + + return expand diff --git a/addons/reactivex/operators/_filter.gd b/addons/reactivex/operators/_filter.gd new file mode 100644 index 0000000..fad8153 --- /dev/null +++ b/addons/reactivex/operators/_filter.gd @@ -0,0 +1,82 @@ +static func filter_(predicate : Callable = GDRx.basic.default_condition) -> Callable: + var filter = func(source : Observable) -> Observable: +# """Partially applied filter operator. +# +# Filters the elements of an observable sequence based on a +# predicate. +# +# Example: +# >>> filter.call(source) +# +# Args: +# source: Source observable to filter. +# +# Returns: +# A filtered observable sequence. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var on_next = func(value): + var should_run = RefValue.Set(true) + if GDRx.try(func(): + should_run.v = predicate.call(value) + ) \ + .catch("Error", func(err): + observer.on_error(err) + ) \ + .end_try_catch(): return + + if should_run.v: + observer.on_next(value) + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return filter + +static func filter_indexed_(predicate : Callable = GDRx.basic.default_condition) -> Callable: + var filter_indexed = func(source : Observable) -> Observable: +# """Partially applied indexed filter operator. +# +# Filters the elements of an observable sequence based on a +# predicate by incorporating the element's index. +# +# Example: +# >>> filter_indexed.call(source) +# +# Args: +# source: Source observable to filter. +# +# Returns: +# A filtered observable sequence. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + + var count = RefValue.Set(0) + + var on_next = func(value): + var should_run = predicate.call(value, count.v) + if not should_run is bool: + observer.on_error(BadArgumentError.new()) + return + count.v += 1 + if should_run: + observer.on_next(value) + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return filter_indexed diff --git a/addons/reactivex/operators/_finallyaction.gd b/addons/reactivex/operators/_finallyaction.gd new file mode 100644 index 0000000..8894490 --- /dev/null +++ b/addons/reactivex/operators/_finallyaction.gd @@ -0,0 +1,42 @@ +static func finally_action_( + action : Callable +) -> Callable: + var finally_action = func(source: Observable) -> Observable: +# """Invokes a specified action after the source observable +# sequence terminates gracefully or exceptionally. +# +# Example: +# res = finally(source) +# +# Args: +# source: Observable sequence. +# +# Returns: +# An observable sequence with the action-invoking termination +# behavior applied. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var subscription = RefValue.Null() + GDRx.try(func(): + subscription.v = source.subscribe(observer, GDRx.basic.noop, GDRx.basic.noop, scheduler) + ) \ + .catch("Error", func(e): + action.call() + GDRx.raise(e) + ) \ + .end_try_catch() + + var dispose = func(): + GDRx.try(func(): + subscription.v.dispose() + ).end_try_catch() + action.call() + + return Disposable.new(dispose) + + return Observable.new(subscribe) + + return finally_action diff --git a/addons/reactivex/operators/_find.gd b/addons/reactivex/operators/_find.gd new file mode 100644 index 0000000..87359f8 --- /dev/null +++ b/addons/reactivex/operators/_find.gd @@ -0,0 +1,40 @@ +static func find_value_( + predicate : Callable, yield_index : bool +) -> Callable: + var find_value = func(source : Observable) -> Observable: + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var index = RefValue.Set(0) + + var on_next = func(x): + var should_run = RefValue.Set(false) + if GDRx.try(func(): + should_run.v = predicate.call(x, index.v, source) + ) \ + .catch("Error", func(err): + observer.on_error(err) + ) \ + .end_try_catch(): return + + if should_run.v: + observer.on_next(index.v if yield_index else x) + observer.on_completed() + else: + index.v += 1 + + var on_completed = func(): + @warning_ignore("incompatible_ternary") + observer.on_next(-1 if yield_index else null) + observer.on_completed() + + return source.subscribe( + on_next, observer.on_error, on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return find_value diff --git a/addons/reactivex/operators/_first.gd b/addons/reactivex/operators/_first.gd new file mode 100644 index 0000000..5d7db5a --- /dev/null +++ b/addons/reactivex/operators/_first.gd @@ -0,0 +1,26 @@ +static func first_( + predicate = null +) -> Callable: +# """Returns the first element of an observable sequence that +# satisfies the condition in the predicate if present else the first +# item in the sequence. +# +# Examples: +# >>> var res = first().call(source) +# >>> var res = first(func(x): return x > 3).call(source) +# +# Args: +# predicate -- [Optional] A predicate function to evaluate for +# elements in the source sequence. +# +# Returns: +# A function that takes an observable source and returns an +# observable sequence containing the first element in the +# observable sequence that satisfies the condition in the predicate if +# provided, else the first item in the sequence. +# """ + if predicate != null: + var predicate_ : Callable = predicate + return GDRx.pipe.compose2(GDRx.op.filter(predicate_), GDRx.op.first()) + + return GDRx.op.first_or_default_async(false) diff --git a/addons/reactivex/operators/_firstordefault.gd b/addons/reactivex/operators/_firstordefault.gd new file mode 100644 index 0000000..3962751 --- /dev/null +++ b/addons/reactivex/operators/_firstordefault.gd @@ -0,0 +1,63 @@ +static func first_or_default_async_( + has_default : bool = false, default_value = null +) -> Callable: + var first_or_default_async = func(source : Observable) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + + var on_next = func(x): + observer.on_next(x) + observer.on_completed() + + var on_completed = func(): + if not has_default: + observer.on_error(SequenceContainsNoElementsError.new()) + else: + observer.on_next(default_value) + observer.on_completed() + + return source.subscribe( + on_next, observer.on_error, on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return first_or_default_async + +static func first_or_default_( + predicate = null, + default_value = null +) -> Callable: +# """Returns the first element of an observable sequence that +# satisfies the condition in the predicate, or a default value if no +# such element exists. +# +# Examples: +# >>> var res = source.pipe1(GDRx.op.first_or_default()) +# >>> var res = source.pipe1(GDRx.op.first_or_default(func(x): return x > 3)) +# >>> var res = source.pipe1(GDRx.op.first_or_default(func(x): return x > 3, 0)) +# >>> var res = source.pipe1(GDRx.op.first_or_default(null, 0)) +# +# Args: +# source -- Observable sequence. +# predicate -- [optional] A predicate function to evaluate for +# elements in the source sequence. +# default_value -- [Optional] The default value if no such element +# exists. If not specified, defaults to None. +# +# Returns: +# A function that takes an observable source and reutrn an +# observable sequence containing the first element in the +# observable sequence that satisfies the condition in the +# predicate, or a default value if no such element exists. +# """ + if predicate != null: + var predicate_ : Callable = predicate + return GDRx.pipe.compose2( + GDRx.op.filter(predicate_), + GDRx.op.first_or_default(null, default_value) + ) + return first_or_default_async_(true, default_value) diff --git a/addons/reactivex/operators/_flatmap.gd b/addons/reactivex/operators/_flatmap.gd new file mode 100644 index 0000000..9aac8df --- /dev/null +++ b/addons/reactivex/operators/_flatmap.gd @@ -0,0 +1,110 @@ +static func _flat_map_internal( + source : Observable, + mapper = null, + mapper_indexed = null +) -> Observable: + var projection = func(x, i : int) -> Observable: + var mapper_result = mapper.call(x) if mapper != null else mapper_indexed.call(x, i) if mapper_indexed != null else GDRx.basic.identity + var result : Observable + if mapper_result is Observable: + result = mapper_result + elif mapper_result is IterableBase: + result = GDRx.obs.from_iterable(mapper_result) + elif mapper_result is Array: + result = GDRx.obs.from_iterable(GDRx.to_iterable(mapper_result)) + else: + BadMappingError.new( + "Mapper failed to produce a sequence of observables!").throw() + result = GDRx.obs.empty() + return result + + return source.pipe2( + GDRx.op.map_indexed(projection), + GDRx.op.merge_all() + ) + +static func flat_map_( + mapper = null +) -> Callable: + var flat_map = func(source : Observable) -> Observable: +# """One of the Following: +# Projects each element of an observable sequence to an observable +# sequence and merges the resulting observable sequences into one +# observable sequence. +# +# Example: +# >>> flat_map.call(source) +# +# Args: +# source: Source observable to flat map. +# +# Returns: +# An operator function that takes a source observable and returns +# an observable sequence whose elements are the result of invoking +# the one-to-many transform function on each element of the +# input sequence . +# """ + var ret : Observable + if mapper is Callable: + ret = _flat_map_internal(source, mapper) + else: + ret = _flat_map_internal(source, func(__): return mapper) + return ret + + return flat_map + +static func flat_map_indexed_( + mapper_indexed = null +) -> Callable: + var flat_map_indexed = func(source : Observable) -> Observable: +# """One of the Following: +# Projects each element of an observable sequence to an observable +# sequence and merges the resulting observable sequences into one +# observable sequence. +# +# Example: +# >>> flat_map_indexed.call(source) +# +# Args: +# source: Source observable to flat map. +# +# Returns: +# An observable sequence whose elements are the result of invoking +# the one-to-many transform function on each element of the input +# sequence. +# """ + var ret : Observable + if mapper_indexed is Callable: + ret = _flat_map_internal(source, null, mapper_indexed) + else: + ret = _flat_map_internal(source, func(__): return mapper_indexed) + return ret + + return flat_map_indexed + +static func flat_map_latest_( + mapper : Callable +) -> Callable: + var flat_map_latest = func(source : Observable) -> Observable: +# """Projects each element of an observable sequence into a new +# sequence of observable sequences by incorporating the element's +# index and then transforms an observable sequence of observable +# sequences into an observable sequence producing values only +# from the most recent observable sequence. +# +# Args: +# source: Source observable to flat map latest. +# +# Returns: +# An observable sequence whose elements are the result of +# invoking the transform function on each element of source +# producing an observable of Observable sequences and that at +# any point in time produces the elements of the most recent +# inner observable sequence that has been received. +# """ + return source.pipe2( + GDRx.op.map(mapper), + GDRx.op.switch_latest() + ) + + return flat_map_latest diff --git a/addons/reactivex/operators/_forkjoin.gd b/addons/reactivex/operators/_forkjoin.gd new file mode 100644 index 0000000..5ddca20 --- /dev/null +++ b/addons/reactivex/operators/_forkjoin.gd @@ -0,0 +1,19 @@ +static func fork_join_(args) -> Callable: + var fork_join = func(source : Observable) -> Observable: +# """Wait for observables to complete and then combine last values +# they emitted into a tuple. Whenever any of that observables +# completes without emitting any value, result sequence will +# complete at that moment as well. +# +# Examples: +# >>> var obs = fork_join.call(source) +# +# Returns: +# An observable sequence containing the result of combining +# last element from each source in given sequence. +# """ + var sources : Array[Observable] = GDRx.util.unpack_arg(args) + sources.push_front(source) + return GDRx.obs.fork_join(sources) + + return fork_join diff --git a/addons/reactivex/operators/_groupby.gd b/addons/reactivex/operators/_groupby.gd new file mode 100644 index 0000000..1c06e2f --- /dev/null +++ b/addons/reactivex/operators/_groupby.gd @@ -0,0 +1,13 @@ +static func group_by_( + key_mapper : Callable, + element_mapper = null, + subject_mapper = null +) -> Callable: + + var duration_mapper = func(__ : GroupedObservable) -> Observable: + return GDRx.obs.never() + + return GDRx.op.group_by_until( + key_mapper, duration_mapper, + element_mapper, subject_mapper + ) diff --git a/addons/reactivex/operators/_groupbyuntil.gd b/addons/reactivex/operators/_groupbyuntil.gd new file mode 100644 index 0000000..a085913 --- /dev/null +++ b/addons/reactivex/operators/_groupbyuntil.gd @@ -0,0 +1,161 @@ +static func group_by_until_( + key_mapper : Callable, + duration_mapper : Callable, + element_mapper = null, + subject_mapper = null +) -> Callable: + +# """Groups the elements of an observable sequence according to a +# specified key mapper function. A duration mapper function is used +# to control the lifetime of groups. When a group expires, it receives +# an OnCompleted notification. When a new element with the same key +# value as a reclaimed group occurs, the group will be reborn with a +# new lifetime request. +# +# Examples: +# >>> GDRx.op.group_by_until(func(x): return x.id, null, func() : return GDRx.obs.never()) +# >>> GDRx.op.group_by_until( +# func(x): return x.id, func(x): return x.name, func(grp): return GDRx.obs.never() +# ) +# >>> GDRx.op.group_by_until( +# func(x): return x.id, +# func(x): return x.name, +# func(grp): return GDRx.obs.never(), +# func(): return ReplaySubject.new() +# ) +# +# Args: +# key_mapper: A function to extract the key for each element. +# duration_mapper: A function to signal the expiration of a group. +# subject_mapper: A function that returns a subject used to initiate +# a grouped observable. Default mapper returns a Subject object. +# +# Returns: a sequence of observable groups, each of which corresponds to +# a unique key value, containing all elements that share that same key +# value. If a group's lifetime expires, a new group with the same key +# value can be created once an element with such a key value is +# encountered. +# """ + + var element_mapper_ : Callable = element_mapper if element_mapper != null else GDRx.basic.identity + + var default_subject_mapper = func(): return Subject.new() + @warning_ignore("incompatible_ternary") + var subject_mapper_ : Callable = subject_mapper if subject_mapper != null else default_subject_mapper + + var group_by_until = func(source : Observable) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var writers : Dictionary = {} # TO DO OrderedDict + var group_disposable = CompositeDisposable.new() + var ref_count_disposable = RefCountDisposable.new(group_disposable) + + var on_next = func(x): + var writer = RefValue.Null() + var key = RefValue.Null() + + if GDRx.try(func(): + key.v = key_mapper.call(x) + ) \ + .catch("Error", func(__): + for wrt in writers.values(): + wrt.on_error(key.v) + observer.on_error(key.v) + ) \ + .end_try_catch(): return + + var fire_new_map_entry = false + writer.v = writers.get(key.v) + if writer.v == null: + if GDRx.try(func(): + writer.v = subject_mapper_.call() + ) \ + .catch("Error", func(e): + for wrt in writers.values(): + wrt.on_error(e) + observer.on_error(e) + ) \ + .end_try_catch(): return + + writers[key.v] = writer.v + fire_new_map_entry = true + + if fire_new_map_entry: + var group : GroupedObservable = GroupedObservable.new( + key.v, writer.v.as_observable(), ref_count_disposable + ) + var duration_group : GroupedObservable = GroupedObservable.new( + key.v, writer.v.as_observable() + ) + var duration = RefValue.Null() + if GDRx.try(func(): + duration.v = duration_mapper.call(duration_group) + ) \ + .catch("Error", func(e): + for wrt in writers.values(): + wrt.on_error(e) + observer.on_error(e) + ) \ + .end_try_catch(): return + + observer.on_next(group) + var sad = SingleAssignmentDisposable.new() + group_disposable.add(sad) + + var expire = func(): + if writers.get(key.v) != null: + writers.erase(key.v) + writer.v.on_completed() + + group_disposable.remove(sad) + + var on_next = func(__): + pass + + var on_error = func(exn): + for wrt in writers.values(): + wrt.on_error(exn) + observer.on_error(exn) + + var on_completed = func(): + expire.call() + + sad.disposable = duration.v.pipe1( + GDRx.op.take(1) + ).subscribe( + on_next, on_error, on_completed, scheduler + ) + + var element = RefValue.Null() + if GDRx.try(func(): + element.v = element_mapper_.call(x) + ) \ + .catch("Error", func(e): + for wrt in writers.values(): + wrt.on_error(e) + observer.on_error(e) + ) \ + .end_try_catch(): return + + writer.v.on_next(element.v) + + var on_error = func(ex): + for wrt in writers.values(): + wrt.on_error(ex) + observer.on_error(ex) + + var on_completed = func(): + for wrt in writers.values(): + wrt.on_completed() + observer.on_completed() + + group_disposable.add(source.subscribe( + on_next, on_error, on_completed, scheduler + )) + return ref_count_disposable + + return Observable.new(subscribe) + + return group_by_until diff --git a/addons/reactivex/operators/_groupjoin.gd b/addons/reactivex/operators/_groupjoin.gd new file mode 100644 index 0000000..8c0e4fa --- /dev/null +++ b/addons/reactivex/operators/_groupjoin.gd @@ -0,0 +1,171 @@ +static func group_join_( + right : Observable, + left_duration_mapper : Callable, + right_duration_mapper : Callable +) -> Callable: + +# """Correlates the elements of two sequences based on overlapping +# durations, and groups the results. +# +# Args: +# right: The right observable sequence to join elements for. +# left_duration_mapper: A function to select the duration (expressed +# as an observable sequence) of each element of the left observable +# sequence, used to determine overlap. +# right_duration_mapper: A function to select the duration (expressed +# as an observable sequence) of each element of the right observable +# sequence, used to determine overlap. +# +# Returns: +# An observable sequence that contains elements combined into a tuple +# from source elements that have an overlapping duration. +# """ + + var nothing = func(__): + return null + + var group_join = func(left : Observable) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var group = CompositeDisposable.new() + var rcd = RefCountDisposable.new(group) + var left_map : Dictionary = {} # TO DO OrderedDict() + var right_map : Dictionary = {} + var left_id = [0] + var right_id = [0] + + var on_next_left = func(value): + var subject : Subject = Subject.new() + + var _id + if true: + var __ = LockGuard.new(left.lock) + _id = left_id[0] + left_id[0] += 1 + left_map[_id] = subject + + var result = RefValue.Null() + if GDRx.try(func(): + result.v = Tuple.new([value, GDRx.util.add_ref(subject.as_observable(), rcd)]) + ) \ + .catch("Error", func(e): + push_error("*** Error: ", e) + for left_value in left_map.values(): + left_value.on_error(e) + + observer.on_error(e) + ) \ + .end_try_catch(): return + + observer.on_next(result.v) + + for right_value in right_map.values(): + subject.on_next(right_value) + + var md = SingleAssignmentDisposable.new() + group.add(md) + + var expire = func(): + if left_map.has(_id): + left_map.erase(_id) + subject.on_completed() + + group.remove(md) + + var duration = RefValue.Null() + if GDRx.try(func(): + duration.v = left_duration_mapper.call(value) + ) \ + .catch("Error", func(e): + for left_value in left_map.values(): + left_value.on_error(e) + + observer.on_error(e) + ) \ + .end_try_catch(): return + + var on_error = func(error): + for left_value in left_map.values(): + left_value.on_error(error) + + observer.on_error(error) + + md.disposable = duration.v.pipe1(GDRx.op.take(1)).subscribe( + nothing, on_error, expire, scheduler + ) + + var on_error_left = func(error): + for left_value in left_map.values(): + left_value.on_error(error) + + observer.on_error(error) + + group.add( + left.subscribe( + on_next_left, + on_error_left, + observer.on_completed, + scheduler + ) + ) + + var send_right = func(value): + var _id + if true: + var __ = LockGuard.new(left.lock) + _id = right_id[0] + right_id[0] += 1 + right_map[_id] = value + + var md = SingleAssignmentDisposable.new() + group.add(md) + + var expire = func(): + right_map.erase(_id) + group.remove(md) + + var duration = RefValue.Null() + if GDRx.try(func(): + duration.v = right_duration_mapper.call(value) + ) \ + .catch("Error", func(e): + for left_value in left_map.values(): + left_value.on_error(e) + + observer.on_error(e) + ) \ + .end_try_catch(): return + + var on_error = func(error): + var _guard = LockGuard.new(left.lock) + for left_value in left_map.values(): + left_value.on_error(error) + + observer.on_error(error) + + md.disposable = duration.v.pipe1(GDRx.obs.take(1)).subscribe( + nothing, on_error, expire, + scheduler + ) + + if true: + var __ = LockGuard.new(left.lock) + for left_value in left_map.values(): + left_value.on_next(value) + + var on_error_right = func(error): + for left_value in left_map.values(): + left_value.as_observer().on_error(error) + + observer.on_error(error) + + group.add(right.subscribe( + send_right, on_error_right, func():return, scheduler + )) + return rcd + + return Observable.new(subscribe) + + return group_join diff --git a/addons/reactivex/operators/_ignoreelements.gd b/addons/reactivex/operators/_ignoreelements.gd new file mode 100644 index 0000000..abd620a --- /dev/null +++ b/addons/reactivex/operators/_ignoreelements.gd @@ -0,0 +1,23 @@ +static func ignore_elements_() -> Callable: +# """Ignores all elements in an observable sequence leaving only the +# termination messages. +# +# Returns: +# An empty observable {Observable} sequence that signals +# termination, successful or exceptional, of the source sequence. +# """ + var ignore_elements = func(source : Observable) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + return source.subscribe( + func(__):return, + observer.on_error, + observer.on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return ignore_elements diff --git a/addons/reactivex/operators/_isempty.gd b/addons/reactivex/operators/_isempty.gd new file mode 100644 index 0000000..f471875 --- /dev/null +++ b/addons/reactivex/operators/_isempty.gd @@ -0,0 +1,15 @@ +static func is_empty_() -> Callable: +# """Determines whether an observable sequence is empty. +# +# Returns: +# An observable sequence containing a single element +# determining whether the source sequence is empty. +# """ + + var mapper = func(b : bool) -> bool: + return not b + + return GDRx.pipe.compose2( + GDRx.op.some(), + GDRx.op.map(mapper) + ) diff --git a/addons/reactivex/operators/_join.gd b/addons/reactivex/operators/_join.gd new file mode 100644 index 0000000..442d81e --- /dev/null +++ b/addons/reactivex/operators/_join.gd @@ -0,0 +1,130 @@ +static func join_( + right : Observable, + left_duration_mapper : Callable, + right_duration_mapper : Callable +) -> Callable: + var join = func(source : Observable) -> Observable: +# """Correlates the elements of two sequences based on +# overlapping durations. +# +# Args: +# source: Source observable. +# +# Return: +# An observable sequence that contains elements +# combined into a tuple from source elements that have an overlapping +# duration. +# """ + var left = source + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var group = CompositeDisposable.new() + var left_done = RefValue.Set(false) + var left_map : Dictionary = {} # to do OrderedDict() + var left_id = RefValue.Set(0) + var right_done = RefValue.Set(false) + var right_map : Dictionary = {} # TO DO OrderedDict() + var right_id = RefValue.Set(0) + + var on_next_left = func(value): + var duration = RefValue.Null() + var current_id = left_id.v + left_id.v += 1 + var md = SingleAssignmentDisposable.new() + + left_map[current_id] = value + group.add(md) + + var expire = func(): + if left_map.has(current_id): + left_map.erase(current_id) + if left_map.is_empty() and left_done.v: + observer.on_completed() + + group.remove(md) + + if GDRx.try(func(): + duration.v = left_duration_mapper.call(value) + ) \ + .catch("Error", func(error): + observer.on_error(error) + ) \ + .end_try_catch(): return + + md.disposable = duration.v.pipe1(GDRx.op.take(1)).subscribe( + func(__):return, observer.on_error, func(): expire.call(), + scheduler + ) + + for val in right_map.values(): + var result = Tuple.new([value, val]) + observer.on_next(result) + + var on_completed_left = func(): + left_done.v = true + if right_done.v or left_map.is_empty(): + observer.on_completed() + + group.add( + left.subscribe( + on_next_left, + observer.on_error, + on_completed_left, + scheduler + ) + ) + + var on_next_right = func(value): + var duration = RefValue.Null() + var current_id = right_id.v + right_id.v += 1 + var md = SingleAssignmentDisposable.new() + right_map[current_id] = value + group.add(md) + + var expire = func(): + if right_map.has(current_id): + right_map.erase(current_id) + if right_map.is_empty() and right_done.v: + observer.on_completed() + + group.remove(md) + + if GDRx.try(func(): + duration.v = right_duration_mapper.call(value) + ) \ + .catch("Error", func(error): + observer.on_error(error) + ) \ + .end_try_catch(): return + + md.disposable = duration.v.pipe1(GDRx.op.take(1)).subscribe( + func(__):return, observer.on_error, func(): expire.call(), + scheduler + ) + + for val in left_map.values(): + var result = Tuple.new([val, value]) + observer.on_next(result) + + var on_completed_right = func(): + right_done.v = true + if left_done.v or right_map.is_empty(): + observer.on_completed() + + group.add( + right.subscribe( + on_next_right, + observer.on_error, + on_completed_right, + scheduler + ) + ) + return group + + return Observable.new(subscribe) + + return join diff --git a/addons/reactivex/operators/_last.gd b/addons/reactivex/operators/_last.gd new file mode 100644 index 0000000..f9937c7 --- /dev/null +++ b/addons/reactivex/operators/_last.gd @@ -0,0 +1,29 @@ +static func last_(predicate = null) -> Callable: + var last = func(source : Observable) -> Observable: +# """Partially applied last operator. +# +# Returns the last element of an observable sequence that +# satisfies the condition in the predicate if specified, else +# the last element. +# +# Examples: +# >>> var res = last.call(source) +# +# Args: +# source: Source observable to get last item from. +# +# Returns: +# An observable sequence containing the last element in the +# observable sequence that satisfies the condition in the +# predicate. +# """ + if predicate != null: + var predicate_ : Callable = predicate + return source.pipe2( + GDRx.op.filter(predicate_), + GDRx.op.last() + ) + + return GDRx.op.last_or_default_async(source, false) + + return last diff --git a/addons/reactivex/operators/_lastordefault.gd b/addons/reactivex/operators/_lastordefault.gd new file mode 100644 index 0000000..d34e8c0 --- /dev/null +++ b/addons/reactivex/operators/_lastordefault.gd @@ -0,0 +1,56 @@ +static func last_or_default_async( + source : Observable, + has_default : bool = false, + default_value = null +) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var value = [default_value] + var seen_value = [false] + + var on_next = func(x): + value[0] = x + seen_value[0] = true + + var on_completed = func(): + if not seen_value[0] and not has_default: + observer.on_error(SequenceContainsNoElementsError.new()) + else: + observer.on_next(value[0]) + observer.on_completed() + + return source.subscribe( + on_next, observer.on_error, on_completed, + scheduler + ) + + return Observable.new(subscribe) + +static func last_or_default_( + default_value = null, predicate = null +) -> Callable: + var last_or_default = func(source : Observable) -> Observable: +# """Return last or default element. +# +# Examples: +# >>> var res = last_or_default.call(source) +# +# Args: +# source: Observable sequence to get the last item from. +# +# Returns: +# Observable sequence containing the last element in the +# observable sequence. +# """ + if predicate != null: + var predicate_ : Callable = predicate + return source.pipe2( + GDRx.op.filter(predicate_), + GDRx.op.last_or_default(default_value) + ) + + return last_or_default_async(source, true, default_value) + + return last_or_default diff --git a/addons/reactivex/operators/_map.gd b/addons/reactivex/operators/_map.gd new file mode 100644 index 0000000..f47c3b0 --- /dev/null +++ b/addons/reactivex/operators/_map.gd @@ -0,0 +1,55 @@ +static func map_( + mapper : Callable = GDRx.basic.identity +) -> Callable: + var _mapper = mapper + + var map = func(source : Observable) -> Observable: +# """Partially applied map operator. +# +# Project each element of an observable sequence into a new form +# by incorporating the element's index. +# +# Example: +# >>> map.call(source) +# +# Args: +# source: The observable source to transform. +# +# Returns: +# Returns an observable sequence whose elements are the +# result of invoking the transform function on each element +# of the source. +# """ + var subscribe = func( + obv : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var on_next = func(value): + var result = RefValue.Null() + if not GDRx.try(func(): + result.v = _mapper.call(value) + ) \ + .catch("Error", func(err): + obv.on_error(err) + ) \ + .end_try_catch(): + obv.on_next(result.v) + + return source.subscribe( + on_next, obv.on_error, obv.on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return map + +static func map_indexed_( + mapper_indexed : Callable = GDRx.basic.identity +) -> Callable: + var _mapper_indexed = mapper_indexed + + return GDRx.pipe.compose2( + GDRx.op.zip_with_iterable(InfiniteIterable.new()), + GDRx.op.map(func(i : Tuple): return _mapper_indexed.call(i.at(0), i.at(1))) + ) diff --git a/addons/reactivex/operators/_materialize.gd b/addons/reactivex/operators/_materialize.gd new file mode 100644 index 0000000..53e2d24 --- /dev/null +++ b/addons/reactivex/operators/_materialize.gd @@ -0,0 +1,37 @@ +static func materialize_() -> Callable: + var materialize = func(source : Observable) -> Observable: +# """Partially applied materialize operator. +# +# Materializes the implicit notifications of an observable +# sequence as explicit notification values. +# +# Args: +# source: Source observable to materialize. +# +# Returns: +# An observable sequence containing the materialized +# notification values from the source sequence. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ): + var on_next = func(value): + observer.on_next(OnNextNotification.new(value)) + + var on_error = func(error): + observer.on_next(OnErrorNotification.new(error)) + observer.on_completed() + + var on_completed = func(): + observer.on_next(OnCompletedNotification.new()) + observer.on_completed() + + return source.subscribe( + on_next, on_error, on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return materialize diff --git a/addons/reactivex/operators/_max.gd b/addons/reactivex/operators/_max.gd new file mode 100644 index 0000000..99dff23 --- /dev/null +++ b/addons/reactivex/operators/_max.gd @@ -0,0 +1,21 @@ +static func max_(comparer = null) -> Callable: +# """Returns the maximum value in an observable sequence according to +# the specified comparer. +# +# Examples: +# >>> op = GDRx.op.max() +# >>> op = GDRx.op.max(func(x, y): return x.value - y.value) +# +# Args: +# comparer: [Optional] Comparer used to compare elements. +# +# Returns: +# An operator function that takes an observable source and returns +# an observable sequence containing a single element with the +# maximum element in the source sequence. +# """ + return GDRx.pipe.compose2( + GDRx.op.max_by(GDRx.basic.identity, comparer), + GDRx.op.map(func(x : Array): return GDRx.op._Min_.first_only(x)) + ) + diff --git a/addons/reactivex/operators/_maxby.gd b/addons/reactivex/operators/_maxby.gd new file mode 100644 index 0000000..3e7584a --- /dev/null +++ b/addons/reactivex/operators/_maxby.gd @@ -0,0 +1,25 @@ +static func max_by_( + key_mapper : Callable, + comparer = null +) -> Callable: + var cmp = comparer if comparer != null else GDRx.basic.default_sub_comparer + + var max_by = func(source : Observable) -> Observable: +# """Partially applied max_by operator. +# +# Returns the elements in an observable sequence with the maximum +# key value. +# +# Examples: +# >>> var res = max_by.call(source) +# +# Args: +# source: The source observable sequence to. +# +# Returns: +# An observable sequence containing a list of zero or more +# elements that have a maximum key value. +# """ + return GDRx.op.extrema_by(source, key_mapper, cmp) + + return max_by diff --git a/addons/reactivex/operators/_merge.gd b/addons/reactivex/operators/_merge.gd new file mode 100644 index 0000000..8bc95ba --- /dev/null +++ b/addons/reactivex/operators/_merge.gd @@ -0,0 +1,148 @@ +static func merge_(_sources, max_concorrent : int = -1) -> Callable: + var merge = func(source : Observable) -> Observable: +# """Merges an observable sequence of observable sequences into +# an observable sequence, limiting the number of concurrent +# subscriptions to inner sequences. Or merges two observable +# sequences into a single observable sequence. +# +# Examples: +# >>> var res = merge.call(sources) +# +# Args: +# source: Source observable. +# +# Returns: +# The observable sequence that merges the elements of the +# inner sequences. +# """ + var sources : Array[Observable] = GDRx.util.unpack_arg(_sources) + + if max_concorrent < 0: + var sources_ : Array[Observable] = sources.duplicate() + sources_.push_front(source) + return GDRx.obs.merge(sources_) + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ): + var active_count = [0] + var group = CompositeDisposable.new() + var is_stopped = [false] + var queue : Array[Observable] = [] + + var subscribe = func(xs : Observable, subscribe_rec__ : Callable): + var subscription = SingleAssignmentDisposable.new() + group.add(subscription) + + var on_completed = func(): + var __ = LockGuard.new(source.lock) + group.remove(subscription) + if queue.size() > 0: + var s = queue.pop_front() + subscribe_rec__.bind(subscribe_rec__).call(s) + else: + active_count[0] -= 1 + if is_stopped[0] and active_count[0] == 0: + observer.on_completed() + + var on_next = func(i): + var __ = LockGuard.new(source.lock) + observer.on_next(i) + + var on_error = func(e): + var __ = LockGuard.new(source.lock) + observer.on_error(e) + + subscription.disposable = xs.subscribe( + on_next, on_error, on_completed, + scheduler + ) + + var on_next = func(inner_source : Observable): + if GDRx.assert_(max_concorrent != 0): return + if active_count[0] < max_concorrent: + active_count[0] += 1 + subscribe.call(inner_source, subscribe) + else: + queue.append(inner_source) + + var on_completed = func(): + is_stopped[0] = true + if active_count[0] == 0: + observer.on_completed() + + group.add( + source.subscribe( + on_next, observer.on_error, on_completed, + scheduler + ) + ) + return group + + return Observable.new(subscribe) + + return merge + + +static func merge_all_() -> Callable: + var merge_all = func(source : Observable) -> Observable: +# """Partially applied merge_all operator. +# +# Merges an observable sequence of observable sequences into an +# observable sequence. +# +# Args: +# source: Source observable to merge. +# +# Returns: +# The observable sequence that merges the elements of the inner +# sequences. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ): + var group = CompositeDisposable.new() + var is_stopped = [false] + var m = SingleAssignmentDisposable.new() + group.add(m) + + var on_next = func(inner_source : Observable): + var inner_subscription = SingleAssignmentDisposable.new() + group.add(inner_subscription) + + var on_completed = func(): + var __ = LockGuard.new(source.lock) + group.remove(inner_subscription) + if is_stopped[0] and group.length == 1: + observer.on_completed() + + var on_next = func(i): + var __ = LockGuard.new(source.lock) + observer.on_next(i) + + var on_error = func(e): + var __ = LockGuard.new(source.lock) + observer.on_error(e) + + var subscription = inner_source.subscribe( + on_next, on_error, on_completed, + scheduler + ) + inner_subscription.set_disposable(subscription) + + var on_completed = func(): + is_stopped[0] = true + if group.length == 1: + observer.on_completed() + + m.disposable = source.subscribe( + on_next, observer.on_error, on_completed, + scheduler + ) + return group + + return Observable.new(subscribe) + + return merge_all diff --git a/addons/reactivex/operators/_min.gd b/addons/reactivex/operators/_min.gd new file mode 100644 index 0000000..6ea3576 --- /dev/null +++ b/addons/reactivex/operators/_min.gd @@ -0,0 +1,28 @@ +static func first_only(x : Array): + if x.is_empty(): + return SequenceContainsNoElementsError.raise() + return x[0] + +static func min_( + comparer = null +) -> Callable: +# """The `min` operator. +# +# Returns the minimum element in an observable sequence according to +# the optional comparer else a default greater than less than check. +# +# Examples: +# >>> var res = source.pipe1(GDRx.op.min()) +# >>> var res = source.pipe1(GDRx.op.min(func(x, y): return x.value - y.value)) +# +# Args: +# comparer: [Optional] Comparer used to compare elements. +# +# Returns: +# An observable sequence containing a single element +# with the minimum element in the source sequence. +# """ + return GDRx.pipe.compose2( + GDRx.op.min_by(GDRx.basic.identity, comparer), + GDRx.op.map(func(x): return first_only(x)) + ) diff --git a/addons/reactivex/operators/_minby.gd b/addons/reactivex/operators/_minby.gd new file mode 100644 index 0000000..af4fc6b --- /dev/null +++ b/addons/reactivex/operators/_minby.gd @@ -0,0 +1,82 @@ +static func extrema_by( + source : Observable, + key_mapper : Callable, + comparer : Callable +) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var has_value = RefValue.Set(false) + var last_key = RefValue.Null() + var items = [] + + var on_next = func(x): + var key = RefValue.Null() + if GDRx.try(func(): + key.v = key_mapper.call(x) + ) \ + .catch("Error", func(err): + observer.on_error(err) + ) \ + .end_try_catch(): return + + var comparison = RefValue.Set(0) + + if not has_value.v: + has_value.v = true + last_key.v = key.v + else: + if GDRx.try(func(): + comparison.v = comparer.call(key.v, last_key.v) + ) \ + .catch("Error", func(err): + observer.on_error(err) + ) \ + .end_try_catch(): return + + if comparison.v > 0: + last_key.v = key.v + items.clear() + + if comparison.v >= 0: + items.append(x) + + var on_completed = func(): + observer.on_next(items) + observer.on_completed() + + return source.subscribe( + on_next, observer.on_error, on_completed, + scheduler + ) + + return Observable.new(subscribe) + +static func min_by_( + key_mapper : Callable, + comparer = null +) -> Callable: +# """The `min_by` operator. +# +# Returns the elements in an observable sequence with the minimum key +# value according to the specified comparer. +# +# Examples: +# >>> var res = GDRx.op.min_by(func(x): return x.value) +# >>> var res = GDRx.op.min_by(func(x): return x.value, func(x, y): return x - y) +# +# Args: +# key_mapper: Key mapper function. +# comparer: [Optional] Comparer used to compare key values. +# +# Returns: +# An observable sequence containing a list of zero or more +# elements that have a minimum key value. +# """ + var cmp = comparer if comparer != null else GDRx.basic.default_sub_comparer + + var min_by = func(source : Observable) -> Observable: + return extrema_by(source, key_mapper, func(x, y): return -cmp.call(x, y)) + + return min_by diff --git a/addons/reactivex/operators/_multicast.gd b/addons/reactivex/operators/_multicast.gd new file mode 100644 index 0000000..9ea3cc5 --- /dev/null +++ b/addons/reactivex/operators/_multicast.gd @@ -0,0 +1,66 @@ +static func multicast_( + subject : SubjectBase = null, + subject_factory = null, + mapper = null +) -> Callable: + +# """Multicasts the source sequence notifications through an +# instantiated subject into all uses of the sequence within a mapper +# function. Each subscription to the resulting sequence causes a +# separate multicast invocation, exposing the sequence resulting from +# the mapper function's invocation. For specializations with fixed +# subject types, see Publish, PublishLast, and Replay. +# +# Examples: +# >>> var res = GDRx.op.multicast(observable) +# >>> var res = GDRx.op.multicast( +# null, +# func(scheduler): Subject.new(), +# func(x): return x +# ) +# +# Args: +# subject_factory: Factory function to create an intermediate +# subject through which the source sequence's elements will be +# multicast to the mapper function. +# subject: Subject to push source elements into. +# mapper: [Optional] Mapper function which can use the +# multicasted source sequence subject to the policies enforced +# by the created subject. Specified only if subject_factory +# is a factory function. +# +# Returns: +# An observable sequence that contains the elements of a sequence +# produced by multicasting the source sequence within a mapper +# function. +# """ + + var multicast = func(source : Observable) -> Observable: + if subject_factory != null: + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + if GDRx.assert_(subject_factory is Callable): return Disposable.new() + var connectable = source.pipe1( + GDRx.op.multicast(subject_factory.call(scheduler)) + ) + if GDRx.assert_(mapper is Callable): return Disposable.new() + var subscription = mapper.call(connectable).subscribe( + observer, GDRx.basic.noop, GDRx.basic.noop, + scheduler + ) + + return CompositeDisposable.new([ + subscription, connectable.connect_observable(scheduler) + ]) + + return Observable.new(subscribe) + + if subject == null: + NullReferenceError.raise() + var ret = ConnectableObservable.new(source, subject) + return ret + + return multicast diff --git a/addons/reactivex/operators/_observeon.gd b/addons/reactivex/operators/_observeon.gd new file mode 100644 index 0000000..465c261 --- /dev/null +++ b/addons/reactivex/operators/_observeon.gd @@ -0,0 +1,31 @@ +static func observe_on_(scheduler : SchedulerBase) -> Callable: + var observe_on = func(source : Observable) -> Observable: +# """Wraps the source sequence in order to run its observer +# callbacks on the specified scheduler. +# +# This only invokes observer callbacks on a scheduler. In case +# the subscription and/or unsubscription actions have +# side-effects that require to be run on a scheduler, use +# subscribe_on. +# +# Args: +# source: Source observable. +# +# +# Returns: +# Returns the source sequence whose observations happen on +# the specified scheduler. +# """ + var subscribe = func( + observer : ObserverBase, + subscribe_scheduler : SchedulerBase = null + ) -> DisposableBase: + return source.subscribe( + ObserveOnObserver.new(scheduler, observer), + GDRx.basic.noop, GDRx.basic.noop, + subscribe_scheduler + ) + + return Observable.new(subscribe) + + return observe_on diff --git a/addons/reactivex/operators/_oftype.gd b/addons/reactivex/operators/_oftype.gd new file mode 100644 index 0000000..411c78d --- /dev/null +++ b/addons/reactivex/operators/_oftype.gd @@ -0,0 +1,38 @@ +static func oftype_(type, push_err : bool = true, type_equality : Callable = GDRx.basic.default_type_equality) -> Callable: + var oftype = func(source : Observable) -> Observable: +# """Partially applied oftype operator. +# +# Fixes the type of an element to a specific class. Elements of wrong +# type cause an error. +# +# Example: +# >>> oftype.call(source) +# +# Args: +# source: Source observable to oftype. +# +# Returns: +# An observable sequence with fixed type. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var on_next = func(value): + var type_mismatch : bool = not type_equality.call(type, value) + if type_mismatch: + var exc = TypeMismatchError.new(value) + observer.on_error(exc) + if push_err: + push_error(exc) + else: + observer.on_next(value) + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return oftype diff --git a/addons/reactivex/operators/_onerrorresumenext.gd b/addons/reactivex/operators/_onerrorresumenext.gd new file mode 100644 index 0000000..667acb5 --- /dev/null +++ b/addons/reactivex/operators/_onerrorresumenext.gd @@ -0,0 +1,7 @@ +static func on_error_resume_next_( + second : Observable +) -> Callable: + var on_error_resume_next = func(source : Observable) -> Observable: + return GDRx.obs.on_error_resume_next([source, second]) + + return on_error_resume_next diff --git a/addons/reactivex/operators/_pairwise.gd b/addons/reactivex/operators/_pairwise.gd new file mode 100644 index 0000000..95cc690 --- /dev/null +++ b/addons/reactivex/operators/_pairwise.gd @@ -0,0 +1,41 @@ +static func pairwise_() -> Callable: + var pairwise = func(source : Observable) -> Observable: +# """Partially applied pairwise operator. +# +# Returns a new observable that triggers on the second and +# subsequent triggerings of the input observable. The Nth +# triggering of the input observable passes the arguments from +# the N-1th and Nth triggering as a pair. The argument passed to +# the N-1th triggering is held in hidden internal state until the +# Nth triggering occurs. +# +# Returns: +# An observable that triggers on successive pairs of +# observations from the input observable as an array. +# """ + var subscribe = func( + observer : ObserverBase, + _scheduler : SchedulerBase = null + ) -> DisposableBase: + var has_previous = RefValue.Set(false) + var previous = RefValue.Null() + + var on_next = func(x): + var pair = null + + if true: + var __ = LockGuard.new(source.lock) + if has_previous.v: + pair = Tuple.new([previous.v, x]) + else: + has_previous.v = true + previous.v = x + + if pair != null: + observer.on_next(pair) + + return source.subscribe(on_next, observer.on_error, observer.on_completed) + + return Observable.new(subscribe) + + return pairwise diff --git a/addons/reactivex/operators/_partition.gd b/addons/reactivex/operators/_partition.gd new file mode 100644 index 0000000..371cb00 --- /dev/null +++ b/addons/reactivex/operators/_partition.gd @@ -0,0 +1,75 @@ +static func partition_( + predicate : Callable = GDRx.basic.default_condition +) -> Callable: + + var partition = func(source : Observable) -> Array[Observable]: +# """The partially applied `partition` operator. +# +# Returns two observables which partition the observations of the +# source by the given function. The first will trigger +# observations for those values for which the predicate returns +# true. The second will trigger observations for those values +# where the predicate returns false. The predicate is executed +# once for each subscribed observer. Both also propagate all +# error observations arising from the source and each completes +# when the source completes. +# +# Args: +# source: Source obserable to partition. +# +# Returns: +# A list of observables. The first triggers when the +# predicate returns True, and the second triggers when the +# predicate returns False. +# """ + var not_predicate = func(x) -> bool: + return not predicate.call(x) + + var published = source.pipe2( + GDRx.op.publish(), + GDRx.op.ref_count() + ) + return [ + published.pipe1(GDRx.op.filter(predicate)), + published.pipe1(GDRx.op.filter(not_predicate)) + ] + + return partition + +static func partition_indexed_( + predicate_indexed : Callable = GDRx.basic.default_condition +) -> Callable: + + var partition_indexed = func(source : Observable) -> Array[Observable]: +# """The partially applied indexed partition operator. +# +# Returns two observables which partition the observations of the +# source by the given function. The first will trigger +# observations for those values for which the predicate returns +# true. The second will trigger observations for those values +# where the predicate returns false. The predicate is executed +# once for each subscribed observer. Both also propagate all +# error observations arising from the source and each completes +# when the source completes. +# +# Args: +# source: Source observable to partition. +# +# Returns: +# A list of observables. The first triggers when the +# predicate returns 'true', and the second triggers when the +# predicate returns 'false'. +# """ + var not_predicate_indexed = func(x, i : int) -> bool: + return not predicate_indexed.call(x, i) + + var published = source.pipe2( + GDRx.op.publish(), + GDRx.op.ref_count() + ) + return [ + published.pipe1(GDRx.op.filter_indexed(predicate_indexed)), + published.pipe1(GDRx.op.filter_indexed(not_predicate_indexed)) + ] + + return partition_indexed diff --git a/addons/reactivex/operators/_pluck.gd b/addons/reactivex/operators/_pluck.gd new file mode 100644 index 0000000..1ee3620 --- /dev/null +++ b/addons/reactivex/operators/_pluck.gd @@ -0,0 +1,29 @@ +static func pluck_(key) -> Callable: +# """Retrieves the value of a specified key using dict-like access (as in +# element[key]) from all elements in the Observable sequence. +# +# Args: +# key: The key to pluck. +# +# Returns a new Observable {Observable} sequence of key values. +# +# To pluck an attribute of each element, use pluck_attr. +# """ + var mapper = func(x : Dictionary): + return x[key] + + return GDRx.op.map(mapper) + +static func pluck_attr_(prop : String) -> Callable: +# """Retrieves the value of a specified property (using getattr) from +# all elements in the Observable sequence. +# +# Args: +# property: The property to pluck. +# +# Returns a new Observable {Observable} sequence of property values. +# +# To pluck values using dict-like access (as in element[key]) on each +# element, use pluck. +# """ + return GDRx.op.map(func(x): return x.get(prop)) diff --git a/addons/reactivex/operators/_publish.gd b/addons/reactivex/operators/_publish.gd new file mode 100644 index 0000000..c254a87 --- /dev/null +++ b/addons/reactivex/operators/_publish.gd @@ -0,0 +1,45 @@ +static func publish_(mapper = null) -> Callable: +# """Returns an observable sequence that is the result of invoking the +# mapper on a connectable observable sequence that shares a single +# subscription to the underlying sequence. This operator is a +# specialization of Multicast using a regular Subject. +# +# Example: +# >>> var res = GDRx.op.publish() +# >>> var res = GDRx.op.publish(func(x): return x) +# +# mapper: [Optional] Selector function which can use the +# multicasted source sequence as many times as needed, without causing +# multiple subscriptions to the source sequence. Subscribers to the +# given source will receive all notifications of the source from the +# time of the subscription on. +# +# Returns: +# An observable sequence that contains the elements of a sequence +# produced by multicasting the source sequence within a mapper +# function. +# """ + if mapper != null: + var factory = func(_scheduler : SchedulerBase = null) -> Subject: + return Subject.new() + + return GDRx.op.multicast(null, factory, mapper) + + var subject : Subject = Subject.new() + return GDRx.op.multicast(subject) + +static func share_() -> Callable: +# """Share a single subscription among multple observers. +# +# Returns a new Observable that multicasts (shares) the original +# Observable. As long as there is at least one Subscriber this +# Observable will be subscribed and emitting data. When all +# subscribers have unsubscribed it will unsubscribe from the source +# Observable. +# +# This is an alias for a composed publish() and ref_count(). +# """ + return GDRx.pipe.compose2( + GDRx.op.publish(), + GDRx.op.ref_count() + ) diff --git a/addons/reactivex/operators/_publishvalue.gd b/addons/reactivex/operators/_publishvalue.gd new file mode 100644 index 0000000..adb96f4 --- /dev/null +++ b/addons/reactivex/operators/_publishvalue.gd @@ -0,0 +1,17 @@ +static func publish_value_( + initial_value, + mapper = null +) -> Callable: + if mapper != null: + + var subject_factory = func( + _scheduler : SchedulerBase + ) -> BehaviorSubject: + return BehaviorSubject.new(initial_value) + + return GDRx.op.multicast( + null, subject_factory, mapper + ) + + var subject = BehaviorSubject.new(initial_value) + return GDRx.op.multicast(subject) diff --git a/addons/reactivex/operators/_reduce.gd b/addons/reactivex/operators/_reduce.gd new file mode 100644 index 0000000..72ee49b --- /dev/null +++ b/addons/reactivex/operators/_reduce.gd @@ -0,0 +1,37 @@ +static func reduce_( + accumulator : Callable, seed_ = GDRx.util.GetNotSet() +) -> Callable: +# """Applies an accumulator function over an observable sequence, +# returning the result of the aggregation as a single element in the +# result sequence. The specified seed value is used as the initial +# accumulator value. +# +# For aggregation behavior with incremental intermediate results, see +# `scan()`. +# +# Examples: +# >>> var res = GDRx.op.reduce(func(acc, x): return acc + x) +# >>> var res = GDRx.op.reduce(func(acc, x): return acc + x, 0) +# +# Args: +# accumulator: An accumulator function to be +# invoked on each element. +# seed: Optional initial accumulator value. +# +# Returns: +# An operator function that takes an observable source and returns +# an observable sequence containing a single element with the +# final accumulator value. +# """ + if !(is_instance_of(seed_, GDRx.util.NotSet)): + var _seed = seed_ + var scanner = GDRx.op.scan(accumulator, _seed) + return GDRx.pipe.compose2( + scanner, + GDRx.op.last_or_default(_seed) + ) + + return GDRx.pipe.compose2( + GDRx.op.scan(accumulator), + GDRx.last() + ) diff --git a/addons/reactivex/operators/_repeat.gd b/addons/reactivex/operators/_repeat.gd new file mode 100644 index 0000000..eace73d --- /dev/null +++ b/addons/reactivex/operators/_repeat.gd @@ -0,0 +1,34 @@ +static func repeat_( + repeat_count = null +) -> Callable: + + var repeat = func(source : Observable) -> Observable: +# """Repeats the observable sequence a specified number of times. +# If the repeat count is not specified, the sequence repeats +# indefinitely. +# +# Examples: +# >>> var repeated = source.pipe1(GDRx.op.repeat()) +# >>> var repeated = source.pipe1(GDRx.op.repeat(42)) +# +# Args: +# source: The observable source to repeat. +# +# Returns: +# The observable sequence producing the elements of the given +# sequence repeatedly. +# """ + var gen : IterableBase + if repeat_count == null: + gen = InfiniteIterable.new(source) + else: + var repeat_count_ : int = repeat_count + var repeats_ : Array[Observable] = [] + for __ in range(repeat_count_): repeats_.append(source) + gen = GDRx.to_iterable(repeats_) + + return GDRx.obs.defer( + func(__): return GDRx.obs.concat_with_iterable(gen) + ) + + return repeat diff --git a/addons/reactivex/operators/_replay.gd b/addons/reactivex/operators/_replay.gd new file mode 100644 index 0000000..bc2d92b --- /dev/null +++ b/addons/reactivex/operators/_replay.gd @@ -0,0 +1,52 @@ +static func replay_( + mapper = null, + buffer_size = null, + window = null, + scheduler : SchedulerBase = null +) -> Callable: +# """Returns an observable sequence that is the result of invoking the +# mapper on a connectable observable sequence that shares a single +# subscription to the underlying sequence replaying notifications +# subject to a maximum time length for the replay buffer. +# +# This operator is a specialization of Multicast using a +# ReplaySubject. +# +# Examples: +# >>> var res = GDRx.op.replay(null, 3) +# >>> var res = GDRx.op.replay(null, 3, 500) +# >>> var res = GDRx.op.replay(func(x): return x.pipe2(GDRx.op.take(6), GDRx.op.repeat()), 3, 500) +# +# Args: +# mapper: [Optional] Selector function which can use the multicasted +# source sequence as many times as needed, without causing +# multiple subscriptions to the source sequence. Subscribers to +# the given source will receive all the notifications of the +# source subject to the specified replay buffer trimming policy. +# buffer_size: [Optional] Maximum element count of the replay +# buffer. +# window: [Optional] Maximum time length of the replay buffer. +# scheduler: [Optional] Scheduler the observers are invoked on. +# +# Returns: +# An observable sequence that contains the elements of a +# sequence produced by multicasting the source sequence within a +# mapper function. +# """ + if mapper != null: + + var subject_factory = func( + scheduler : SchedulerBase + ) -> ReplaySubject: + var _buffer_size : int = buffer_size + var _window : float = window + return ReplaySubject.new(_buffer_size, _window, scheduler) + + return GDRx.op.multicast( + null, subject_factory, mapper + ) + + var _buffer_size : int = buffer_size + var _window : float = window + var rs : ReplaySubject = ReplaySubject.new(_buffer_size, _window, scheduler) + return GDRx.op.multicast(rs) diff --git a/addons/reactivex/operators/_retry.gd b/addons/reactivex/operators/_retry.gd new file mode 100644 index 0000000..6ba1fac --- /dev/null +++ b/addons/reactivex/operators/_retry.gd @@ -0,0 +1,28 @@ +static func retry_(retry_count : int = -1) -> Callable: +# """Repeats the source observable sequence the specified number of +# times or until it successfully terminates. If the retry count is +# not specified, it retries indefinitely. +# +# Examples: +# >>> var retried = GDRx.op.retry() +# >>> var retried = GDRx.op.retry(42) +# +# Args: +# retry_count: [Optional] Number of times to retry the sequence. +# If not provided, retry the sequence indefinitely. +# +# Returns: +# An observable sequence producing the elements of the given +# sequence repeatedly until it terminates successfully. +# """ + var retry = func(source : Observable) -> Observable: + var gen : IterableBase + if retry_count < 0: + gen = InfiniteIterable.new(source) + else: + var _gen_lst = [] ; for __ in range(retry_count): _gen_lst.append(source) + gen = GDRx.to_iterable(_gen_lst) + + return GDRx.obs.catch_with_iterable(gen) + + return retry diff --git a/addons/reactivex/operators/_sample.gd b/addons/reactivex/operators/_sample.gd new file mode 100644 index 0000000..f2c1978 --- /dev/null +++ b/addons/reactivex/operators/_sample.gd @@ -0,0 +1,67 @@ +static func sample_observable( + source : Observable, sampler : Observable +) -> Observable: + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var at_end = RefValue.Set(false) + var has_value = RefValue.Set(false) + var value = RefValue.Null() + + var sample_subscribe = func(__ = null): + if has_value.v: + has_value.v = false + observer.on_next(value.v) + + if at_end.v: + observer.on_completed() + + var on_next = func(new_value): + has_value.v = true + value.v = new_value + + var on_completed = func(): + at_end.v = true + + return CompositeDisposable.new([ + source.subscribe( + on_next, observer.on_error, on_completed, + scheduler + ), + sampler.subscribe( + sample_subscribe, + observer.on_error, + sample_subscribe, + scheduler + ) + ]) + + return Observable.new(subscribe) + +static func sample_( + sampler : Observable, + sampler_time : float = NAN, + scheduler : SchedulerBase = null +) -> Callable: +# """Samples the observable sequence at each interval. +# +# Examples: +# >>> var res = sample.call(source) +# +# Args: +# source: Source sequence to sample. +# +# Returns: +# Sampled observable sequence. +# """ + var sample = func(source : Observable) -> Observable: + if sampler_time != NAN: + return sample_observable( + source, GDRx.obs.interval(sampler_time, scheduler) + ) + else: + return sample_observable(source, sampler) + + return sample diff --git a/addons/reactivex/operators/_scan.gd b/addons/reactivex/operators/_scan.gd new file mode 100644 index 0000000..85dd30c --- /dev/null +++ b/addons/reactivex/operators/_scan.gd @@ -0,0 +1,39 @@ +static func scan_( + accumulator : Callable, + seed_ = GDRx.util.GetNotSet() +) -> Callable: + var has_seed = !is_instance_of(seed_, GDRx.util.NotSet) + + var scan = func(source : Observable) -> Observable: +# """Partially applied scan operator. +# +# Applies an accumulator function over an observable sequence and +# returns each intermediate result. +# +# Examples: +# >>> var scanned = scan.call(source) +# +# Args: +# source: The observable source to scan. +# +# Returns: +# An observable sequence containing the accumulated values. +# """ + var factory = func(_scheduler : SchedulerBase) -> Observable: + var has_accumulation = RefValue.Set(false) + var accumulation = RefValue.Null() + + var projection = func(x): + if has_accumulation.v: + accumulation.v = accumulator.call(accumulation.v, x) + else: + accumulation.v = accumulator.call(seed_, x) if has_seed else x + has_accumulation.v = true + + return accumulation.v + + return source.pipe1(GDRx.op.map(projection)) + + return GDRx.obs.defer(factory) + + return scan diff --git a/addons/reactivex/operators/_sequenceequal.gd b/addons/reactivex/operators/_sequenceequal.gd new file mode 100644 index 0000000..5852627 --- /dev/null +++ b/addons/reactivex/operators/_sequenceequal.gd @@ -0,0 +1,121 @@ +static func sequence_equal_( + second, # Array or Observable + comparer = null, + second_it : IterableBase = null +) -> Callable: + var comparer_ = comparer if comparer != null else GDRx.basic.default_comparer + var second_ : Observable + if second is Array: + second_ = GDRx.obs.from_iterable(GDRx.util.Iter(second)) + else: + second_ = second if second_it == null else GDRx.obs.from_iterable(second_it) + + var sequence_equal = func(source : Observable) -> Observable: +# """Determines whether two sequences are equal by comparing the +# elements pairwise using a specified equality comparer. +# +# Examples: +# >>> var res = sequence_equal([1,2,3]) +# >>> var res = sequence_equal([{ "value": 42 }], func(x, y): return x.value == y.value) +# >>> var res = sequence_equal(GDRx.obs.return_value(42)) +# >>> var res = sequence_equal( +# GDRx.obs.return_value({ "value": 42 }), +# func(x, y): return x.value == y.value +# ) +# +# Args: +# source: Source obserable to compare. +# +# Returns: +# An observable sequence that contains a single element which +# indicates whether both sequences are of equal length and their +# corresponding elements are equal according to the specified +# equality comparer. +# """ + var first = source + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var donel = [false] + var doner = [false] + var ql = [] + var qr = [] + + var on_next1 = func(x): + if qr.size() > 0: + var v = qr.pop_front() + var equal = RefValue.Set(true) + if GDRx.try(func(): + equal.v = comparer_.call(v, x) + ) \ + .catch("Error", func(e): + observer.on_error(e) + ) \ + .end_try_catch(): return + + if not equal.v: + observer.on_next(false) + observer.on_completed() + + elif doner[0]: + observer.on_next(false) + observer.on_completed() + else: + ql.append(x) + + var on_completed1 = func(): + donel[0] = true + if ql.is_empty(): + if not qr.is_empty(): + observer.on_next(false) + observer.on_completed() + elif doner[0]: + observer.on_next(true) + observer.on_completed() + + var on_next2 = func(x): + if ql.size() > 0: + var v = ql.pop_front() + var equal = RefValue.Set(true) + if GDRx.try(func(): + equal.v = comparer_.call(v, x) + ) \ + .catch("Error", func(e): + observer.on_error(e) + ) \ + .end_try_catch(): return + + if not equal.v: + observer.on_next(false) + observer.on_completed() + + elif donel[0]: + observer.on_next(false) + observer.on_completed() + else: + qr.append(x) + + var on_completed2 = func(): + doner[0] = true + if qr.is_empty(): + if not ql.is_empty(): + observer.on_next(false) + observer.on_completed() + elif donel[0]: + observer.on_next(true) + observer.on_completed() + + var subscription1 = first.subscribe( + on_next1, observer.on_error, on_completed1, scheduler + ) + var subscription2 = second_.subscribe( + on_next2, observer.on_error, on_completed2, scheduler + ) + + return CompositeDisposable.new([subscription1, subscription2]) + + return Observable.new(subscribe) + + return sequence_equal diff --git a/addons/reactivex/operators/_single.gd b/addons/reactivex/operators/_single.gd new file mode 100644 index 0000000..6730524 --- /dev/null +++ b/addons/reactivex/operators/_single.gd @@ -0,0 +1,29 @@ +static func single_( + predicate = null +) -> Callable: + +# """Returns the only element of an observable sequence that satisfies the +# condition in the optional predicate, and reports an error if there +# is not exactly one element in the observable sequence. +# +# Example: +# >>> var res = GDRx.op.single() +# >>> var res = GDRx.op.single(func(x): return x == 42) +# +# Args: +# predicate -- [Optional] A predicate function to evaluate for +# elements in the source sequence. +# +# Returns: +# An observable sequence containing the single element in the +# observable sequence that satisfies the condition in the predicate. +# """ + + if predicate != null: + var _predicate : Callable = predicate + return GDRx.pipe.compose2( + GDRx.op.filter(_predicate), + GDRx.op.single() + ) + else: + return GDRx.op.single_or_default_async(false) diff --git a/addons/reactivex/operators/_singleordefault.gd b/addons/reactivex/operators/_singleordefault.gd new file mode 100644 index 0000000..e394e0d --- /dev/null +++ b/addons/reactivex/operators/_singleordefault.gd @@ -0,0 +1,68 @@ +static func single_or_default_async_( + has_default : bool = false, default_value = null +) -> Callable: + var single_or_default_async = func(source : Observable) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var value = RefValue.Set(default_value) + var seen_value = RefValue.Set(false) + + var on_next = func(x): + if seen_value.v: + observer.on_error(RxBaseError.new("Sequence contains more than one element")) + else: + value.v = x + seen_value.v = true + + var on_completed = func(): + if not seen_value.v and not has_default: + observer.on_error(SequenceContainsNoElementsError.new()) + else: + observer.on_next(value.v) + observer.on_completed() + + return source.subscribe( + on_next, observer.on_error, on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return single_or_default_async + +static func single_or_default_( + predicate = null, + default_value = null +) -> Callable: +# """Returns the only element of an observable sequence that matches +# the predicate, or a default value if no such element exists this +# method reports an error if there is more than one element in the +# observable sequence. +# +# Examples: +# >>> var res = GDRx.op.single_or_default() +# >>> var res = GDRx.op.single_or_default(func(x): return x == 42) +# >>> var res = GDRx.op.single_or_default(func(x): return x == 42, 0) +# >>> var res = GDRx.op.single_or_default(null, 0) +# +# Args: +# predicate -- [Optional] A predicate function to evaluate for +# elements in the source sequence. +# default_value -- [Optional] The default value if the index is +# outside the bounds of the source sequence. +# +# Returns: +# An observable Sequence containing the single element in the +# observable sequence that satisfies the condition in the predicate, +# or a default value if no such element exists. +# """ + if predicate != null: + var _predicate : Callable = predicate + return GDRx.pipe.compose2( + GDRx.op.filter(_predicate), + GDRx.op.single_or_default(null, default_value) + ) + else: + return single_or_default_async_(true, default_value) diff --git a/addons/reactivex/operators/_skip.gd b/addons/reactivex/operators/_skip.gd new file mode 100644 index 0000000..eed4803 --- /dev/null +++ b/addons/reactivex/operators/_skip.gd @@ -0,0 +1,38 @@ +static func skip_(count : int) -> Callable: + if count < 0: + ArgumentOutOfRangeError.new().throw() + count = 0 + + var skip = func(source : Observable) -> Observable: +# """The skip operator. +# +# Bypasses a specified number of elements in an observable sequence +# and then returns the remaining elements. +# +# Args: +# source: The source observable. +# +# Returns: +# An observable sequence that contains the elements that occur +# after the specified index in the input sequence. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var remaining = RefValue.Set(count) + + var on_next = func(value): + if remaining.v <= 0: + observer.on_next(value) + else: + remaining.v -= 1 + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return skip diff --git a/addons/reactivex/operators/_skiplast.gd b/addons/reactivex/operators/_skiplast.gd new file mode 100644 index 0000000..cfff5a8 --- /dev/null +++ b/addons/reactivex/operators/_skiplast.gd @@ -0,0 +1,43 @@ +static func skip_last_(count : int) -> Callable: + var skip_last = func(source : Observable) -> Observable: +# """Bypasses a specified number of elements at the end of an +# observable sequence. +# +# This operator accumulates a queue with a length enough to store +# the first `count` elements. As more elements are received, +# elements are taken from the front of the queue and produced on +# the result sequence. This causes elements to be delayed. +# +# Args: +# count: Number of elements to bypass at the end of the +# source sequence. +# +# Returns: +# An observable sequence containing the source sequence +# elements except for the bypassed ones at the end. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var q : Array = [] + + var on_next = func(value): + var front = null + source.lock.lock() + q.append(value) + if q.size() > count: + front = q.pop_front() + source.lock.unlock() + + if front != null: + observer.on_next(front) + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return skip_last diff --git a/addons/reactivex/operators/_skiplastwithtime.gd b/addons/reactivex/operators/_skiplastwithtime.gd new file mode 100644 index 0000000..81366d5 --- /dev/null +++ b/addons/reactivex/operators/_skiplastwithtime.gd @@ -0,0 +1,58 @@ +static func skip_last_with_time_( + duration : float, scheduler : SchedulerBase = null +) -> Callable: +# """Skips elements for the specified duration from the end of the +# observable source sequence. +# +# Example: +# >>> var res = GDRx.op.skip_last_with_time(5.0) +# +# This operator accumulates a queue with a length enough to store +# elements received during the initial duration window. As more +# elements are received, elements older than the specified duration +# are taken from the queue and produced on the result sequence. This +# causes elements to be delayed with duration. +# +# Args: +# duration: Duration for skipping elements from the end of the +# sequence. +# scheduler: Scheduler to use for time handling. +# +# Returns: +# An observable sequence with the elements skipped during the +# specified duration from the end of the source sequence. +# """ + var _duration = RefValue.Set(duration) + + var skip_last_with_time = func(source : Observable) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler_ : SchedulerBase = null + ) -> DisposableBase: + var _scheduler : SchedulerBase + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = SceneTreeTimeoutScheduler.singleton() + _duration.v = duration + var q : Array[Dictionary] = [] + + var on_next = func(x): + var now = _scheduler.now() + q.append({"interval":now, "value":x}) + while not q.is_empty() and now - q[0]["interval"] >= _duration.v: + observer.on_next(q.pop_front()["value"]) + + var on_completed = func(): + var now = _scheduler.now() + while not q.is_empty() and now - q[0]["interval"] >= _duration.v: + observer.on_next(q.pop_front()["value"]) + observer.on_completed() + + return source.subscribe( + on_next, observer.on_error, on_completed, + _scheduler + ) + + return Observable.new(subscribe) + + return skip_last_with_time diff --git a/addons/reactivex/operators/_skipuntil.gd b/addons/reactivex/operators/_skipuntil.gd new file mode 100644 index 0000000..00cdda7 --- /dev/null +++ b/addons/reactivex/operators/_skipuntil.gd @@ -0,0 +1,57 @@ +static func skip_until_(other : Observable) -> Callable: +# """Returns the values from the source observable sequence only after +# the other observable sequence produces a value. +# +# Args: +# other: The observable sequence that triggers propagation of +# elements of the source sequence. +# +# Returns: +# An observable sequence containing the elements of the source +# sequence starting from the point the other sequence triggered +# propagation. +# """ + + var obs : Observable = other + + var skip_until = func(source : Observable) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var is_open = [false] + + var on_next = func(left): + if is_open[0]: + observer.on_next(left) + + var on_completed = func(): + if is_open[0]: + observer.on_completed() + + var subs = source.subscribe( + on_next, observer.on_error, on_completed, + scheduler + ) + var subscriptions = CompositeDisposable.new([subs]) + + var right_subscription = SingleAssignmentDisposable.new() + subscriptions.add(right_subscription) + + var on_next2 = func(__): + is_open[0] = true + right_subscription.dispose() + + var on_completed2 = func(): + right_subscription.dispose() + + right_subscription.disposable = obs.subscribe( + on_next2, observer.on_error, on_completed2, + scheduler + ) + + return subscriptions + + return Observable.new(subscribe) + + return skip_until diff --git a/addons/reactivex/operators/_skipuntilwithtime.gd b/addons/reactivex/operators/_skipuntilwithtime.gd new file mode 100644 index 0000000..9549bd4 --- /dev/null +++ b/addons/reactivex/operators/_skipuntilwithtime.gd @@ -0,0 +1,59 @@ +static func skip_until_with_time_( + start_time : float, + time_absolute : bool = false, + scheduler : SchedulerBase = null +) -> Callable: + var skip_until_with_time = func(source : Observable) -> Observable: +# """Skips elements from the observable source sequence until the +# specified start time. +# +# Errors produced by the source sequence are always forwarded to +# the result sequence, even if the error occurs before the start +# time. +# +# Examples: +# >>> var res = source.pipe1(GDRx.op.skip_until_with_time(datetime)) +# >>> var res = source.pipe1(GDRx.op.skip_until_with_time(5.0)) +# +# Args: +# start_time: Time to start taking elements from the source +# sequence. If this value is less than or equal to +# `datetime.utcnow`, no elements will be skipped. +# +# Returns: +# An observable sequence with the elements skipped until the +# specified start time. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler_ : SchedulerBase = null + ) -> DisposableBase: + var _scheduler : SchedulerBase + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = SceneTreeTimeoutScheduler.singleton() + + var open = [false] + + var on_next = func(x): + if open[0]: + observer.on_next(x) + + var subscription = source.subscribe( + on_next, observer.on_error, observer.on_completed, + scheduler_ + ) + + var action = func(_scheduler : SchedulerBase, _state = null): + open[0] = true + + var disp : DisposableBase + if time_absolute: + disp = _scheduler.schedule_absolute(start_time, action) + else: + disp = _scheduler.schedule_relative(start_time, action) + return CompositeDisposable.new([disp, subscription]) + + return Observable.new(subscribe) + + return skip_until_with_time diff --git a/addons/reactivex/operators/_skipwhile.gd b/addons/reactivex/operators/_skipwhile.gd new file mode 100644 index 0000000..5636eb1 --- /dev/null +++ b/addons/reactivex/operators/_skipwhile.gd @@ -0,0 +1,63 @@ +static func skip_while_(predicate : Callable) -> Callable: + var skip_while = func(source : Observable) -> Observable: + +# """Bypasses elements in an observable sequence as long as a +# specified condition is true and then returns the remaining +# elements. The element's index is used in the logic of the +# predicate function. +# +# Example: +# >>> skip_while.call(source) +# +# Args: +# source: The source observable to skip elements from. +# +# Returns: +# An observable sequence that contains the elements from the +# input sequence starting at the first element in the linear +# series that does not pass the test specified by predicate. +# """ + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var running = RefValue.Set(false) + + var on_next = func(value): + if not running.v: + if GDRx.try(func(): + running.v = not predicate.call(value) + ) \ + .catch("Error", func(exn): + observer.on_error(exn) + ) \ + .end_try_catch(): return + + if running.v: + observer.on_next(value) + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return skip_while + +func skip_while_indexed_(predicate : Callable) -> Callable: + var indexer = func(x, i : int) -> Tuple: + return Tuple.new([x, i]) + + var skipper = func(x : Tuple) -> bool: + return predicate.call(x.at(0), x.at(1)) + + var mapper = func(x : Tuple): + return x.at(0) + + return GDRx.pipe.compose3( + GDRx.op.map_indexed(indexer), + GDRx.op.skip_while(skipper), + GDRx.op.map(mapper) + ) diff --git a/addons/reactivex/operators/_skipwithtime.gd b/addons/reactivex/operators/_skipwithtime.gd new file mode 100644 index 0000000..dec584f --- /dev/null +++ b/addons/reactivex/operators/_skipwithtime.gd @@ -0,0 +1,58 @@ +static func skip_with_time_( + duration : float, + scheduler : SchedulerBase = null +) -> Callable: + var skip_with_time = func(source : Observable) -> Observable: +# """Skips elements for the specified duration from the start of +# the observable source sequence. +# +# Args: +# >>> var res = GDRx.op.skip_with_time(5.0) +# +# Specifying a zero value for duration doesn't guarantee no +# elements will be dropped from the start of the source sequence. +# This is a side-effect of the asynchrony introduced by the +# scheduler, where the action that causes callbacks from the +# source sequence to be forwarded may not execute immediately, +# despite the zero due time. +# +# Errors produced by the source sequence are always forwarded to +# the result sequence, even if the error occurs before the +# duration. +# +# Args: +# duration: Duration for skipping elements from the start of +# the sequence. +# +# Returns: +# An observable sequence with the elements skipped during the +# specified duration from the start of the source sequence. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler_ : SchedulerBase = null + ) -> DisposableBase: + var _scheduler : SchedulerBase + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = SceneTreeTimeoutScheduler.singleton() + var open = [false] + + var action = func(_scheduler : SchedulerBase, _state = null): + open[0] = true + + var t = _scheduler.schedule_relative(duration, action) + + var on_next = func(x): + if open[0]: + observer.on_next(x) + + var d = source.subscribe( + on_next, observer.on_error, observer.on_completed, + scheduler_ + ) + return CompositeDisposable.new([t, d]) + + return Observable.new(subscribe) + + return skip_with_time diff --git a/addons/reactivex/operators/_slice.gd b/addons/reactivex/operators/_slice.gd new file mode 100644 index 0000000..990ea5f --- /dev/null +++ b/addons/reactivex/operators/_slice.gd @@ -0,0 +1,64 @@ +static func slice_( + start : int = 0, + stop : int = GDRx.util.MAX_SIZE, + step : int = 1 +) -> Callable: + var _start = start + var _stop = stop + var _step = step + + var pipeline : Array[Callable] = [] + + var slice = func(source : Observable) -> Observable: +# """The partially applied slice operator. +# +# Slices the given observable. It is basically a wrapper around the operators +# GDRx.op.skip, +# GDRx.op.skip_last, +# GDRx.op.take, +# GDRx.op.take_last and +# GDRx.op.filter. +# +# The following diagram helps you remember how slices works with streams. +# +# Positive numbers are relative to the start of the events, while negative +# numbers are relative to the end (close) of the stream. +# +# .. code:: +# +# r---e---a---c---t---i---v---e---! +# 0 1 2 3 4 5 6 7 8 +# -8 -7 -6 -5 -4 -3 -2 -1 0 +# +# Examples: +# >>> var result = source.pipe1(GDRx.op.slice(1, 10)) +# >>> var result = source.pipe1(GDRx.op.slice(1, -2)) +# >>> var result = source.pipe1(GDRx.op.slice(1, -1, 2)) +# +# Args: +# source: Observable to slice +# +# Returns: +# A sliced observable sequence. +# """ + if _stop >= 0: + pipeline.append(GDRx.op.take(_stop)) + + if _start > 0: + pipeline.append(GDRx.op.skip(_start)) + elif _start < 0: + pipeline.append(GDRx.op.take_last(-_start)) + + if _stop < 0: + pipeline.append(GDRx.op.take_last(-_stop)) + + if _step > 1: + pipeline.append( + GDRx.filter_indexed(func(_x, i): return i % int(_step == 0)) + ) + elif _step < 0: + ArgumentOutOfRangeError.raise() + + return source.pipe(GDRx.util.Iter(pipeline)) + + return slice diff --git a/addons/reactivex/operators/_some.gd b/addons/reactivex/operators/_some.gd new file mode 100644 index 0000000..5ec0f77 --- /dev/null +++ b/addons/reactivex/operators/_some.gd @@ -0,0 +1,48 @@ +static func some_( + predicate = null +) -> Callable: + var some = func(source : Observable) -> Observable: +# """Partially applied operator. +# +# Determines whether some element of an observable sequence satisfies a +# condition if present, else if some items are in the sequence. +# +# Example: +# >>> var obs = some.call(source) +# +# Args: +# predicate -- A function to test each element for a condition. +# +# Returns: +# An observable sequence containing a single element +# determining whether some elements in the source sequence +# pass the test in the specified predicate if given, else if +# some items are in the sequence. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var on_next = func(__): + observer.on_next(true) + observer.on_completed() + + var on_error = func(): + observer.on_next(false) + observer.on_completed() + + return source.subscribe( + on_next, observer.on_error, on_error, + scheduler + ) + + if predicate != null: + var predicate_ : Callable = predicate + return source.pipe2( + GDRx.op.filter(predicate_), + some_() + ) + + return Observable.new(subscribe) + + return some diff --git a/addons/reactivex/operators/_startswith.gd b/addons/reactivex/operators/_startswith.gd new file mode 100644 index 0000000..df7ce92 --- /dev/null +++ b/addons/reactivex/operators/_startswith.gd @@ -0,0 +1,17 @@ +static func start_with_(args) -> Callable: + var start_with = func(source : Observable) -> Observable: +# """Partially applied start_with operator. +# +# Prepends a sequence of values to an observable sequence. +# +# Example: +# >>> start_with.call(source) +# +# Returns: +# The source sequence prepended with the specified values. +# """ + var start = GDRx.obs.from_iterable(GDRx.to_iterable(args)) + var sequence = [start, source] + return GDRx.obs.concat_with_iterable(GDRx.to_iterable(sequence)) + + return start_with diff --git a/addons/reactivex/operators/_subscribeon.gd b/addons/reactivex/operators/_subscribeon.gd new file mode 100644 index 0000000..06e1c97 --- /dev/null +++ b/addons/reactivex/operators/_subscribeon.gd @@ -0,0 +1,39 @@ +static func subscribe_on_(scheduler : SchedulerBase) -> Callable: + var subscribe_on = func(source : Observable) -> Observable: +# """Subscribe on the specified scheduler. +# +# Wrap the source sequence in order to run its subscription and +# unsubscription logic on the specified scheduler. This operation +# is not commonly used; see the remarks section for more +# information on the distinction between subscribe_on and +# observe_on. +# +# This only performs the side-effects of subscription and +# unsubscription on the specified scheduler. In order to invoke +# observer callbacks on a scheduler, use observe_on. +# +# Args: +# source: The source observable.. +# +# Returns: +# The source sequence whose subscriptions and +# un-subscriptions happen on the specified scheduler. +# """ + var subscribe = func( + observer : ObserverBase, __ : SchedulerBase = null + ) -> DisposableBase: + var m = SingleAssignmentDisposable.new() + var d = SerialDisposable.new() + d.disposable = m + + var action = func(scheduler : SchedulerBase, _state = null): + d.disposable = ScheduledDisposable.new( + scheduler, source.subscribe(observer) + ) + + m.disposable = scheduler.schedule(action) + return d + + return Observable.new(subscribe) + + return subscribe_on diff --git a/addons/reactivex/operators/_sum.gd b/addons/reactivex/operators/_sum.gd new file mode 100644 index 0000000..349c7e5 --- /dev/null +++ b/addons/reactivex/operators/_sum.gd @@ -0,0 +1,11 @@ +static func sum_( + key_mapper = null +) -> Callable: + if key_mapper != null: + var key_mapper_ : Callable = key_mapper + return GDRx.pipe.compose2(GDRx.op.map(key_mapper_), GDRx.op.sum()) + + var accumulator = func(prev : float, curr : float) -> float: + return prev + curr + + return GDRx.op.reduce(accumulator, 0) diff --git a/addons/reactivex/operators/_switchlatest.gd b/addons/reactivex/operators/_switchlatest.gd new file mode 100644 index 0000000..7873f39 --- /dev/null +++ b/addons/reactivex/operators/_switchlatest.gd @@ -0,0 +1,69 @@ +static func switch_latest_() -> Callable: + var switch_latest = func(source : Observable) -> Observable: +# """Partially applied switch_latest operator. +# +# Transforms an observable sequence of observable sequences into +# an observable sequence producing values only from the most +# recent observable sequence. +# +# Returns: +# An observable sequence that at any point in time produces +# the elements of the most recent inner observable sequence +# that has been received. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var inner_subscription = SerialDisposable.new() + var has_latest = [false] + var is_stopped = [false] + var latest = [0] + + var on_next = func(inner_source : Observable): + var d = SingleAssignmentDisposable.new() + var _id + if true: + var __ = LockGuard.new(source.lock) + latest[0] += 1 + _id = latest[0] + has_latest[0] = true + inner_subscription.disposable = d + + var obs = inner_source + + var on_next = func(x): + if latest[0] == _id: + observer.on_next(x) + + var on_error = func(e): + if latest[0] == _id: + observer.on_error(e) + + var on_completed = func(): + if latest[0] == _id: + has_latest[0] = false + if is_stopped[0]: + observer.on_completed() + + d.disposable = obs.subscribe( + on_next, on_error, on_completed, + scheduler + ) + + var on_completed = func(): + is_stopped[0] = true + if not has_latest[0]: + observer.on_completed() + + var subscription = source.subscribe( + on_next, observer.on_error, on_completed, + scheduler + ) + return CompositeDisposable.new([ + subscription, inner_subscription + ]) + + return Observable.new(subscribe) + + return switch_latest diff --git a/addons/reactivex/operators/_take.gd b/addons/reactivex/operators/_take.gd new file mode 100644 index 0000000..f5c4386 --- /dev/null +++ b/addons/reactivex/operators/_take.gd @@ -0,0 +1,41 @@ +static func take_(count : int) -> Callable: + if count < 0: + ArgumentOutOfRangeError.raise() + count = 0 + + var take = func(source : Observable) -> Observable: +# """Returns a specified number of contiguous elements from the start of +# an observable sequence. +# +# >>> take.call(source) +# +# Keyword arguments: +# count -- The number of elements to return. +# +# Returns an observable sequence that contains the specified number of +# elements from the start of the input sequence. +# """ + if count == 0: + return GDRx.obs.empty() + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var remaining = RefValue.Set(count) + + var on_next = func(value): + if remaining.v > 0: + remaining.v -= 1 + observer.on_next(value) + if not bool(remaining.v): + observer.on_completed() + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return take diff --git a/addons/reactivex/operators/_takelast.gd b/addons/reactivex/operators/_takelast.gd new file mode 100644 index 0000000..52c16d8 --- /dev/null +++ b/addons/reactivex/operators/_takelast.gd @@ -0,0 +1,45 @@ +static func take_last_(count : int) -> Callable: + var take_last = func(source : Observable) -> Observable: +# """Returns a specified number of contiguous elements from the end of an +# observable sequence. +# +# Example: +# >>> var res = take_last.call(source) +# +# This operator accumulates a buffer with a length enough to store +# elements count elements. Upon completion of the source sequence, this +# buffer is drained on the result sequence. This causes the elements to be +# delayed. +# +# Args: +# source: Number of elements to take from the end of the source +# sequence. +# +# Returns: +# An observable sequence containing the specified number of elements +# from the end of the source sequence. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var q : Array = [] + + var on_next = func(x): + q.append(x) + if q.size() > count: + q.pop_front() + + var on_completed = func(): + while !q.is_empty(): + observer.on_next(q.pop_front()) + observer.on_completed() + + return source.subscribe( + on_next, observer.on_error, on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return take_last diff --git a/addons/reactivex/operators/_takelastbuffer.gd b/addons/reactivex/operators/_takelastbuffer.gd new file mode 100644 index 0000000..0db0a4b --- /dev/null +++ b/addons/reactivex/operators/_takelastbuffer.gd @@ -0,0 +1,46 @@ +static func take_last_buffer_(count : int) -> Callable: + var take_last_buffer = func(source : Observable) -> Observable: +# """Returns an array with the specified number of contiguous +# elements from the end of an observable sequence. +# +# Example: +# >>> var res = take_last.call(source) +# +# This operator accumulates a buffer with a length enough to +# store elements count elements. Upon completion of the source +# sequence, this buffer is drained on the result sequence. This +# causes the elements to be delayed. +# +# Args: +# source: Source observable to take elements from. +# +# Returns: +# An observable sequence containing a single list with the +# specified number of elements from the end of the source +# sequence. +# """ + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var q : Array = [] + + var on_next = func(x): + var __ = LockGuard.new(source.lock) + q.append(x) + if q.size() > count: + q.pop_front() + + var on_completed = func(): + observer.on_next(q) + observer.on_completed() + + return source.subscribe( + on_next, observer.on_error, on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return take_last_buffer diff --git a/addons/reactivex/operators/_takelastwithtime.gd b/addons/reactivex/operators/_takelastwithtime.gd new file mode 100644 index 0000000..79e42ed --- /dev/null +++ b/addons/reactivex/operators/_takelastwithtime.gd @@ -0,0 +1,58 @@ +static func take_last_with_time_( + duration : float, + scheduler : SchedulerBase = null +) -> Callable: + var take_last_with_time = func(source : Observable) -> Observable: +# """Returns elements within the specified duration from the end +# of the observable source sequence. +# +# Example: +# >>> var res = take_last_with_time.call(source) +# +# This operator accumulates a queue with a length enough to store +# elements received during the initial duration window. As more +# elements are received, elements older than the specified +# duration are taken from the queue and produced on the result +# sequence. This causes elements to be delayed with duration. +# +# Args: +# duration: Duration for taking elements from the end of the +# sequence. +# +# Returns: +# An observable sequence with the elements taken during the +# specified duration from the end of the source sequence. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler_ : SchedulerBase = null + ) -> DisposableBase: + var _scheduler : SchedulerBase + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = SceneTreeTimeoutScheduler.singleton() + + var q : Array[Dictionary] = [] + + var on_next = func(x): + var now = _scheduler.now() + q.append({"interval": now, "value": x}) + while not q.is_empty() and now - q[0]["interval"] >= duration: + q.pop_front() + + var on_completed = func(): + var now = _scheduler.now() + while not q.is_empty(): + var _next = q.pop_front() + if now - _next["interval"] <= duration: + observer.on_next(_next["value"]) + observer.on_completed() + + return source.subscribe( + on_next, observer.on_error, on_completed, + scheduler_ + ) + + return Observable.new(subscribe) + + return take_last_with_time diff --git a/addons/reactivex/operators/_takeuntil.gd b/addons/reactivex/operators/_takeuntil.gd new file mode 100644 index 0000000..b4e0f17 --- /dev/null +++ b/addons/reactivex/operators/_takeuntil.gd @@ -0,0 +1,32 @@ +static func take_until_( + other : Observable +) -> Callable: + var obs = other + + var take_until = func(source : Observable) -> Observable: +# """Returns the values from the source observable sequence until +# the other observable sequence produces a value. +# +# Args: +# source: The source observable sequence. +# +# Returns: +# An observable sequence containing the elements of the source +# sequence up to the point the other sequence interrupted +# further propagation. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var on_completed = func(__): + observer.on_completed() + + return CompositeDisposable.new([ + source.subscribe(observer, GDRx.basic.noop, GDRx.basic.noop, scheduler), + obs.subscribe(on_completed, observer.on_error, func():return, scheduler) + ]) + + return Observable.new(subscribe) + + return take_until diff --git a/addons/reactivex/operators/_takeuntilwithtime.gd b/addons/reactivex/operators/_takeuntilwithtime.gd new file mode 100644 index 0000000..d7427a0 --- /dev/null +++ b/addons/reactivex/operators/_takeuntilwithtime.gd @@ -0,0 +1,44 @@ +static func take_until_with_time_( + end_time : float, + absolute : bool = false, + scheduler : SchedulerBase = null +) -> Callable: + var take_until_with_time = func(source : Observable) -> Observable: +# """Takes elements for the specified duration until the specified end +# time, using the specified scheduler to run timers. +# +# Examples: +# >>> var res = take_until_with_time.call(source) +# +# Args: +# source: Source observale to take elements from. +# +# Returns: +# An observable sequence with the elements taken +# until the specified end time. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler_ : SchedulerBase = null + ) -> DisposableBase: + var _scheduler : SchedulerBase + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = SceneTreeTimeoutScheduler.singleton() + + var action = func(_scheduler : SchedulerBase, _state = null): + observer.on_completed() + + var task + if absolute: + task = _scheduler.schedule_absolute(end_time, action) + else: + task = _scheduler.schedule_relative(end_time, action) + + return CompositeDisposable.new([ + task, source.subscribe(observer, GDRx.basic.noop, GDRx.basic.noop, scheduler_) + ]) + + return Observable.new(subscribe) + + return take_until_with_time diff --git a/addons/reactivex/operators/_takewhile.gd b/addons/reactivex/operators/_takewhile.gd new file mode 100644 index 0000000..512d41c --- /dev/null +++ b/addons/reactivex/operators/_takewhile.gd @@ -0,0 +1,111 @@ +static func take_while_( + predicate : Callable = GDRx.basic.default_condition, + inclusive : bool = false +) -> Callable: + var take_while = func(source : Observable) -> Observable: +# """Returns elements from an observable sequence as long as a +# specified condition is true. +# +# Example: +# >>> take_while.call(source) +# +# Args: +# source: The source observable to take from. +# +# Returns: +# An observable sequence that contains the elements from the +# input sequence that occur before the element at which the +# test no longer passes. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var running = RefValue.Set(true) + + var on_next = func(value): + if true: + var __ = LockGuard.new(source.lock) + if not running.v: + return + + if GDRx.try(func(): + running.v = predicate.call(value) + ) \ + .catch("Error", func(err): + observer.on_error(err) + ).end_try_catch(): return + + if running.v: + observer.on_next(value) + else: + if inclusive: + observer.on_next(value) + observer.on_completed() + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return take_while + +static func take_while_indexed_( + predicate : Callable = GDRx.basic.default_condition, + inclusive : bool = false +) -> Callable: + var take_while_indexed = func(source : Observable) -> Observable: +# """Returns elements from an observable sequence as long as a +# specified condition is true. The element's index is used in the +# logic of the predicate function. +# +# Example: +# >>> take_while_indexed.call(source) +# +# Args: +# source: Source observable to take from. +# +# Returns: +# An observable sequence that contains the elements from the +# input sequence that occur before the element at which the +# test no longer passes. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var running = RefValue.Set(true) + var i = RefValue.Set(0) + + var on_next = func(value): + if true: + var __ = LockGuard.new(source.lock) + if not running.v: + return + + if GDRx.try(func(): + running.v = predicate.call(value, i.v) + ) \ + .catch("Error", func(err): + observer.on_error(err) + ).end_try_catch(): return + else: + i.v += 1 + + if running.v: + observer.on_next(value) + else: + if inclusive: + observer.on_next(value) + observer.on_completed() + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return take_while_indexed diff --git a/addons/reactivex/operators/_takewithtime.gd b/addons/reactivex/operators/_takewithtime.gd new file mode 100644 index 0000000..69b2695 --- /dev/null +++ b/addons/reactivex/operators/_takewithtime.gd @@ -0,0 +1,44 @@ +static func take_with_time_( + duration : float, scheduler : SchedulerBase = null +) -> Callable: + var take_with_time = func(source : Observable) -> Observable: +# """Takes elements for the specified duration from the start of +# the observable source sequence. +# +# Example: +# >>> var res = take_with_time.call(source) +# +# This operator accumulates a queue with a length enough to store +# elements received during the initial duration window. As more +# elements are received, elements older than the specified +# duration are taken from the queue and produced on the result +# sequence. This causes elements to be delayed with duration. +# +# Args: +# source: Source observable to take elements from. +# +# Returns: +# An observable sequence with the elements taken during the +# specified duration from the start of the source sequence. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler_ : SchedulerBase = null + ) -> DisposableBase: + var _scheduler : SchedulerBase + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = SceneTreeTimeoutScheduler.singleton() + + var action = func(_scheduler : SchedulerBase, _state = null): + observer.on_completed() + + var disp = _scheduler.schedule_relative(duration, action) + return CompositeDisposable.new([ + disp, source.subscribe(observer, GDRx.basic.noop, GDRx.basic.noop, + scheduler_) + ]) + + return Observable.new(subscribe) + + return take_with_time diff --git a/addons/reactivex/operators/_throttlefirst.gd b/addons/reactivex/operators/_throttlefirst.gd new file mode 100644 index 0000000..413e980 --- /dev/null +++ b/addons/reactivex/operators/_throttlefirst.gd @@ -0,0 +1,50 @@ +static func throttle_first_( + window_duration : float, + scheduler : SchedulerBase = null +) -> Callable: + var throttle_first = func(source : Observable) -> Observable: +# """Returns an observable that emits only the first item emitted +# by the source Observable during sequential time windows of a +# specified duration. +# +# Args: +# source: Source observable to throttle. +# +# Returns: +# An Observable that performs the throttle operation. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler_ : SchedulerBase = null + ) -> DisposableBase: + var _scheduler : SchedulerBase + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = SceneTreeTimeoutScheduler.singleton() + + var duration : float = window_duration + if duration <= 0: + ArgumentOutOfRangeError.raise() + var last_on_next = RefValue.Null() + + var on_next = func(x): + var emit = false + var now = _scheduler.now() + + if true: + var __ = LockGuard.new(source.lock) + if last_on_next.v == null or now - last_on_next.v >= duration: + last_on_next.v = now + emit = true + + if emit: + observer.on_next(x) + + return source.subscribe( + on_next, observer.on_error, observer.on_completed, + _scheduler + ) + + return Observable.new(subscribe) + + return throttle_first diff --git a/addons/reactivex/operators/_timeinterval.gd b/addons/reactivex/operators/_timeinterval.gd new file mode 100644 index 0000000..da885f7 --- /dev/null +++ b/addons/reactivex/operators/_timeinterval.gd @@ -0,0 +1,37 @@ +static func time_interval_( + scheduler : SchedulerBase = null +) -> Callable: + var time_interval = func(source : Observable) -> Observable: +# """Records the time interval between consecutive values in an +# observable sequence. +# +# >>> var res = time_interval.call(source) +# +# Return: +# An observable sequence with time interval information on +# values. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler_ : SchedulerBase = null + ) -> DisposableBase: + var _scheduler : SchedulerBase + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = SceneTreeTimeoutScheduler.singleton() + + var last = RefValue.Set(_scheduler.now()) + + var mapper = func(value) -> Tuple: + var now = _scheduler.now() + var span = now - last.v + last.v = now + return Tuple.new([value, span]) + + return source.pipe1(GDRx.op.map(mapper)).subscribe( + observer, GDRx.basic.noop, GDRx.basic.noop, _scheduler + ) + + return Observable.new(subscribe) + + return time_interval diff --git a/addons/reactivex/operators/_timeout.gd b/addons/reactivex/operators/_timeout.gd new file mode 100644 index 0000000..387bde9 --- /dev/null +++ b/addons/reactivex/operators/_timeout.gd @@ -0,0 +1,87 @@ +static func timeout_( + duetime : float, + absolute : bool = false, + other : Observable = null, + scheduler : SchedulerBase = null +) -> Callable: + + var _other = other if other != null else GDRx.obs.throw(RxBaseError.new("Timeout")) + var obs = _other + + var timeout = func(source : Observable) -> Observable: +# """Returns the source observable sequence or the other observable +# sequence if duetime elapses. +# +# Examples: +# >>> var res = timeout.call(source) +# +# Args: +# source: Source observable to timeout +# +# Returns: +# An obserable sequence switching to the other sequence in +# case of a timeout. +# """ + var subscribe = func( + observer : ObserverBase, + scheduler_ : SchedulerBase = null + ) -> DisposableBase: + var _scheduler : SchedulerBase + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = SceneTreeTimeoutScheduler.singleton() + + var switched = [false] + var _id = [0] + + var original = SingleAssignmentDisposable.new() + var subscription = SerialDisposable.new() + var timer = SerialDisposable.new() + subscription.disposable = original + + var create_timer = func(): + var my_id = _id[0] + + var action = func(scheduler : SchedulerBase, _state = null): + switched[0] = _id[0] == my_id + var timer_wins = switched[0] + if timer_wins: + subscription.disposable = obs.subscribe( + observer, GDRx.basic.noop, GDRx.basic.noop, scheduler + ) + + if absolute: + timer.disposable = _scheduler.schedule_absolute(duetime, action) + else: + timer.disposable = _scheduler.schedule_relative(duetime, action) + + create_timer.call() + + var on_next = func(value): + var send_wins = not switched[0] + if send_wins: + _id[0] += 1 + observer.on_next(value) + create_timer.call() + + var on_error = func(error): + var on_error_wins = not switched[0] + if on_error_wins: + _id[0] += 1 + observer.on_error(error) + + var on_completed = func(): + var on_completed_wins = not switched[0] + if on_completed_wins: + _id[0] += 1 + observer.on_completed() + + original.disposable = source.subscribe( + on_next, on_error, on_completed, + scheduler_ + ) + return CompositeDisposable.new([subscription, timer]) + + return Observable.new(subscribe) + + return timeout diff --git a/addons/reactivex/operators/_timeoutwithmapper.gd b/addons/reactivex/operators/_timeoutwithmapper.gd new file mode 100644 index 0000000..ea3f408 --- /dev/null +++ b/addons/reactivex/operators/_timeoutwithmapper.gd @@ -0,0 +1,121 @@ +static func timeout_with_mapper_( + first_timeout : Observable = null, + timeout_duration_mapper : Callable = func(__) -> Observable: return GDRx.obs.never(), + other : Observable = null +) -> Callable: + +# """Returns the source observable sequence, switching to the other +# observable sequence if a timeout is signaled. +# +# var res = GDRx.obs.timeout_with_mapper(GDRx.obs.timer(500)) +# var res = GDRx.obs.timeout_with_mapper(GDRx.obs.timer(500), func(x): return GDRx.obs.timer(200)) +# var res = GDRx.obs.timeout_with_mapper( +# GDRx.obs.timer(500), +# func(x): return GDRx.obs.timer(200)), +# GDRx.obs.return_value(42) +# ) +# +# Args: +# first_timeout -- [Optional] Observable sequence that represents the +# timeout for the first element. If not provided, this defaults to +# reactivex.never(). +# timeout_duration_mapper -- [Optional] Selector to retrieve an +# observable sequence that represents the timeout between the +# current element and the next element. +# other -- [Optional] Sequence to return in case of a timeout. If not +# provided, this is set to reactivex.throw(). +# +# Returns: +# The source sequence switching to the other sequence in case +# of a timeout. +# """ + + var first_timeout_ = first_timeout if first_timeout != null else GDRx.obs.never() + var other_ = other if other != null else GDRx.obs.throw(RxBaseError.new("Timeout")) + + var timeout_with_mapper = func(source : Observable) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var subscription = SerialDisposable.new() + var timer = SerialDisposable.new() + var original = SingleAssignmentDisposable.new() + + subscription.disposable = original + + var switched = false + var _id = [0] + + var set_timer = func(timeout : Observable): + var my_id = _id[0] + + var timer_wins = func(): + return _id[0] == my_id + + var d = SingleAssignmentDisposable.new() + timer.disposable = d + + var on_next = func(__): + if timer_wins.call(): + subscription.disposable = other_.subscribe( + observer, GDRx.basic.noop, GDRx.basic.noop, scheduler + ) + + d.dispose() + + var on_error = func(e): + if timer_wins.call(): + observer.on_error(e) + + var on_completed = func(): + if timer_wins.call(): + subscription.disposable = other_.subscribe( + observer + ) + + d.disposable = timeout.subscribe( + on_next, on_error, on_completed, + scheduler + ) + + set_timer.call(first_timeout_) + + var observer_wins = func(): + var res = not switched + if res: + _id[0] += 1 + + return res + + var on_next = func(x): + if observer_wins.call(): + observer.on_next(x) + var timeout = RefValue.Null() + if GDRx.try(func(): + timeout.v = timeout_duration_mapper.call(x) + ) \ + .catch("Error", func(e): + observer.on_error(e) + ) \ + .end_try_catch(): return + + set_timer.call(timeout.v) + + var on_error = func(error): + if observer_wins.call(): + observer.on_error(error) + + var on_completed = func(): + if observer_wins.call(): + observer.on_completed() + + original.disposable = source.subscribe( + on_next, on_error, on_completed, + scheduler + ) + return CompositeDisposable.new([subscription, timer]) + + return Observable.new(subscribe) + + return timeout_with_mapper diff --git a/addons/reactivex/operators/_timestamp.gd b/addons/reactivex/operators/_timestamp.gd new file mode 100644 index 0000000..0c8bb82 --- /dev/null +++ b/addons/reactivex/operators/_timestamp.gd @@ -0,0 +1,32 @@ +static func timestamp_( + scheduler : SchedulerBase = null +) -> Callable: + var timestamp = func(source : Observable) -> Observable: +# """Records the timestamp for each value in an observable sequence. +# +# Examples: +# >>> timestamp.call(source) +# +# Produces objects with attributes `value` and `timestamp`, where +# value is the original value. +# +# Args: +# source: Observable source to timestamp. +# +# Returns: +# An observable sequence with timestamp information on values. +# """ + var factory = func(scheduler_ : SchedulerBase = null): + var _scheduler : SchedulerBase + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = SceneTreeTimeoutScheduler.singleton() + + var mapper = func(value) -> Tuple: + return Tuple.new([value, _scheduler.now()]) + + return source.pipe1(GDRx.op.map(mapper)) + + return GDRx.obs.defer(factory) + + return timestamp diff --git a/addons/reactivex/operators/_todict.gd b/addons/reactivex/operators/_todict.gd new file mode 100644 index 0000000..4f84c9e --- /dev/null +++ b/addons/reactivex/operators/_todict.gd @@ -0,0 +1,56 @@ +static func to_dict_( + key_mapper : Callable, + element_mapper : Callable = GDRx.basic.identity +) -> Callable: + var to_dict = func(source : Observable) -> Observable: +# """Converts the observable sequence to a Map if it exists. +# +# Args: +# source: Source observable to convert. +# +# Returns: +# An observable sequence with a single value of a dictionary +# containing the values from the observable sequence. +# """ + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + + var m = RefValue.Set({}) + + var on_next = func(x): + var key = RefValue.Null() + if GDRx.try(func(): + key.v = key_mapper.call(x) + ) \ + .catch("Error", func(e): + observer.on_error(e) + ) \ + .end_try_catch(): return + + var element = RefValue.Null() + if GDRx.try(func(): + element.v = element_mapper.call(x) + ) \ + .catch("Error", func(e): + observer.on_error(e) + ) \ + .end_try_catch(): return + + m.v[key.v] = element.v + + var on_completed = func(): + observer.on_next.call(m.v) + m.v = {} + observer.on_completed() + + return source.subscribe( + on_next, observer.on_error, on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return to_dict diff --git a/addons/reactivex/operators/_toiterable.gd b/addons/reactivex/operators/_toiterable.gd new file mode 100644 index 0000000..0f61d0b --- /dev/null +++ b/addons/reactivex/operators/_toiterable.gd @@ -0,0 +1,33 @@ +static func to_iterable_() -> Callable: + var to_iterable = func(source : Observable) -> Observable: +# """Creates an iterable from an observable sequence. +# +# Returns: +# An observable sequence containing a single element with an +# iterable containing all the elements of the source +# sequence. +# """ + var _source = RefValue.Set(source) + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var queue = RefValue.Set([]) + + var on_next = func(item): + queue.v.append(item) + + var on_completed = func(): + observer.on_next(GDRx.to_iterable(queue.v)) + queue.v = [] + observer.on_completed() + + return _source.v.subscribe( + on_next, observer.on_error, on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return to_iterable diff --git a/addons/reactivex/operators/_tolist.gd b/addons/reactivex/operators/_tolist.gd new file mode 100644 index 0000000..71cf3c3 --- /dev/null +++ b/addons/reactivex/operators/_tolist.gd @@ -0,0 +1,27 @@ +static func to_list_() -> Callable: + var to_list = func(source : Observable) -> Observable: + + var _source = RefValue.Set(source) + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var queue = RefValue.Set([]) + + var on_next = func(item): + queue.v.append(item) + + var on_completed = func(): + observer.on_next(queue.v) + queue.v = [] + observer.on_completed() + + return _source.v.subscribe( + on_next, observer.on_error, on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return to_list diff --git a/addons/reactivex/operators/_toset.gd b/addons/reactivex/operators/_toset.gd new file mode 100644 index 0000000..feee327 --- /dev/null +++ b/addons/reactivex/operators/_toset.gd @@ -0,0 +1,26 @@ +static func to_set_() -> Callable: +# """Converts the observable sequence to a set. +# +# Returns an observable sequence with a single value of a set +# containing the values from the observable sequence. +# """ + var to_set = func(source : Observable) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var s = RefValue.Set(ArraySet.new()) + + var on_completed = func(): + observer.on_next(s.v) + s.v = ArraySet.new() + observer.on_completed() + + return source.subscribe( + s.v.add, observer.on_error, on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return to_set diff --git a/addons/reactivex/operators/_whiledo.gd b/addons/reactivex/operators/_whiledo.gd new file mode 100644 index 0000000..c51093b --- /dev/null +++ b/addons/reactivex/operators/_whiledo.gd @@ -0,0 +1,20 @@ +static func while_do_( + condition : Callable = GDRx.basic.default_condition +) -> Callable: + var while_do = func(source : Observable) -> Observable: +# """Repeats source as long as condition holds emulating a while +# loop. +# +# Args: +# source: The observable sequence that will be run if the +# condition function returns true. +# +# Returns: +# An observable sequence which is repeated as long as the +# condition holds. +# """ + var obs = source + var it : IterableBase = WhileIterable.new(InfiniteIterable.new(obs), condition) + return GDRx.obs.concat_with_iterable(it) + + return while_do diff --git a/addons/reactivex/operators/_window.gd b/addons/reactivex/operators/_window.gd new file mode 100644 index 0000000..74124de --- /dev/null +++ b/addons/reactivex/operators/_window.gd @@ -0,0 +1,152 @@ +static func window_toggle_( + openings : Observable, closing_mapper : Callable +) -> Callable: +# """Projects each element of an observable sequence into zero or +# more windows. +# +# Args: +# source: Source observable to project into windows. +# +# Returns: +# An observable sequence of windows. +# """ + var window_toggle = func(source : Observable) -> Observable: + var mapper = func(args : Tuple): + var window = args.at(1) + return window + + return openings.pipe2( + GDRx.op.group_join( + source, + closing_mapper, + func(__): return GDRx.obs.empty() + ), + GDRx.op.map(mapper) + ) + + return window_toggle + +static func window_(boundaries : Observable) -> Callable: +# """Projects each element of an observable sequence into zero or +# more windows. +# +# Args: +# source: Source observable to project into windows. +# +# Returns: +# An observable sequence of windows. +# """ + var window = func(source : Observable) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var window_subject = RefValue.Set(Subject.new()) + var d = CompositeDisposable.new() + var r = RefCountDisposable.new(d) + + observer.on_next(GDRx.util.add_ref(window_subject.v.as_observable(), r)) + + var on_next_window = func(x): + window_subject.v.on_next(x) + + var on_error = func(err): + window_subject.v.on_error(err) + observer.on_error(err) + + var on_completed = func(): + window_subject.v.on_completed() + observer.on_completed() + + d.add( + source.subscribe( + on_next_window, on_error, on_completed, + scheduler + ) + ) + + var on_next_observer = func(_w): + window_subject.v.on_completed() + window_subject.v = Subject.new() + observer.on_next(GDRx.util.add_ref(window_subject.v.as_observable(), r)) + + d.add( + boundaries.subscribe( + on_next_observer, on_error, on_completed, + scheduler + ) + ) + + return r + + return Observable.new(subscribe) + + return window + + +static func window_when_(closing_mapper : Callable) -> Callable: +# """Projects each element of an observable sequence into zero or +# more windows. +# +# Args: +# source: Source observable to project into windows. +# +# Returns: +# An observable sequence of windows. +# """ + var window_when = func(source : Observable) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var m = SerialDisposable.new() + var d = CompositeDisposable.new(m) + var r = RefCountDisposable.new(d) + var window = RefValue.Set(Subject.new()) + + observer.on_next(GDRx.util.add_ref(window.v, r)) + + var on_next = func(value): + window.v.on_next(value) + + var on_error = func(error): + window.v.on_error(error) + observer.on_error(error) + + var on_completed = func(): + window.v.on_completed() + observer.on_completed() + + d.add( + source.subscribe(on_next, on_error, on_completed, + scheduler) + ) + + var create_window_on_completed = func(__rec_cb : Callable): + var window_close = RefValue.Null() + if GDRx.try(func(): + window_close.v = closing_mapper.call() + ) \ + .catch("Error", func(error): + observer.on_error(error) + ) \ + .end_try_catch(): return + + var on_completed_inner = func(): + window.v.on_completed() + window.v = Subject.new() + observer.on_next(GDRx.util.add_ref(window.v, r)) + __rec_cb.bind(__rec_cb).call() + + var m1 = SingleAssignmentDisposable.new() + m.disposable = m1 + m1.disposable = window_close.v.pipe1(GDRx.op.take(1)).subscribe( + func(__): return, on_error, on_completed_inner, scheduler + ) + + create_window_on_completed.bind(create_window_on_completed).call() + return r + + return Observable.new(subscribe) + + return window_when diff --git a/addons/reactivex/operators/_windowwithcount.gd b/addons/reactivex/operators/_windowwithcount.gd new file mode 100644 index 0000000..3de8b25 --- /dev/null +++ b/addons/reactivex/operators/_windowwithcount.gd @@ -0,0 +1,78 @@ +static func window_with_count_( + count : int, skip = null +) -> Callable: +# """Projects each element of an observable sequence into zero or more +# windows which are produced based on element count information. +# +# Examples: +# >>> GDRx.op.window_with_count(10) +# >>> GDRx.op.window_with_count(10, 1) +# +# Args: +# count: Length of each window. +# skip: [Optional] Number of elements to skip between creation of +# consecutive windows. If not specified, defaults to the +# count. +# +# Returns: +# An observable sequence of windows. +# """ + if count <= 0: + ArgumentOutOfRangeError.raise() + count = 1 + + @warning_ignore("incompatible_ternary") + var skip_ : int = skip if skip != null else count + if skip_ <= 0: + ArgumentOutOfRangeError.raise() + skip_ = 1 + + var window_with_count = func(source : Observable) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + var m = SingleAssignmentDisposable.new() + var refCountDisposable = RefCountDisposable.new(m) + var n = [0] + var q : Array[Subject] = [] + + var create_window = func(): + var s : Subject = Subject.new() + q.append(s) + observer.on_next(GDRx.util.add_ref(s.as_observable(), refCountDisposable)) + + create_window.call() + + var on_next = func(x): + for item in q: + item.on_next(x) + + var c = n[0] - count + 1 + if c >= 0 and c % skip_ == 0: + var s = q.pop_front() + s.on_completed() + + n[0] += 1 + if (n[0] % skip_) == 0: + create_window.call() + + var on_error = func(error): + while not q.is_empty(): + q.pop_front().on_error(error) + observer.on_error(error) + + var on_completed = func(): + while not q.is_empty(): + q.pop_front().on_completed() + observer.on_completed() + + m.disposable = source.subscribe( + on_next, on_error, on_completed, + scheduler + ) + return refCountDisposable + + return Observable.new(subscribe) + + return window_with_count diff --git a/addons/reactivex/operators/_windowwithtime.gd b/addons/reactivex/operators/_windowwithtime.gd new file mode 100644 index 0000000..0eb1525 --- /dev/null +++ b/addons/reactivex/operators/_windowwithtime.gd @@ -0,0 +1,96 @@ +static func window_with_time_( + timespan : float, + timeshift = null, + scheduler : SchedulerBase = null +) -> Callable: + if timeshift == null: + timeshift = timespan + + var window_with_time = func(source : Observable) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler_ : SchedulerBase = null + ) -> DisposableBase: + var _scheduler : SchedulerBase + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = SceneTreeTimeoutScheduler.singleton() + + var timer_d = SerialDisposable.new() + var next_shift = [timeshift] + var next_span = [timespan] + var total_time = [Scheduler.DELTA_ZERO] + var queue : Array[Subject] = [] + + var group_disposable = CompositeDisposable.new(timer_d) + var ref_count_disposable = RefCountDisposable.new(group_disposable) + + var create_timer = func(__create_timer_rec : Callable): + var m = SingleAssignmentDisposable.new() + timer_d.disposable = m + var is_span = false + var is_shift = false + + if next_span[0] == next_shift[0]: + is_span = true + is_shift = true + elif next_span[0] < next_shift[0]: + is_span = true + else: + is_shift = true + + var new_total_time = next_span[0] if is_span else next_shift[0] + var ts = new_total_time - total_time[0] + total_time[0] = new_total_time + if is_span: + next_span[0] += timeshift + if is_shift: + next_shift[0] += timeshift + + var action = func(_scheduler : SchedulerBase, _state = null): + var __ = LockGuard.new(source.lock) + var s : Subject = null + + if is_shift: + s = Subject.new() + queue.append(s) + observer.on_next(GDRx.util.add_ref(s.as_observable(), ref_count_disposable)) + + if is_span: + s = queue.pop_front() + s.on_completed() + + __create_timer_rec.bind(__create_timer_rec).call() + + m.disposable = _scheduler.schedule_relative(ts, action) + + queue.append(Subject.new()) + observer.on_next(GDRx.util.add_ref(queue[0].as_observable(), ref_count_disposable)) + create_timer.bind(create_timer).call() + + var on_next = func(x): + var __ = LockGuard.new(source.lock) + for s in queue: + s.as_observer().on_next(x) + + var on_error = func(e): + var __ = LockGuard.new(source.lock) + for s in queue: + s.as_observer().on_error(e) + observer.on_error(e) + + var on_completed = func(): + var __ = LockGuard.new(source.lock) + for s in queue: + s.as_observer().on_completed() + observer.on_completed() + + group_disposable.add(source.subscribe( + on_next, on_error, on_completed, + scheduler_ + )) + return ref_count_disposable + + return Observable.new(subscribe) + + return window_with_time diff --git a/addons/reactivex/operators/_windowwithtimeorcount.gd b/addons/reactivex/operators/_windowwithtimeorcount.gd new file mode 100644 index 0000000..d309b28 --- /dev/null +++ b/addons/reactivex/operators/_windowwithtimeorcount.gd @@ -0,0 +1,79 @@ +static func window_with_time_or_count_( + timespan : float, + count : int, + scheduler : SchedulerBase = null +) -> Callable: + + var window_with_time_or_count = func(source : Observable) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler_ : SchedulerBase = null + ) -> DisposableBase: + var _scheduler : SchedulerBase + if scheduler != null: _scheduler = scheduler + elif scheduler_ != null: _scheduler = scheduler_ + else: _scheduler = SceneTreeTimeoutScheduler.singleton() + + var n : RefValue = RefValue.Set(0) + var s : RefValue = RefValue.Set(Subject.new()) + var timer_d = SerialDisposable.new() + var window_id : RefValue = RefValue.Set(0) + var group_disposable = CompositeDisposable.new([timer_d]) + var ref_count_disposable = RefCountDisposable.new(group_disposable) + + var create_timer = func(_id : int, __create_timer_rec : Callable): + var m = SingleAssignmentDisposable.new() + timer_d.v.disposable = m + + var action = func(_scheduler : SchedulerBase, _state = null): + if _id != window_id.v: + return + + n.v = 0 + window_id.v += 1 + var new_id = window_id.v + s.v.on_completed() + s.v = Subject.new() + observer.on_next(GDRx.util.add_ref(s.v.as_observable(), ref_count_disposable)) + __create_timer_rec.bind(__create_timer_rec).call(new_id) + + m.disposable = _scheduler.schedule_relative(timespan, action) + + observer.on_next(GDRx.util.add_ref(s.v.as_observable(), ref_count_disposable)) + create_timer.bind(create_timer).call(0) + + var on_next = func(x): + var new_window = false + var new_id = 0 + + s.v.on_next(x) + n.v += 1 + if n.v == count: + new_window = true + n.v = 0 + window_id.v += 1 + new_id = window_id.v + s.v.on_completed() + s.v = Subject.new() + observer.on_next(GDRx.util.add_ref(s.v.as_observable(), ref_count_disposable)) + + if new_window: + create_timer.bind(create_timer).call(new_id) + + var on_error = func(e): + s.v.on_error(e) + observer.on_error(e) + + var on_completed = func(): + s.v.on_completed() + observer.on_completed() + + group_disposable.add(source.subscribe( + on_next, on_error, on_completed, + scheduler_) + ) + return ref_count_disposable + + return Observable.new(subscribe) + + return window_with_time_or_count diff --git a/addons/reactivex/operators/_withlatestfrom.gd b/addons/reactivex/operators/_withlatestfrom.gd new file mode 100644 index 0000000..eab968b --- /dev/null +++ b/addons/reactivex/operators/_withlatestfrom.gd @@ -0,0 +1,20 @@ +static func with_latest_from_(sources_) -> Callable: +# """With latest from operator. +# +# Merges the specified observable sequences into one observable +# sequence by creating a tuple only when the first +# observable sequence produces an element. The observables can be +# passed either as seperate arguments or as a list. +# +# Examples: +# >>> var op = GDRx.op.with_latest_from([obs1]) +# >>> var op = GDRx.op.with_latest_from([obs1, obs2, obs3]) +# +# Returns: +# An observable sequence containing the result of combining +# elements of the sources into a tuple. +# """ + var sources : Array[Observable] = GDRx.util.unpack_arg(sources_) + var with_latest_from = func(source : Observable) -> Observable: + return GDRx.obs.with_latest_from(source, sources) + return with_latest_from diff --git a/addons/reactivex/operators/_zip.gd b/addons/reactivex/operators/_zip.gd new file mode 100644 index 0000000..327a028 --- /dev/null +++ b/addons/reactivex/operators/_zip.gd @@ -0,0 +1,64 @@ +static func zip_(args) -> Callable: + var _zip = func(source : Observable) -> Observable: +# """Merges the specified observable sequences into one observable +# sequence by creating a tuple whenever all of the +# observable sequences have produced an element at a corresponding +# index. +# +# Example: +# >>> var res = zip.call(source) +# +# Args: +# source: Source observable to zip. +# +# Returns: +# An observable sequence containing the result of combining +# elements of the sources as a tuple. +# """ + var sources : Array[Observable] = GDRx.util.unpack_arg(args) + sources.push_front(source) + return GDRx.obs.zip(sources) + return _zip + +static func zip_with_iterable_(seq : IterableBase) -> Callable: + var zip_with_iterable = func(source : Observable) -> Observable: +# """Merges the specified observable sequence and list into one +# observable sequence by creating a tuple whenever all of +# the observable sequences have produced an element at a +# corresponding index. +# +# Example +# >>> var res = zip_with_iterable.call(source) +# +# Args: +# source: Source observable to zip. +# +# Returns: +# An observable sequence containing the result of combining +# elements of the sources as a tuple. +# """ + var first = source + var second : Iterator = seq.iter() + + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ): + # var index = RefValue.Set(0) ? weird var def from RxPY + + var on_next = func(left): + var right = second.next() + if right is ItEnd: + observer.on_completed() + else: + var result = Tuple.new([left, right]) + observer.on_next(result) + + return first.subscribe( + on_next, observer.on_error, observer.on_completed, + scheduler + ) + + return Observable.new(subscribe) + + return zip_with_iterable diff --git a/addons/reactivex/operators/connectable/_refcount.gd b/addons/reactivex/operators/connectable/_refcount.gd new file mode 100644 index 0000000..ad0e96c --- /dev/null +++ b/addons/reactivex/operators/connectable/_refcount.gd @@ -0,0 +1,31 @@ +static func ref_count_() -> Callable: + + var connectable_subscription = RefValue.Null() + var count = RefValue.Set(0) + + var ref_count = func(source : ConnectableObservable) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + + count.v += 1 + var should_connect = count.v == 1 + var subscription = source.subscribe( + observer, GDRx.basic.noop, GDRx.basic.noop, + scheduler + ) + if should_connect: + connectable_subscription.v = source.connect_observable(scheduler) + + var dispose = func(): + subscription.dispose() + count.v -= 1 + if count.v <= 0 and connectable_subscription.v != null: + connectable_subscription.v.dispose() + + return Disposable.new(dispose) + + return Observable.new(subscribe) + + return ref_count diff --git a/addons/reactivex/pipe.gd b/addons/reactivex/pipe.gd new file mode 100644 index 0000000..8a64fdd --- /dev/null +++ b/addons/reactivex/pipe.gd @@ -0,0 +1,180 @@ +func reduce(fun : Callable, iterable : IterableBase, initial = GDRx.util.NOT_SET): + var it = iterable.iter() + var next_ = it.next() + var value_ = initial + while not next_ is ItEnd: + value_ = fun.call(value_, next_) + next_ = it.next() + return value_ + +func compose1(__op1 : Callable) -> Callable: + return compose(GDRx.util.Iter([__op1])) + +func compose2(__op1 : Callable, __op2 : Callable) -> Callable: + return compose(GDRx.util.Iter([__op1, __op2])) + +func compose3( + __op1 : Callable, + __op2 : Callable, + __op3 : Callable +) -> Callable: + return compose(GDRx.util.Iter([__op1, __op2, __op3])) + +func compose4( + __op1 : Callable, + __op2 : Callable, + __op3 : Callable, + __op4 : Callable +) -> Callable: + return compose(GDRx.util.Iter([__op1, __op2, __op3, __op4])) + +func compose5( + __op1 : Callable, + __op2 : Callable, + __op3 : Callable, + __op4 : Callable, + __op5 : Callable +) -> Callable: + return compose(GDRx.util.Iter([__op1, __op2, __op3, __op4, __op5])) + +func compose6( + __op1 : Callable, + __op2 : Callable, + __op3 : Callable, + __op4 : Callable, + __op5 : Callable, + __op6 : Callable +) -> Callable: + return compose(GDRx.util.Iter([__op1, __op2, __op3, __op4, __op5, __op6])) + +func composea(operators : Array[Callable]) -> Callable: + return compose(GDRx.util.Iter(operators)) + +## Compose multiple operators left to right. +## Composes zero or more operators into a functional composition. The +## operators are composed to left to right. A composition of zero +## operators gives back the source. +## [br] +## [b]Examples:[/b] +## [codeblock] +## pipe0().call(source) == source +## pipe1(f).call(source) == f.call(source) +## pipe2(f, g).call(source) == g.call(f.call(source)) +## pipe3(f, g, h).call(source) == h.call(g.call(f.call(source))) +## ... +## [/codeblock] +## [br][br] +## [b]Returns:[/b] +## [br] +## The composed observable. +func compose(operators : IterableBase) -> Callable: + var _compose = func(source): + return reduce(func(obs, op): return op.call(obs), operators, source) + return _compose + +func pipe0(_value) -> Variant: + return pipe(_value, GDRx.util.Iter([])) + +func pipe1(__value : Variant, __fn1 : Callable) -> Variant: + return pipe(__value, GDRx.util.Iter([__fn1])) + +func pipe2( + __value : Variant, + __fn1 : Callable, + __fn2 : Callable +) -> Variant: + return pipe(__value, GDRx.util.Iter([__fn1, __fn2])) + +func pipe3( + __value : Variant, + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable +) -> Variant: + return pipe(__value, GDRx.util.Iter([__fn1, __fn2, __fn3])) + +func pipe4( + __value : Variant, + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable +) -> Variant: + return pipe(__value, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4])) + +func pipe5( + __value : Variant, + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable, + __fn5 : Callable +) -> Variant: + return pipe(__value, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5])) + +func pipe6( + __value : Variant, + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable, + __fn5 : Callable, + __fn6 : Callable +) -> Variant: + return pipe(__value, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5, __fn6])) + +func pipe7( + __value : Variant, + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable, + __fn5 : Callable, + __fn6 : Callable, + __fn7 : Callable +) -> Variant: + return pipe(__value, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5, __fn6, __fn7])) + +func pipe8( + __value : Variant, + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable, + __fn5 : Callable, + __fn6 : Callable, + __fn7 : Callable, + __fn8 : Callable +) -> Variant: + return pipe(__value, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5, __fn6, __fn7, __fn8])) + +func pipe9( + __value : Variant, + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable, + __fn5 : Callable, + __fn6 : Callable, + __fn7 : Callable, + __fn8 : Callable, + __fn9 : Callable +) -> Variant: + return pipe(__value, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5, __fn6, __fn7, __fn8, __fn9])) + +func pipea(__value, arr : Array): + return pipe(__value, GDRx.util.Iter(arr)) + +## Functional pipe (`|>`) +## [br] +## Allows the use of function argument on the left side of the +## function. +## [br] +## [b]Example:[/b] +## [codeblock] +## pipe2(x, fn) == __fn.call(x) # Same as x |> fn +## pipe3(x, fn, gn) == gn.call(fn.call(x)) # Same as x |> fn |> gn +## ... +## [/codeblock] +func pipe(__value : Variant, fns : IterableBase) -> Variant: + return compose(fns).call(__value) diff --git a/addons/reactivex/plugin.cfg b/addons/reactivex/plugin.cfg new file mode 100644 index 0000000..6300a6d --- /dev/null +++ b/addons/reactivex/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="GodotRx (GDRx)" +description="Reactive Extensions for Godot Engine version 4" +author="Raoul Zebisch (Neroware)" +version="1.0.0" +script="plugin.gd" diff --git a/addons/reactivex/plugin.gd b/addons/reactivex/plugin.gd new file mode 100644 index 0000000..64dafc8 --- /dev/null +++ b/addons/reactivex/plugin.gd @@ -0,0 +1,13 @@ +@tool +extends EditorPlugin + + +const PLUGIN_NAME = "GDRx" + + +func _enter_tree() -> void: + add_autoload_singleton(PLUGIN_NAME, "res://addons/reactivex/__gdrxsingleton__.gd") + + +func _exit_tree() -> void: + remove_autoload_singleton(PLUGIN_NAME) \ No newline at end of file diff --git a/addons/reactivex/scheduler/catchscheduler.gd b/addons/reactivex/scheduler/catchscheduler.gd new file mode 100644 index 0000000..75925b8 --- /dev/null +++ b/addons/reactivex/scheduler/catchscheduler.gd @@ -0,0 +1,110 @@ +extends PeriodicScheduler +class_name CatchScheduler + +## A scheduler with error handling wrapping a [SchedulerBase] + +var _scheduler : SchedulerBase +var _handler : Callable +var _recursive_original : SchedulerBase +var _recursive_wrapper : CatchScheduler + +## Wraps a scheduler, passed as constructor argument, adding error +## handling for scheduled actions. The handler should return [b]true[/b] to +## indicate it handled the error successfully. Falsy return values will +## be taken to indicate that the error should be escalated (raised by +## this scheduler). +## [br][br] +## [b]Args:[/b] [br] +## [code]scheduler[/code]: The scheduler to be wrapped. [br] +## [code]handler[/code]: Callable to handle errors raised by wrapped scheduler. +func _init(scheduler : SchedulerBase, handler : Callable): + super._init() + self._scheduler = scheduler + self._handler = handler + self._recursive_original = null + self._recursive_wrapper = null + +## Returns the current point in time (timestamp) +func now() -> float: + return self._scheduler.now() + +## Schedule a new action for future execution +func schedule(action : Callable, state = null) -> DisposableBase: + action = self._wrap(action) + return self._scheduler.schedule(action, state) + +## Schedule a new action for future execution in [code]duetime[/code] seconds. +func schedule_relative(duetime, action : Callable, state = null) -> DisposableBase: + action = self._wrap(action) + return self._scheduler.schedule_relative(duetime, action, state) + +## Schedule a new action for future execution at [code]duetime[/code]. +func schedule_absolute(duetime, action : Callable, state = null) -> DisposableBase: + action = self._wrap(action) + return self._scheduler.schedule_absolute(duetime, action, state) + +## Schedule a periodic action for repeated execution every time +## [code]period[/code] seconds have expired. +func schedule_periodic( + period : float, + action : Callable, + state = null) -> DisposableBase: + var schedule_periodic = self._scheduler.get("schedule_periodic") + if schedule_periodic == null: + NotImplementedError.raise() + return Disposable.new() + + var disp : SingleAssignmentDisposable = SingleAssignmentDisposable.new() + var failed : RefValue = RefValue.Set(false) + + var periodic = func(state = null): + if failed.v: + return null + var res = RefValue.Null() + if GDRx.try(func(): + res.v = action.call(state) + ) \ + .catch("Error", func(e): + failed.v = true + if not self._handler.call(e): + GDRx.raise(e) + disp.dispose() + ) \ + .end_try_catch(): + return null + return res.v + + var scheduler : PeriodicScheduler = self._scheduler + disp.disposable = scheduler.schedule_periodic(period, periodic, state) + return disp + +func _clone(scheduler : SchedulerBase) -> CatchScheduler: + return CatchScheduler.new(scheduler, self._handler) + +func _wrap(action : Callable) -> Callable: + var parent : CatchScheduler = self + + var wrapped_action = func(self_ : SchedulerBase, state = null): + var res = RefValue.Null() + if GDRx.try(func(): + res.v = action.call(parent._get_recursive_wrapper(self_), state) + ) \ + .catch("Error", func(ex): + if not parent._handler.call(ex): + GDRx.raise(ex) + ) \ + .end_try_catch(): + return Disposable.new() + return res.v + + return wrapped_action + +func _get_recursive_wrapper(scheduler : SchedulerBase) -> CatchScheduler: + if self._recursive_wrapper == null or self._recursive_original != scheduler: + self._recursive_original = scheduler + var wrapper = self._clone(scheduler) + wrapper._recursive_original = scheduler + wrapper._recursive_wrapper = wrapper + self._recursive_wrapper = wrapper + + return self._recursive_wrapper diff --git a/addons/reactivex/scheduler/currentthreadscheduler.gd b/addons/reactivex/scheduler/currentthreadscheduler.gd new file mode 100644 index 0000000..bf9ce1c --- /dev/null +++ b/addons/reactivex/scheduler/currentthreadscheduler.gd @@ -0,0 +1,73 @@ +extends TrampolineScheduler +class_name CurrentThreadScheduler + +## Represents an object that schedules units of work on the current thread. +## +## You should never schedule timeouts using the [CurrentThreadScheduler], +## as that will block the thread while waiting. +## [br][br] +## Each instance manages a number of trampolines (and queues), one for each +## thread that calls a [code]schedule(...)[/code] method. These trampolines are automatically +## garbage-collected when threads disappear, because they're stored in a weak +## key dictionary. + +var _tramps : WeakKeyDictionary + +## Obtain a singleton instance for the current thread. Please note, if you +## pass this instance to another thread, it will effectively behave as +## if it were created by that other thread (separate trampoline and queue). +## [br] +## [b]Returns:[/b] +## [br] +## The singleton [CurrentThreadScheduler] instance. +static func singleton() -> CurrentThreadScheduler: + var thread = GDRx.get_current_thread() + var class_map_ = GDRx.CurrentThreadScheduler_global_.get_value(CurrentThreadScheduler) + var class_map : WeakKeyDictionary + if class_map_ == null: + class_map = WeakKeyDictionary.new() + GDRx.CurrentThreadScheduler_global_.set_pair(CurrentThreadScheduler, class_map) + else: + class_map = class_map_ + + var self_ + if not class_map.has_key(thread): + self_ = CurrentThreadSchedulerSingleton.new() + class_map.set_pair(thread, self_) + else: + self_ = class_map.get_value(thread) + + return self_ + +func _init(): + self._tramps = WeakKeyDictionary.new() + +## Returns a [Trampoline] +func get_trampoline() -> Trampoline: + var thread = GDRx.get_current_thread() + var tramp = self._tramps.get_value(thread) + if tramp == null: + tramp = Trampoline.new() + self._tramps.set_pair(thread, tramp) + return tramp + +class _Local: + var _tramp : WeakKeyDictionary + + func _init(): + self._tramp = WeakKeyDictionary.new() + + func _trampoline(): + var thread = GDRx.get_current_thread() + if not self._tramp.has_key(thread): + self._tramp.set_pair(thread, Trampoline.new()) + return self._tramp.get_value(thread) + + var tramp : Trampoline: get = _trampoline + +class CurrentThreadSchedulerSingleton extends CurrentThreadScheduler: + func _init(): + pass + + func get_trampoline() -> Trampoline: + return GDRx.CurrentThreadScheduler_local_.tramp diff --git a/addons/reactivex/scheduler/eventloopscheduler.gd b/addons/reactivex/scheduler/eventloopscheduler.gd new file mode 100644 index 0000000..7ad9bcf --- /dev/null +++ b/addons/reactivex/scheduler/eventloopscheduler.gd @@ -0,0 +1,181 @@ +extends PeriodicScheduler +class_name EventLoopScheduler + +## Creates an object that schedules units of work on a designated thread. + +var _is_disposed : bool +var _thread_factory : Callable +var _thread : StartableBase +var _lock : Lock +var _condition : ConditionalVariable +var _queue : PriorityQueue +var _ready_list : Array[ScheduledItem] + +var _exit_if_empty : bool + +var this + +func _init( + thread_factory : Callable = GDRx.concur.default_thread_factory, + exit_if_empty : bool = false +): + super._init() + this = self + this.unreference() + + self._is_disposed = false + self._thread_factory = thread_factory + self._thread = null + self._lock = Lock.new() + self._condition = ConditionalVariable.new() + self._queue = PriorityQueue.new() + self._ready_list = [] + + self._exit_if_empty = exit_if_empty + +## Schedule a new action for future execution +func schedule(action : Callable, state = null) -> DisposableBase: + return self.schedule_absolute(self.now(), action, state) + +## Schedule a new action for future execution in [code]duetime[/code] seconds. +func schedule_relative(duetime, action : Callable, state = null) -> DisposableBase: + duetime = max(DELTA_ZERO, duetime) + return self.schedule_absolute(self.now() + duetime, action, state) + +## Schedule a new action for future execution at [code]duetime[/code]. +func schedule_absolute(duetime, action : Callable, state = null) -> DisposableBase: + if self._is_disposed: + DisposedError.raise() + return Disposable.new() + + var dt : float = duetime + var si : ScheduledItem = ScheduledItem.new(self, dt, state, action) + + if true: + var __ = LockGuard.new(self._lock) + if dt <= self.now(): + self._ready_list.append(si) + else: + self._queue.enqueue(si) + self._condition.notify() # signal that a new item is available + self._ensure_thread() + + return Disposable.new(func(): si.cancel()) + +## Schedule a periodic action for repeated execution every time +## [code]period[/code] seconds have expired. +func schedule_periodic( + period : float, + action : Callable, + state = null) -> DisposableBase: + if self._is_disposed: + DisposedError.raise() + return Disposable.new() + + return super.schedule_periodic(period, action, state) + +## Checks if there is an event loop thread running. +func _has_thread() -> bool: + var __ = LockGuard.new(self._lock) + return not self._is_disposed and self._thread != null + +## Ensures there is an event loop thread running. Should be called under the gate. +func _ensure_thread(): + if self._thread == null: + var _self = self + var thread_manager = GDRx.THREAD_MANAGER + + var _run = func(): + var thread = _self._thread + _self.run() + if thread: + thread_manager.finish(thread) + + var thread_ : StartableBase = self._thread_factory.call(_run) + if thread_: + self._thread = thread_ + self._thread.start() + else: + BadArgumentError.raise() + +## Event loop scheduled on the designated event loop thread. +## The loop is suspended/resumed using the condition which gets notified +## by calls to Schedule or calls to dispose. +func run(): + var ready : Array[ScheduledItem] = [] + + while true: + + var time : float + if true: + var __ = LockGuard.new(self._lock) + + # The notification could be because of a call to dispose. This + # takes precedence over everything else: We'll exit the loop + # immediately. Subsequent calls to Schedule won't ever create a + # new thread. + if self._is_disposed: + return + + # Sort the ready_list (from recent calls for immediate schedule) + # and the due subset of previously queued items. + time = self.now() + while not self._queue.is_empty(): + var due = self._queue.peek().duetime + while not self._ready_list.is_empty() and due > self._ready_list[0].duetime: + ready.append(self._ready_list.pop_front()) + if due > time: + break + ready.append(self._queue.dequeue()) + while not self._ready_list.is_empty(): + ready.append(self._ready_list.pop_front()) + + # Execute the gathered actions + while not ready.is_empty(): + var item = ready.pop_front() + if not item.is_cancelled(): + item.invoke() + + # Wait for next cycle, or if we're done let's exit if so configured + if true: + var __ = LockGuard.new(self._lock) + + if not self._ready_list.is_empty(): + continue + + elif not self._queue.is_empty(): + time = self.now() + var item = self._queue.peek() + var seconds = item.duetime - time + if seconds > 0: + #print("timeout: ", seconds) + self._condition.wait_for(self._lock, seconds) + + elif self._exit_if_empty: + self._thread = null + return + + else: + self._condition.wait(self._lock) + +## Ends the thread associated with this scheduler. All +## remaining work in the scheduler queue is abandoned. +func dispose(): + var thread : StartableBase = null + + if true: + var __ = LockGuard.new(self._lock) + if not this._is_disposed: + this._is_disposed = true + this._ready_list.clear() + this._queue.clear() + this._condition.notify() + thread = this._thread + this._thread = null + + if thread: + GDRx.THREAD_MANAGER.finish(thread) + +func _notification(what): + if what == NOTIFICATION_PREDELETE: + this.dispose() diff --git a/addons/reactivex/scheduler/immediatescheduler.gd b/addons/reactivex/scheduler/immediatescheduler.gd new file mode 100644 index 0000000..b1d2fef --- /dev/null +++ b/addons/reactivex/scheduler/immediatescheduler.gd @@ -0,0 +1,28 @@ +extends Scheduler +class_name ImmediateScheduler + +## Represents an object that schedules units of work to run immediately +## on the current thread. +## +## You're not allowed to schedule timeouts using the +## [ImmediateScheduler] since that will block the current thread while waiting. +## Attempts to do so will raise a [WouldBlockError] + +func _init(verify_ = null): + if not verify_ == "GDRx": + push_warning("Warning! Must only instance Scheduler from GDRx singleton!") + +static func singleton() -> ImmediateScheduler: + return GDRx.ImmediateScheduler_ + +func schedule(action : Callable, state = null) -> DisposableBase: + return self.invoke_action(action, state) + +func schedule_relative(duetime, action : Callable, state = null) -> DisposableBase: + if duetime > DELTA_ZERO: + WouldBlockError.raise() + return Disposable.new() + return self.invoke_action(action, state) + +func schedule_absolute(duetime, action : Callable, state = null) -> DisposableBase: + return self.schedule_relative(duetime - self.now(), action, state) diff --git a/addons/reactivex/scheduler/newthreadscheduler.gd b/addons/reactivex/scheduler/newthreadscheduler.gd new file mode 100644 index 0000000..be9682e --- /dev/null +++ b/addons/reactivex/scheduler/newthreadscheduler.gd @@ -0,0 +1,68 @@ +extends PeriodicScheduler +class_name NewThreadScheduler + +## Creates an object that schedules each unit of work on a separate thread. + +var thread_factory : Callable + +func _init(thread_factory_ : Callable = GDRx.concur.default_thread_factory): + super._init() + self.thread_factory = thread_factory_ + +## Access to global singleton +static func singleton() -> NewThreadScheduler: + return GDRx.NewThreadScheduler_ + +## Schedule a new action for future execution +func schedule(action : Callable, state = null) -> DisposableBase: + var scheduler : EventLoopScheduler = EventLoopScheduler.new(self.thread_factory, true) + return CompositeDisposable.new([ + scheduler.schedule(action, state), + Disposable.Cast(scheduler) + ]) + +## Schedule a new action for future execution in [code]duetime[/code] seconds. +func schedule_relative(duetime, action : Callable, state = null) -> DisposableBase: + var scheduler : EventLoopScheduler = EventLoopScheduler.new(self.thread_factory, true) + return CompositeDisposable.new([ + scheduler.schedule_relative(duetime, action, state), + Disposable.Cast(scheduler) + ]) + +## Schedule a new action for future execution at [code]duetime[/code]. +func schedule_absolute(duetime, action : Callable, state = null) -> DisposableBase: + var dt : float = duetime + return self.schedule_relative(dt - self.now(), action, state) + +## Schedule a periodic action for repeated execution every time +## [code]period[/code] seconds have expired. +func schedule_periodic( + period : float, + action : Callable, + state = null) -> DisposableBase: + var seconds : RefValue = RefValue.Set(period) + var timeout : RefValue = RefValue.Set(seconds.v) + var _state : RefValue = RefValue.Set(state) + var disposed : ThreadingEvent = ThreadingEvent.new() + + var run = func(): + while true: + if timeout.v > 0.0: + disposed.wait(timeout.v) + if disposed.is_set(): + return + + var time : float = self.now() + + _state.v = action.call(_state.v) + + timeout.v = seconds.v - (self.now() - time) + + var thread : StartableBase = self.thread_factory.call(run) + thread.start() + + var dispose = func(): + disposed.set_flag() + thread.wait_to_finish() + + return Disposable.new(dispose) diff --git a/addons/reactivex/scheduler/periodicscheduler.gd b/addons/reactivex/scheduler/periodicscheduler.gd new file mode 100644 index 0000000..5dcc656 --- /dev/null +++ b/addons/reactivex/scheduler/periodicscheduler.gd @@ -0,0 +1,69 @@ +extends PeriodicSchedulerBase +class_name PeriodicScheduler + +const UTC_ZERO : float = Scheduler.UTC_ZERO +const DELTA_ZERO : float = Scheduler.DELTA_ZERO + +## Represents a notion of time for this scheduler. Tasks being +## scheduled on a scheduler will adhere to the time denoted by this +## property. +## [br] +## [b]Returns:[/b] +## [br] +## The scheduler's current time, as a datetime instance. +func now() -> float: + return GDRx.basic.default_now() + +## Invoke the given given action. This is typically called by instances +## of [ScheduledItem]. +## [br] +## [b]Args:[/b] +## [br] +## [code]action[/code] Action to be executed. +## [br] +## [code]state[/code] [Optional] state to be given to the action function. +## [br][br] +## [b]Returns:[/b] +## [br] +## The disposable object returned by the action, if any; or a new +## (no-op) disposable otherwise. +func invoke_action(action : Callable, state = null) -> DisposableBase: + var ret = action.call(self, state) + if ret is DisposableBase: + return ret + return Disposable.new() + +## Schedule a periodic action for repeated execution every time +## [code]period[/code] seconds have expired. +func schedule_periodic( + period : float, + action : Callable, + state = null) -> DisposableBase: + + var disp : MultipleAssignmentDisposable = MultipleAssignmentDisposable.new() + var seconds : float = period + + var periodic : Callable = func(scheduler : SchedulerBase, state = null, periodic_ : Callable = func(__, ___, ____): return) -> Disposable: + if disp.is_disposed: + return null + + var _now : float = scheduler.now() + + var state_res = RefValue.Null() + if not GDRx.try(func(): + state_res.v = action.call(state) + ) \ + .catch("Error", func(err): + disp.dispose() + GDRx.raise(err) + ) \ + .end_try_catch(): state = state_res.v + + var time = seconds - (scheduler.now() - _now) + disp.disposable = scheduler.schedule_relative(time, periodic_.bind(periodic_), state) + + return null + periodic = periodic.bind(periodic) + + disp.disposable = self.schedule_relative(period, periodic, state) + return disp diff --git a/addons/reactivex/scheduler/scenetreetimeoutscheduler.gd b/addons/reactivex/scheduler/scenetreetimeoutscheduler.gd new file mode 100644 index 0000000..7ce00ed --- /dev/null +++ b/addons/reactivex/scheduler/scenetreetimeoutscheduler.gd @@ -0,0 +1,113 @@ +extends PeriodicScheduler +class_name SceneTreeTimeoutScheduler +## A scheduler that schedules work via a [SceneTreeTimer]. + +var _process_always : bool +var _process_in_physics : bool +var _ignore_time_scale : bool + +func _init( + verify_ = null, + process_always : bool = false, + process_in_physics : bool = false, + ignore_time_scale : bool = false): + if not verify_ == "GDRx": + push_warning("Warning! Must only instance Scheduler from GDRx singleton!") + self._process_always = process_always + self._process_in_physics = process_in_physics + self._ignore_time_scale = ignore_time_scale + +## Returns singleton +static func singleton( + process_always : bool = false, + process_in_physics : bool = false, + ignore_time_scale : bool = false) -> SceneTreeTimeoutScheduler: + return GDRx.SceneTreeTimeoutScheduler_[ + int(process_always) * 0b1 + + int(process_in_physics) * 0b10 + + int(ignore_time_scale) * 0b100 + ] + +## Schedules an action to be executed. +## [br] +## [b]Args:[/b] +## [br] +## [code]action[/code] Action to be executed. +## [br] +## [code]state[/code] [Optional] state to be given to the action function. +## [br][br] +## [b]Returns:[/b] +## [br] +## The disposable object used to cancel the scheduled action +## (best effort). +func schedule(action : Callable, state = null) -> DisposableBase: + var sad : SingleAssignmentDisposable = SingleAssignmentDisposable.new() + + var interval = func(): + sad.disposable = self.invoke_action(action, state) + + var timer : SceneTreeTimer = GDRx.get_tree().create_timer( + 0.0, self._process_always, self._process_in_physics, self._ignore_time_scale) + timer.connect("timeout", func(): interval.call() ; _cancel_timer(timer)) + + var dispose = func(): + _cancel_timer(timer) + + return CompositeDisposable.new([sad, Disposable.new(dispose)]) + +## Schedules an action to be executed after duetime. +## [br] +## [b]Args:[/b] +## [br] +## [code]duetime[/code] Relative time after which to execute the action. +## [br] +## [code]action[/code] Action to be executed. +## [br] +## [code]state[/code] [Optional] state to be given to the action function. +## [br][br] +## +## [b]Returns:[/b] +## [br] +## The disposable object used to cancel the scheduled action +## (best effort). +func schedule_relative(duetime, action : Callable, state = null) -> DisposableBase: + var seconds : float = duetime + if seconds <= 0.0: + return self.schedule(action, state) + + var sad : SingleAssignmentDisposable = SingleAssignmentDisposable.new() + + var interval = func(): + sad.disposable = self.invoke_action(action, state) + + var timer = GDRx.get_tree().create_timer( + seconds, self._process_always, self._process_in_physics, self._ignore_time_scale) + timer.connect("timeout", func(): interval.call() ; _cancel_timer(timer)) + + var dispose = func(): + _cancel_timer(timer) + + return CompositeDisposable.new([sad, Disposable.new(dispose)]) + +## Schedules an action to be executed at duetime. +## [br] +## [b]Args:[/b] +## [br] +## [code]duetime[/code] Absolute time at which to execute the action. +## [br] +## [code]action[/code] Action to be executed. +## [br] +## [code]state[/code] [Optional] state to be given to the action function. +## [br][br] +## +## [b]Returns:[/b] +## [br] +## The disposable object used to cancel the scheduled action +## (best effort). +func schedule_absolute(duetime, action : Callable, state = null) -> DisposableBase: + return self.schedule_relative(duetime - self.now(), action, state) + +## Utility function to cancel a timer +func _cancel_timer(timer : SceneTreeTimer): + for conn in timer.timeout.get_connections(): + timer.timeout.disconnect(conn["callable"]) diff --git a/addons/reactivex/scheduler/scheduleditem.gd b/addons/reactivex/scheduler/scheduleditem.gd new file mode 100644 index 0000000..a7dd17e --- /dev/null +++ b/addons/reactivex/scheduler/scheduleditem.gd @@ -0,0 +1,38 @@ +extends Comparable +class_name ScheduledItem + +## Represents a scheduled action. + +var scheduler : SchedulerBase +var state +var action : Callable +var duetime : float +var disposable : SingleAssignmentDisposable + +func _init(scheduler_ : SchedulerBase, duetime_ : float, state_ = null, action_ : Callable = GDRx.basic.noop): + self.scheduler = scheduler_ + self.state = state_ + self.action = action_ + self.duetime = duetime_ + self.disposable = SingleAssignmentDisposable.new() + +func invoke(): + var ret = self.scheduler.invoke_action(self.action, self.state) + self.disposable.disposable = ret + +## Cancels the work item by disposing the resource returned by +## invoke_core as soon as possible. +func cancel(): + self.disposable.dispose() + +func is_cancelled() -> bool: + return self.disposable.is_disposed + +func lt(other : ScheduledItem) -> bool: + return self.duetime < other.duetime + +func gt(other : ScheduledItem) -> bool: + return self.duetime > other.duetime + +func eq(other : ScheduledItem) -> bool: + return self.duetime == other.duetime diff --git a/addons/reactivex/scheduler/scheduler.gd b/addons/reactivex/scheduler/scheduler.gd new file mode 100644 index 0000000..acec5eb --- /dev/null +++ b/addons/reactivex/scheduler/scheduler.gd @@ -0,0 +1,39 @@ +extends SchedulerBase +class_name Scheduler + +## Base class for the various scheduler implementations in this package as +## This does not include an implementation of [method PeriodicSchedulerBase.schedule_periodic], +## refer to [PeriodicScheduler]. + +const UTC_ZERO : float = 0 +const DELTA_ZERO : float = 0 + +## Represents a notion of time for this scheduler. Tasks being +## scheduled on a scheduler will adhere to the time denoted by this +## property. +## [br] +## [b]Returns:[/b] +## [br] +## The scheduler's current time, as a datetime instance. +func now() -> float: + return GDRx.basic.default_now() + +## Invoke the given action. This is typically called by instances +## of [ScheduledItem]. +## [br] +## [b]Args:[/b] +## [br] +## [code]action[/code] Action to be executed. +## [br] +## [code]state[/code] [Optional] state to be given to the action function. +## [br][br] +## +## [b]Returns:[/b] +## [br] +## The disposable object returned by the action, if any; or a new +## (no-op) disposable otherwise. +func invoke_action(action : Callable, state = null) -> DisposableBase: + var ret = action.call(self, state) + if ret is DisposableBase: + return ret + return Disposable.new() diff --git a/addons/reactivex/scheduler/threadedtimeoutscheduler.gd b/addons/reactivex/scheduler/threadedtimeoutscheduler.gd new file mode 100644 index 0000000..55025d5 --- /dev/null +++ b/addons/reactivex/scheduler/threadedtimeoutscheduler.gd @@ -0,0 +1,104 @@ +extends PeriodicScheduler +class_name ThreadedTimeoutScheduler +## A scheduler that schedules work via a threaded timer. + +func _init(verify_ = null): + if not verify_ == "GDRx": + push_warning("Warning! Must only instance Scheduler from GDRx singleton!") + +## Returns singleton +static func singleton() -> ThreadedTimeoutScheduler: + return GDRx.ThreadedTimeoutScheduler_ + +## Schedules an action to be executed. +## [br] +## [b]Args:[/b] +## [br] +## [code]action[/code] Action to be executed. +## [br] +## [code]state[/code] [Optional] state to be given to the action function. +## [br][br] +## [b]Returns:[/b] +## [br] +## The disposable object used to cancel the scheduled action +## (best effort). +func schedule(action : Callable, state = null) -> DisposableBase: + var sad : SingleAssignmentDisposable = SingleAssignmentDisposable.new() + + var interval = func(): + sad.disposable = self.invoke_action(action, state) + + var disposed = RefValue.Set(false) + var dispose = func(): + disposed.v = true + + var timer_thread = RefValue.Null() + var timer = func(): + OS.delay_msec(0) + if not disposed.v: + interval.call() + GDRx.THREAD_MANAGER.finish(timer_thread.v) + + timer_thread.v = GDRx.concur.default_thread_factory.call(timer) + timer_thread.v.start() + + return CompositeDisposable.new([sad, Disposable.new(dispose)]) + +## Schedules an action to be executed after duetime. +## [br] +## [b]Args:[/b] +## [br] +## [code]duetime[/code] Relative time after which to execute the action. +## [br] +## [code]action[/code] Action to be executed. +## [br] +## [code]state[/code] [Optional] state to be given to the action function. +## [br][br] +## +## [b]Returns:[/b] +## [br] +## The disposable object used to cancel the scheduled action +## (best effort). +func schedule_relative(duetime, action : Callable, state = null) -> DisposableBase: + var seconds : float = duetime + if seconds <= 0.0: + return self.schedule(action, state) + + var sad : SingleAssignmentDisposable = SingleAssignmentDisposable.new() + + var interval = func(): + sad.disposable = self.invoke_action(action, state) + + var disposed = RefValue.Set(false) + var dispose = func(): + disposed.v = true + + var timer_thread = RefValue.Null() + var timer = func(): + OS.delay_msec((1000.0 * seconds) as int) + if not disposed.v: + interval.call() + GDRx.THREAD_MANAGER.finish(timer_thread.v) + + timer_thread.v = GDRx.concur.default_thread_factory.call(timer) + timer_thread.v.start() + + return CompositeDisposable.new([sad, Disposable.new(dispose)]) + +## Schedules an action to be executed at duetime. +## [br] +## [b]Args:[/b] +## [br] +## [code]duetime[/code] Absolute time at which to execute the action. +## [br] +## [code]action[/code] Action to be executed. +## [br] +## [code]state[/code] [Optional] state to be given to the action function. +## [br][br] +## +## [b]Returns:[/b] +## [br] +## The disposable object used to cancel the scheduled action +## (best effort). +func schedule_absolute(duetime, action : Callable, state = null) -> DisposableBase: + return self.schedule_relative(duetime - self.now(), action, state) diff --git a/addons/reactivex/scheduler/trampoline.gd b/addons/reactivex/scheduler/trampoline.gd new file mode 100644 index 0000000..30468e6 --- /dev/null +++ b/addons/reactivex/scheduler/trampoline.gd @@ -0,0 +1,60 @@ +class_name Trampoline + +var _idle : bool +var _queue : PriorityQueue +var _lock : Lock +var _condition : ConditionalVariable + +func _init(): + self._idle = true + self._queue = PriorityQueue.new() + self._lock = Lock.new() + self._condition = ConditionalVariable.new() + +func idle() -> bool: + var __ = LockGuard.new(self._lock) + return self._idle + +func run(item : ScheduledItem): + if true: + var __ = LockGuard.new(self._lock) + self._queue.enqueue(item) + if self._idle: + self._idle = false + else: + self._condition.notify() + return + + GDRx.try(self._run).end_try_catch() + + if true: + var __ = LockGuard.new(self._lock) + self._idle = true + self._queue.clear() + +func _run(): + var ready : Array[ScheduledItem] = [] + while true: + if true: + var __ = LockGuard.new(self._lock) + while(self._queue.size() > 0): + var item : ScheduledItem = self._queue.peek() + if item.duetime <= item.scheduler.now(): + self._queue.dequeue() + ready.append(item) + else: + break + + while ready.size() > 0: + var item : ScheduledItem = ready.pop_front() + if not item.is_cancelled(): + item.invoke() + + if true: + var __ = LockGuard.new(self._lock) + if self._queue.size() == 0: + break + var item : ScheduledItem = self._queue.peek() + var seconds = item.duetime - item.scheduler.now() + if seconds > 0.0: + self._condition.wait_for(self._lock, seconds) diff --git a/addons/reactivex/scheduler/trampolinescheduler.gd b/addons/reactivex/scheduler/trampolinescheduler.gd new file mode 100644 index 0000000..78f5ff5 --- /dev/null +++ b/addons/reactivex/scheduler/trampolinescheduler.gd @@ -0,0 +1,95 @@ +extends Scheduler +class_name TrampolineScheduler + +## Represents an object that schedules units of work on the [Trampoline]. +## +## You should never schedule timeouts using the [TrampolineScheduler], as +## it will block the thread while waiting. +## +## Each instance has its own trampoline (and queue), and you can schedule work +## on it from different threads. Beware though, that the first thread to call +## a [code]schedule[/code] method while the trampoline is idle will then remain occupied +## until the queue is empty. + +var _tramp : Trampoline + +func _init(): + self._tramp = Trampoline.new() + +## Returns the [Trampoline] +func get_trampoline() -> Trampoline: + return self._tramp + +## Schedules an action to be executed. +## [br] +## [b]Args:[/b] +## [br] +## [code]action[/code] Action to be executed. +## [br] +## [code]state[/code] [Optional] state to be given to the action function. +## [br][br] +## [b]Returns:[/b] +## [br] +## The disposable object used to cancel the scheduled action +## (best effort). +func schedule(action : Callable, state = null) -> DisposableBase: + return self.schedule_absolute(self.now(), action, state) + +## Schedules an action to be executed after duetime. +## [br] +## [b]Args:[/b] +## [br] +## [code]duetime[/code] Relative time after which to execute the action. +## [br] +## [code]action[/code] Action to be executed. +## [br] +## [code]state[/code] [Optional] state to be given to the action function. +## [br][br] +## +## [b]Returns:[/b] +## [br] +## The disposable object used to cancel the scheduled action +## (best effort). +func schedule_relative(duetime, action : Callable, state = null) -> DisposableBase: + duetime = max(DELTA_ZERO, duetime) + return self.schedule_absolute(self.now() + duetime, action, state) + +## Schedules an action to be executed at duetime. +## [br] +## [b]Args:[/b] +## [br] +## [code]duetime[/code] Absolute time at which to execute the action. +## [br] +## [code]action[/code] Action to be executed. +## [br] +## [code]state[/code] [Optional] state to be given to the action function. +## [br][br] +## +## [b]Returns:[/b] +## [br] +## The disposable object used to cancel the scheduled action +## (best effort). +func schedule_absolute(duetime, action : Callable, state = null) -> DisposableBase: + var dt : float = duetime + if dt > self.now(): + push_warning("Do not schedule blocking work!") + var item : ScheduledItem = ScheduledItem.new(self, dt, state, action) + + self.get_trampoline().run(item) + + return item.disposable + +## Test if scheduling is required. +## +## Gets a value indicating whether the caller must call a +## schedule method. If the [Trampoline] is active, then it returns +## [code]false[/code]; otherwise, if the trampoline is not active, then it +## returns [code]true[/code]. +func schedule_required() -> bool: + return self.get_trampoline().idle() + +## Method for testing the [TrampolineScheduler]. +func ensure_trampoline(action : Callable): + if self.schedule_required(): + return self.schedule(action) + return action.call(self, null) diff --git a/addons/reactivex/scheduler/virtualtimescheduler.gd b/addons/reactivex/scheduler/virtualtimescheduler.gd new file mode 100644 index 0000000..1f3e6bc --- /dev/null +++ b/addons/reactivex/scheduler/virtualtimescheduler.gd @@ -0,0 +1,160 @@ +extends PeriodicScheduler +class_name VirtualTimeScheduler + +const MAX_SPINNING = 100 + +var _clock : float +var _is_enabled : bool +var _lock : Lock +var _queue : PriorityQueue + +func _init(initial_clock = 0): + super._init() + self._clock = initial_clock + self._is_enabled = false + self._lock = Lock.new() + self._queue = PriorityQueue.new() + +func _get_clock() -> float: + var __ = LockGuard.new(self._lock) + return self._clock + +var clock : float: + get: + return _get_clock() + +func now() -> float: + return self._clock + +## Schedule a new action for future execution +func schedule(action : Callable, state = null) -> DisposableBase: + return self.schedule_absolute(self._clock, action, state) + +## Schedule a new action for future execution in [code]duetime[/code] seconds. +func schedule_relative(duetime, action : Callable, state = null) -> DisposableBase: + var time : float = self.add(self._clock, duetime) + return self.schedule_absolute(time, action, state) + +## Schedule a new action for future execution at [code]duetime[/code]. +func schedule_absolute(duetime, action : Callable, state = null) -> DisposableBase: + var dt : float = duetime + var si : ScheduledItem = ScheduledItem.new(self, dt, state, action) + if true: + var __ = LockGuard.new(self._lock) + self._queue.enqueue(si) + return si.disposable + +## Starts the virtual time scheduler. +func start(): + if true: + var __ = LockGuard.new(self._lock) + if self._is_enabled: + return + self._is_enabled = true + + var spinning : int = 0 + + while true: + var item : ScheduledItem + if true: + var __ = LockGuard.new(self._lock) + if not self._is_enabled or self._queue.is_empty(): + break + + item = self._queue.dequeue() + + if item.duetime > self.now(): + self._clock = item.duetime + spinning = 0 + + elif spinning > MAX_SPINNING: + self._clock += 1.0 + spinning = 0 + + if not item.is_cancelled(): + item.invoke() + spinning += 1 + + self.stop() + +## Stops the virtual time scheduler. +func stop(): + var __ = LockGuard.new(self._lock) + self._is_enabled = false + +## Advances the schedulers clock to the specified absolute time, +## running all work til that point. +## [br][br] +## [b]Args:[/b] [br] +## [code]time[/code]: Absolute time to advance the schedulers clock to. +func advance_to(time : float): + var dt : float = time + if true: + var __ = LockGuard.new(self._lock) + if self.now() > dt: + ArgumentOutOfRangeError.raise() + return + + if self.now() == dt or self._is_enabled: + return + + self._is_enabled = true + + while true: + var item : ScheduledItem + if true: + var __ = LockGuard.new(self._lock) + if not self._is_enabled or self._queue.is_empty(): + break + + item = self._queue.peek() + + if item.duetime > dt: + break + + if item.duetime > self.now(): + self._clock = item.duetime + + self._queue.dequeue() + + if not item.is_cancelled(): + item.invoke() + + if true: + var __ = LockGuard.new(self._lock) + self._is_enabled = false + self._clock = dt + +## Advances the schedulers clock by the specified relative time, +## running all work scheduled for that timespan. +## [br][br] +## [b]Args:[/b] [br] +## [code]time[/code]: Relative time to advance the schedulers clock by. +func advance_by(time : float): + self.advance_to(self.add(self.now(), time)) + +## Advances the schedulers clock by the specified relative time. +## [br][br] +## [b]Args:[/b] [br] +## [code]time[/code]: Relative time to advance the schedulers clock by. +func sleep(time : float): + var absolute = self.add(self.now(), time) + var dt : float = absolute + + if self.now() > dt: + ArgumentOutOfRangeError.raise() + return + + var __ = LockGuard.new(self._lock) + self._clock = dt + +## Adds a relative time value to an absolute time value. +## [br][br] +## [b]Args:[/b] [br] +## [code]absolute[/code]: Absolute virtual time value. [br] +## [code]relative[/code]: Relative virtual time value to add. [br] +## [br] +## [b]Returns:[/b] [br] +## The resulting absolute virtual time sum value. +static func add(absolute : float, relative : float) -> float: + return absolute + relative diff --git a/addons/reactivex/subject/asyncsubject.gd b/addons/reactivex/subject/asyncsubject.gd new file mode 100644 index 0000000..bfc36be --- /dev/null +++ b/addons/reactivex/subject/asyncsubject.gd @@ -0,0 +1,83 @@ +extends Subject +class_name AsyncSubject + +## Represents the result of an asynchronous operation. +## +## The last value before the close notification, or the error received through +## [i]on_error[/i], is sent to all subscribed observers. + +var value +var has_value : bool + +## Creates a subject that can only receive one value and that value is +## cached for all future observations. +func _init(): + super._init() + + self.value = null + self.has_value = false + +func _subscribe_core( + observer : ObserverBase, + _scheduler : SchedulerBase = null, +) -> DisposableBase: + if true: + var __ = LockGuard.new(self.lock) + if not check_disposed(): return Disposable.new() + if not self.is_stopped: + self.observers.append(observer) + return InnerSubscription.new(self, observer) + + var err = self.error_value + var has_value_ = self.has_value + var value_ = self.value + + if err != null: + observer.on_error(err) + elif has_value_: + observer.on_next(value_) + observer.on_completed() + else: + observer.on_completed() + + return Disposable.new() + +## Remember the value. Upon completion, the most recently received value +## will be passed on to all subscribed observers. +## [br][br] +## [b]Args:[/b] +## [br] +## [code]value[/code] The value to remember until completion +func _on_next_core(i): + var __ = LockGuard.new(self.lock) + self.value = i + self.has_value = true + +## Notifies all subscribed observers of the end of the sequence. The +## most recently received value, if any, will now be passed on to all +## subscribed observers. +func _on_completed_core(): + var observers_ + var has_value_ + var value_ + + if true: + var __ = LockGuard.new(self.lock) + observers_ = self.observers.duplicate() + self.observers.clear() + value_ = self.value + has_value_ = self.has_value + + if has_value_: + for o in observers_: + o.on_next(value_) + o.on_completed() + else: + for o in observers_: + o.on_completed() + +## Unsubscribe all observers and release resources. +func dispose(): + var __ = LockGuard.new(self.lock) + self.value = null + super.dispose() diff --git a/addons/reactivex/subject/behaviorsubject.gd b/addons/reactivex/subject/behaviorsubject.gd new file mode 100644 index 0000000..066c4d7 --- /dev/null +++ b/addons/reactivex/subject/behaviorsubject.gd @@ -0,0 +1,63 @@ +extends Subject +class_name BehaviorSubject + +## Represents a value that changes over time. +## +## Observers can subscribe to the subject to receive the last (or initial) value and +## all subsequent notifications. + +var value + +## Initializes a new instance of the BehaviorSubject class which +## creates a subject that caches its last value and starts with the +## specified value. +## [br] +## [b]Args:[/b] +## [br] +## [code]value[/code] Initial value sent to observers when no other value has been +## received by the subject yet. +func _init(value_): + super._init() + self.value = value_ + +func _subscribe_core( + observer : ObserverBase, + _scheduler : SchedulerBase = null, +) -> DisposableBase: + var err + if true: + var __ = LockGuard.new(self.lock) + if not check_disposed(): return Disposable.new() + if not self.is_stopped: + self.observers.append(observer) + observer.on_next(self.value) + return InnerSubscription.new(self, observer) + err = self.error_value + + if err != null: + observer.on_error(err) + else: + observer.on_completed() + + return Disposable.new() + +## Notifies all subscribed observers with the value. +func _on_next_core(i): + var observers_ + if true: + var __ = LockGuard.new(self.lock) + observers_ = self.observers.duplicate() + self.value = i + + for observer in observers_: + observer.on_next(i) + +## Release all resources. +## +## Releases all resources used by the current instance of the +## [BehaviorSubject] class and unsubscribe all observers. +func dispose(): + if true: + var __ = LockGuard.new(self.lock) + self.value = null + super.dispose() diff --git a/addons/reactivex/subject/innersubscription.gd b/addons/reactivex/subject/innersubscription.gd new file mode 100644 index 0000000..e897123 --- /dev/null +++ b/addons/reactivex/subject/innersubscription.gd @@ -0,0 +1,20 @@ +extends DisposableBase +class_name InnerSubscription + +var subject +var observer +var lock : RLock + + +func _init(subject_, observer_ = null): + self.subject = subject_ + self.observer = observer_ + self.lock = RLock.new() + super._init() + +func dispose(): + var __ = LockGuard.new(this.lock) + if not this.subject.is_disposed and this.observer != null: + if this.observer in this.subject.observers: + this.subject.observers.erase(this.observer) + this.observer = null diff --git a/addons/reactivex/subject/replaysubject.gd b/addons/reactivex/subject/replaysubject.gd new file mode 100644 index 0000000..26a5b5b --- /dev/null +++ b/addons/reactivex/subject/replaysubject.gd @@ -0,0 +1,140 @@ +extends Subject +class_name ReplaySubject + +## Represents an object that is both an observable sequence as well +## as an observer. +## +## Each notification is broadcasted to all subscribed +## and future observers, subject to buffer trimming policies. + +class RemovableDisposable extends DisposableBase: + var subject : Subject + var observer : Observer + + func _init(subject_ : Subject, observer_ : Observer): + self.subject = subject_ + self.observer = observer_ + + func dispose(): + self.observer.dispose() + if not self.subject.is_disposed and self.observer in self.subject.observers: + self.subject.observers.erase(self.observer) + +class QueueItem extends Tuple: + func _init(interval_ : float, value_): + super._init([interval_, value_]) + var interval : float: + get: return self.at(0) + var value: + get: return self.at(1) + +var buffer_size : int +var window : float +var scheduler : SchedulerBase +var queue : Array + +## Initializes a new instance of the [ReplaySubject] class with +## the specified buffer size, window and scheduler. +## [br] +## [b]Args:[/b] +## [br] +## [code]buffer_size[/code] [Optional] Maximum element count of the replay +## buffer. +## [br] +## [code]window[/code] [Optional] Maximum time length of the replay buffer. +## [br] +## [code]scheduler[/code] [Optional] Scheduler the observers are invoked on. +func _init( + buffer_size_ : int = GDRx.util.MAX_SIZE, + window_ : float = GDRx.util.MAX_SIZE, + scheduler_ : SchedulerBase = null +): + super._init() + self.buffer_size = buffer_size_ + self.scheduler = scheduler_ if scheduler_ != null else CurrentThreadScheduler.singleton() + self.window = window_ + self.queue = [] + +func _subscribe_core( + observer : ObserverBase, + _scheduler : SchedulerBase = null, +) -> DisposableBase: + var so = ScheduledObserver.new(self.scheduler, observer) + var subscription = RemovableDisposable.new(self, so) + + if true: + var __ = LockGuard.new(self.lock) + if not check_disposed(): return Disposable.new() + self._trim(self.scheduler.now()) + self.observers.append(so) + + for item in self.queue: + so.on_next(item.value) + + if self.error_value != null: + so.on_error(self.error_value) + elif self.is_stopped: + so.on_completed() + + so.ensure_active() + return subscription + +func _trim(now : float): + while self.queue.size() > self.buffer_size: + self.queue.pop_front() + + while self.queue.size() > 0 and (now - self.queue[0].interval) > self.window: + self.queue.pop_front() + +## Notifies all subscribed observers with the value. +func _on_next_core(i): + var observers_ + if true: + var __ = LockGuard.new(self.lock) + observers_ = self.observers.duplicate() + var now = self.scheduler.now() + self.queue.append(QueueItem.new(now, i)) + self._trim(now) + + for observer in observers_: + observer.on_next(i) + + for observer in observers_: + var so : ScheduledObserver = observer + so.ensure_active() + +## Notifies all subscribed observers with the error. +func _on_error_core(e): + var observers_ + if true: + var __ = LockGuard.new(self.lock) + observers_ = self.observers.duplicate() + self.observers.clear() + self.error_value = e + var now = self.scheduler.now() + self._trim(now) + + for observer in observers_: + observer.on_error(e) + (observer as ScheduledObserver).ensure_active() + +## Notifies all subscribed observers of the end of the sequence. +func _on_completed_core(): + var observers_ + if true: + var __ = LockGuard.new(self.lock) + observers_ = self.observers.duplicate() + self.observers.clear() + var now = self.scheduler.now() + self._trim(now) + + for observer in observers_: + observer.on_completed() + (observer as ScheduledObserver).ensure_active() + +## Releases all resources used by the current instance of the +## [ReplaySubject] class and unsubscribe all observers. +func dispose(): + var __ = LockGuard.new(self.lock) + self.queue.clear() + super.dispose() diff --git a/addons/reactivex/subject/subject.gd b/addons/reactivex/subject/subject.gd new file mode 100644 index 0000000..56d6d28 --- /dev/null +++ b/addons/reactivex/subject/subject.gd @@ -0,0 +1,957 @@ +extends SubjectBase +class_name Subject + +var is_stopped : bool +var lock : RLock + +var is_disposed : bool +var observers : Array[ObserverBase] +var error_value + +var this + +## View to the [Observable] behavior of the [Subject] +var obs : Observable: + get: return self.as_observable() +## View to the [ObserverBase] behavior of the [Subject] +var obv : ObserverBase: + get: return self.as_observer() + +func _init(): + this = self + this.unreference() + + self.is_stopped = false + self.lock = RLock.new() + + self.is_disposed = false + self.observers = [] + self.error_value = null + +func check_disposed(): + if self.is_disposed: + DisposedError.raise() + return false + return true + +## Subscribe an observer to the observable sequence. +## [br] +## You may subscribe using an observer or callbacks, not both; if the first +## argument is an instance of [Observer] ([ObserverBase]) or if +## it has a [Callable] attribute named [code]on_next[/code], then any callback +## arguments will be ignored. +## [br][br] +## [b]Examples:[/b] +## [codeblock] +## source.subscribe(observer) +## source.subscribe(on_next) +## source.subscribe(on_next, on_error) +## source.subscribe(on_next, on_error, on_completed) +## source.subscribe(on_next, on_error, on_completed, scheduler) +## [/codeblock] +## [br] +## [b]Args:[/b] +## [br] +## [code]observer[/code] The object that is to receive +## notifications. +## [br] +## [code]on_error[/code] [Optional] Action to invoke upon exceptional termination +## of the observable sequence. +## [br] +## [code]on_completed[/code] [Optional] Action to invoke upon graceful termination +## of the observable sequence. +## [br] +## [code]on_next[/code] Action to invoke for each element in the +## observable sequence. +## [br] +## [code]scheduler[/code] [Optional] The default scheduler to use for this +## subscription. +## [br][br] +## [b]Returns:[/b] +## [br] +## Disposable object representing an observer's subscription to +## the observable sequence. +func subscribe( + _on_next = null, # Callable or Observer or Object with callbacks + _on_error : Callable = GDRx.basic.noop, + _on_completed : Callable = GDRx.basic.noop, + scheduler : SchedulerBase = null) -> DisposableBase: + if _on_next == null: + _on_next = GDRx.basic.noop + + if _on_next is ObserverBase: + var _obv : ObserverBase = _on_next + _on_next = func(i): _obv.on_next.call(i) + _on_error = func(e): _obv.on_error.call(e) + _on_completed = func(): _obv.on_completed.call() + elif _on_next is Object and _on_next.has_method("on_next"): + var _obv : Object = _on_next + if _obv.has_method("on_next"): + _on_next = func(i): _obv.on_next.call(i) + if _obv.has_method("on_error"): + _on_error = func(e): _obv.on_error.call(e) + if _obv.has_method("on_completed"): + _on_completed = func(): _obv.on_completed.call() + + var auto_detach_observer : AutoDetachObserver = AutoDetachObserver.new( + _on_next, _on_error, _on_completed + ) + + var fix_subscriber = func(subscriber) -> DisposableBase: + if subscriber is DisposableBase or subscriber.has_method("dispose"): + return subscriber + return Disposable.new(subscriber) + + var set_disposable = func(__ : SchedulerBase = null, ___ = null): + var subscriber = RefValue.Null() + if not GDRx.try(func(): + subscriber.v = self._subscribe_core(auto_detach_observer, scheduler) + ) \ + .catch("Error", func(err): + if not auto_detach_observer.fail(err): + GDRx.raise(err) + ) \ + .end_try_catch(): + auto_detach_observer.subscription = fix_subscriber.call(subscriber.v) + + var current_thread_scheduler = CurrentThreadScheduler.singleton() + if current_thread_scheduler.schedule_required(): + current_thread_scheduler.schedule(set_disposable) + else: + set_disposable.call() + + return Disposable.new(func(): auto_detach_observer.dispose()) + +func _subscribe_core( + observer : ObserverBase, + _scheduler : SchedulerBase = null) -> DisposableBase: + var __ = LockGuard.new(self.lock) + self.check_disposed() + if not self.is_stopped: + self.observers.append(observer) + return InnerSubscription.new(self, observer) + + if self.error_value != null: + observer.on_error(self.error_value) + else: + observer.on_completed() + return Disposable.new() + +func on_next(i): + if true: + var __ = LockGuard.new(self.lock) + self.check_disposed() + if not self.is_stopped: + self._on_next_core(i) + +func _on_next_core(i): + var observers_ : Array[ObserverBase] + if true: + var __ = LockGuard.new(this.lock) + observers_ = self.observers.duplicate() + + for observer in observers_: + observer.on_next(i) + +func on_error(e): + if true: + var __ = LockGuard.new(self.lock) + self.check_disposed() + if not self.is_stopped: + self.is_stopped = true + self._on_error_core(e) + +func _on_error_core(e): + var observers_ : Array[ObserverBase] + if true: + var __ = LockGuard.new(this.lock) + observers_ = self.observers.duplicate() + self.observers.clear() + self.error_value = e + + for observer in observers_: + observer.on_error(e) + +func on_completed(): + if true: + var __ = LockGuard.new(self.lock) + self.check_disposed() + if not self.is_stopped: + self.is_stopped = true + self._on_completed_core() + +func _on_completed_core(): + var observers_ : Array[ObserverBase] + if true: + var __ = LockGuard.new(this.lock) + observers_ = self.observers.duplicate() + self.observers.clear() + + for observer in observers_: + observer.on_completed() + +func dispose(): + if true: + var __ = LockGuard.new(this.lock) + this.is_disposed = true + this.observers.clear() + this.error_value = null + this.is_stopped = true + +func fail(e): + if not self.is_stopped: + self.is_stopped = true + self._on_error_core(e) + return true + return false + +func throw(error : ThrowableBase): + print_stack() + GDRx.raise(error) + +func to_notifier() -> Callable: + return func(notifier : Notification): + return notifier.accept(self.as_observer()) + +func as_observer() -> ObserverBase: + return _Observer.new(self) + +func as_observable() -> Observable: + return _Observable.new(self) + +func _notification(what): + if what == NOTIFICATION_PREDELETE: + this.dispose() + +## Anonymous [ObserverBase] +class _Observer extends ObserverBase: + var _subject : Subject + func _init(subject : Subject): + self._subject = subject + ## Called when the [Observable] emits a new item on the stream + func on_next(i): + self._subject.on_next(i) + ## Called when the [Observable] emits an error on the stream + func on_error(e): + self._subject.on_error(e) + ## Called when the [Observable] is finished and no more items are sent. + func on_completed(): + self._subject.on_completed() + +## Anonymous [ObservableBase] +class _Observable extends Observable: + var _subject : Subject + func _init(subject : Subject): + self._subject = subject + func _subscribe_core( + observer : ObserverBase, scheduler : SchedulerBase = null) -> DisposableBase: + return self._subject.subscribe1(observer, scheduler) + +# ============================================================================ # +# AWAIT # +# ============================================================================ # + +## Coroutine which finishes when next item is emitted. +## [br][br] +## [code]var item = await obs.next()[/code] +func next() -> Variant: + return await ObservableAwait.new().on_next(obs) + +## Coroutine which finishes when sequence terminates with error. +## [br][br] +## [code]var err = await obs.error()[/code] +func error() -> Variant: + return await ObservableAwait.new().on_error(obs) + +## Coroutine which finishes when sequence terminates gracefully. +## [br][br] +## [code]await obs.completed()[/code] +func completed(): + return await ObservableAwait.new().on_completed(obs) + +# ============================================================================ # +# PIPE # +# ============================================================================ # + +## Pipe operator +func pipe0() -> Variant: + return GDRx.pipe.pipe(obs, GDRx.util.Iter([])) + +## Pipe operator +func pipe1(__fn1 : Callable) -> Variant: + return GDRx.pipe.pipe(obs, GDRx.util.Iter([__fn1])) + +## Pipe operator +func pipe2( + __fn1 : Callable, + __fn2 : Callable +) -> Variant: + return GDRx.pipe.pipe(obs, GDRx.util.Iter([__fn1, __fn2])) + +## Pipe operator +func pipe3( + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable +) -> Variant: + return GDRx.pipe.pipe(obs, GDRx.util.Iter([__fn1, __fn2, __fn3])) + +## Pipe operator +func pipe4( + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable +) -> Variant: + return GDRx.pipe.pipe(obs, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4])) + +## Pipe operator +func pipe5( + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable, + __fn5 : Callable +) -> Variant: + return GDRx.pipe.pipe(obs, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5])) + +## Pipe operator +func pipe6( + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable, + __fn5 : Callable, + __fn6 : Callable +) -> Variant: + return GDRx.pipe.pipe(obs, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5, __fn6])) + +## Pipe operator +func pipe7( + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable, + __fn5 : Callable, + __fn6 : Callable, + __fn7 : Callable +) -> Variant: + return GDRx.pipe.pipe(obs, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5, __fn6, __fn7])) + +## Pipe operator +func pipe8( + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable, + __fn5 : Callable, + __fn6 : Callable, + __fn7 : Callable, + __fn8 : Callable +) -> Variant: + return GDRx.pipe.pipe(obs, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5, __fn6, __fn7, __fn8])) + +## Pipe operator +func pipe9( + __fn1 : Callable, + __fn2 : Callable, + __fn3 : Callable, + __fn4 : Callable, + __fn5 : Callable, + __fn6 : Callable, + __fn7 : Callable, + __fn8 : Callable, + __fn9 : Callable +) -> Variant: + return GDRx.pipe.pipe(obs, GDRx.util.Iter([__fn1, __fn2, __fn3, __fn4, __fn5, __fn6, __fn7, __fn8, __fn9])) + +## Pipe operator taking a list +func pipea(arr : Array): + return GDRx.pipe.pipe(obs, GDRx.util.Iter(arr)) + +## Compose multiple operators left to right. +## [br] +## Composes zero or more operators into a functional composition. +## The operators are composed from left to right. A composition of zero +## operators gives back the original source. +## [br][br] +## [b]Examples:[/b] +## [codeblock] +## source.pipe0() == source +## source.pipe1(f) == f(source) +## source.pipe2(g, f) == f(g(source)) +## source.pipe3(h, g, f) == f(g(h(source))) +## [/codeblock] +## [br] +## [b]Args:[/b] +## [br] +## [code]operators[/code] Sequence of operators. +## [br][br] +## [b]Returns:[/b] +## [br] +## The composed observable. +func pipe(fns : IterableBase) -> Variant: + return GDRx.pipe.compose(fns).call(obs) + +# ============================================================================ # +# OPERATORS # +# ============================================================================ # + +## See: [b]res://addons/reactivex/operators/connectable/_refcount.gd[/b] +func ref_count() -> Observable: + return GDRx.op.ref_count().call(obs) + +## See: [b]res://addons/reactivex/operators/_all.gd[/b] +func all(predicate : Callable) -> Observable: + return GDRx.op.all(predicate).call(obs) + +## See: [b]res://addons/reactivex/operators/_amb.gd[/b] +func amb(right_source : Observable) -> Observable: + return GDRx.op.amb(right_source).call(obs) + +## See: [b]res://addons/reactivex/operators/_average.gd[/b] +func average(key_mapper = null) -> Observable: + return GDRx.op.average(key_mapper).call(obs) + +## See: [b]res://addons/reactivex/operators/_buffer.gd[/b] +func buffer(boundaries : Observable) -> Observable: + return GDRx.op.buffer(boundaries).call(obs) + +## See: [b]res://addons/reactivex/operators/_buffer.gd[/b] +func buffer_when(closing_mapper : Callable) -> Observable: + return GDRx.op.buffer_when(closing_mapper).call(obs) + +## See: [b]res://addons/reactivex/operators/_buffer.gd[/b] +func buffer_toggle(openings : Observable, closing_mapper : Callable) -> Observable: + return GDRx.op.buffer_toggle(openings, closing_mapper).call(obs) + +## See: [b]res://addons/reactivex/operators/_buffer.gd[/b] +func buffer_with_count(count_ : int, skip_ = null) -> Observable: + return GDRx.op.buffer_with_count(count_, skip_).call(obs) + +## See: [b]res://addons/reactivex/operators/_bufferwithtime.gd[/b] +func buffer_with_time(timespan : float, timeshift = null, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.buffer_with_time(timespan, timeshift, scheduler).call(obs) + +## See: [b]res://addons/reactivex/operators/_bufferwithtimeourcount.gd[/b] +func buffer_with_time_or_count(timespan : float, count_ : int, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.buffer_with_time_or_count(timespan, count_, scheduler).call(obs) + +## See: [b]res://addons/reactivex/operators/_catch.gd[/b] +func catch_handler(handler : Callable) -> Observable: + return GDRx.op.catch_handler(obs, handler) + +## See: [b]res://addons/reactivex/operators/_catch.gd[/b] +func catch(handler) -> Observable: + return GDRx.op.catch(handler).call(obs) + +## See: [b]res://addons/reactivex/operators/_combinelatest.gd[/b] +func combine_latest(others) -> Observable: + return GDRx.op.combine_latest(others).call(obs) + +## See: [b]res://addons/reactivex/operators/_concat.gd[/b] +func concat(sources) -> Observable: + return GDRx.op.concat(sources).call(obs) + +## See: [b]res://addons/reactivex/operators/_contains.gd[/b] +func contains(value, comparer = GDRx.basic.default_comparer) -> Observable: + return GDRx.op.contains(value, comparer).call(obs) + +## See: [b]res://addons/reactivex/operators/_count.gd[/b] +func count(predicate = null) -> Observable: + return GDRx.op.count(predicate).call(obs) + +## See: [b]res://addons/reactivex/operators/_debounce.gd[/b] +func debounce(duetime : float, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.debounce(duetime, scheduler).call(obs) + +## See: [b]res://addons/reactivex/operators/_debounce.gd[/b] +func throttle_with_mapper(throttle_duration_mapper : Callable) -> Observable: + return GDRx.op.throttle_with_mapper(throttle_duration_mapper).call(obs) + +## See: [b]res://addons/reactivex/operators/_defaultifempty.gd[/b] +func default_if_empty(default_value = null) -> Observable: + return GDRx.op.default_if_empty(default_value).call(obs) + +## See: [b]res://addons/reactivex/operators/_delay.gd[/b] +func observable_delay_timespan(duetime : float, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.observable_delay_timespan(obs, duetime, scheduler) + +## See: [b]res://addons/reactivex/operators/_delay.gd[/b] +func delay(duetime : float, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.delay(duetime, scheduler).call(obs) + +## See: [b]res://addons/reactivex/operators/_delaysubscription.gd[/b] +func delay_subscription(duetime : float, time_absolute : bool = false, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.delay_subscription(duetime, time_absolute, scheduler).call(obs) + +## See: [b]res://addons/reactivex/operators/_delaywithmapper.gd[/b] +func delay_with_mapper(subscription_delay = null, delay_duration_mapper = null) -> Observable: + return GDRx.op.delay_with_mapper(subscription_delay, delay_duration_mapper).call(obs) + +## See: [b]res://addons/reactivex/operators/_dematerialize.gd[/b] +func dematerialize() -> Observable: + return GDRx.op.dematerialize().call(obs) + +## See: [b]res://addons/reactivex/operators/_distinct.gd[/b] +func distinct(key_mapper : Callable = GDRx.basic.identity, comparer : Callable = GDRx.basic.default_comparer) -> Observable: + return GDRx.op.distinct(key_mapper, comparer).call(obs) + +## See: [b]res://addons/reactivex/operators/_distinctuntilchanged.gd[/b] +func distinct_until_changed(key_mapper : Callable = GDRx.basic.identity, comparer : Callable = GDRx.basic.default_comparer) -> Observable: + return GDRx.op.distinct_until_changed(key_mapper, comparer).call(obs) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do_action(on_next = null, on_error = null, on_completed = null) -> Observable: + return GDRx.op.do_action(on_next, on_error, on_completed).call(obs) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do(observer : ObserverBase) -> Observable: + return GDRx.op.do(observer).call(obs) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do_after_next(after_next : Callable) -> Observable: + return GDRx.op.do_after_next(obs, after_next) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do_on_subscribe(on_subscribe : Callable) -> Observable: + return GDRx.op.do_on_subscribe(obs, on_subscribe) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do_on_dispose(on_dispose : Callable) -> Observable: + return GDRx.op.do_on_dispose(obs, on_dispose) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do_on_terminate(on_terminate : Callable) -> Observable: + return GDRx.op.do_on_terminate(obs, on_terminate) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do_after_terminate(after_terminate : Callable) -> Observable: + return GDRx.op.do_after_terminate(obs, after_terminate) + +## See: [b]res://addons/reactivex/operators/_do.gd[/b] +func do_finally(finally_action_ : Callable) -> Observable: + return GDRx.op.do_finally(finally_action_).call(obs) + +## See: [b]res://addons/reactivex/operators/_dowhile.gd[/b] +func do_while(condition : Callable) -> Observable: + return GDRx.op.do_while(condition).call(obs) + +## See: [b]res://addons/reactivex/operators/_elementordefault.gd[/b] +func element_at_or_default(index : int, has_default : bool = false, default_value = GDRx.util.GetNotSet()) -> Observable: + return GDRx.op.element_at_or_default(index, has_default, default_value).call(obs) + +## See: [b]res://addons/reactivex/operators/_exclusive.gd[/b] +func exclusive() -> Observable: + return GDRx.op.exclusive().call(obs) + +## See: [b]res://addons/reactivex/operators/_expand.gd[/b] +func expand(mapper : Callable) -> Observable: + return GDRx.op.expand(mapper).call(obs) + +## See: [b]res://addons/reactivex/operators/_filter.gd[/b] +func filter(predicate : Callable = GDRx.basic.default_condition) -> Observable: + return GDRx.op.filter(predicate).call(obs) + +## See: [b]res://addons/reactivex/operators/_filter.gd[/b] +func filter_indexed(predicate : Callable = GDRx.basic.default_condition) -> Observable: + return GDRx.op.filter_indexed(predicate).call(obs) + +## See: [b]res://addons/reactivex/operators/_finallyaction.gd[/b] +func finally_action(action : Callable) -> Observable: + return GDRx.op.finally_action(action).call(obs) + +## See: [b]res://addons/reactivex/operators/_find.gd[/b] +func find_value(predicate : Callable, yield_index : bool) -> Observable: + return GDRx.op.find_value(predicate, yield_index).call(obs) + +## See: [b]res://addons/reactivex/operators/_first.gd[/b] +func first(predicate = null) -> Observable: + return GDRx.op.first(predicate).call(obs) + +## See: [b]res://addons/reactivex/operators/_firstordefault.gd[/b] +func first_or_default_async(has_default : bool = false, default_value = null) -> Observable: + return GDRx.op.first_or_default_async(has_default, default_value).call(obs) + +## See: [b]res://addons/reactivex/operators/_firstordefault.gd[/b] +func first_or_default(predicate = null, default_value = null) -> Observable: + return GDRx.op.first_or_default(predicate, default_value).call(obs) + +## See: [b]res://addons/reactivex/operators/_flatmap.gd[/b] +func flat_map(mapper = null) -> Observable: + return GDRx.op.flat_map(mapper).call(obs) + +## See: [b]res://addons/reactivex/operators/_flatmap.gd[/b] +func flat_map_indexed(mapper_indexed = null) -> Observable: + return GDRx.op.flat_map_indexed(mapper_indexed).call(obs) + +## See: [b]res://addons/reactivex/operators/_flatmap.gd[/b] +func flat_map_latest(mapper = null) -> Observable: + return GDRx.op.flat_map_latest(mapper).call(obs) + +## See: [b]res://addons/reactivex/operators/_forkjoin.gd[/b] +func fork_join(args) -> Observable: + return GDRx.op.fork_join(args).call(obs) + +## See: [b]res://addons/reactivex/operators/_groupby.gd[/b] +func group_by(key_mapper : Callable, element_mapper = null, subject_mapper = null) -> Observable: + return GDRx.op.group_by(key_mapper, element_mapper, subject_mapper).call(obs) + +## See: [b]res://addons/reactivex/operators/_groupbyuntil.gd[/b] +func group_by_until(key_mapper : Callable, duration_mapper : Callable, element_mapper = null, subject_mapper = null) -> Observable: + return GDRx.op.group_by_until(key_mapper, duration_mapper, element_mapper, subject_mapper).call(obs) + +## See: [b]res://addons/reactivex/operators/_groupjoin.gd[/b] +func group_join(right : Observable, left_duration_mapper : Callable, right_duration_mapper : Callable) -> Observable: + return GDRx.op.group_join(right, left_duration_mapper, right_duration_mapper).call(obs) + +## See: [b]res://addons/reactivex/operators/_ignoreelements.gd[/b] +func ignore_elements() -> Observable: + return GDRx.op.ignore_elements().call(obs) + +## See: [b]res://addons/reactivex/operators/_isempty.gd[/b] +func is_empty() -> Observable: + return GDRx.op.is_empty().call(obs) + +## See: [b]res://addons/reactivex/operators/_join.gd[/b] +func join(right : Observable, left_duration_mapper : Callable, right_duration_mapper : Callable) -> Observable: + return GDRx.op.join(right, left_duration_mapper, right_duration_mapper).call(obs) + +## See: [b]res://addons/reactivex/operators/_last.gd[/b] +func last(predicate = null) -> Observable: + return GDRx.op.last(predicate).call(obs) + +## See: [b]res://addons/reactivex/operators/_lastordefault.gd[/b] +func last_or_default_async(has_default : bool = false, default_value = null) -> Observable: + return GDRx.op.last_or_default_async(obs, has_default, default_value) + +## See: [b]res://addons/reactivex/operators/_lastordefault.gd[/b] +func last_or_default(default_value = null, predicate = null) -> Observable: + return GDRx.op.last_or_default(default_value, predicate).call(obs) + +## See: [b]res://addons/reactivex/operators/_map.gd[/b] +func map(mapper : Callable = GDRx.basic.identity) -> Observable: + return GDRx.op.map(mapper).call(obs) + +## See: [b]res://addons/reactivex/operators/_map.gd[/b] +func map_indexed(mapper_indexed : Callable = GDRx.basic.identity) -> Observable: + return GDRx.op.map_indexed(mapper_indexed).call(obs) + +## See: [b]res://addons/reactivex/operators/_materialize.gd[/b] +func materialize() -> Observable: + return GDRx.op.materialize().call(obs) + +## See: [b]res://addons/reactivex/operators/_max.gd[/b] +@warning_ignore("shadowed_global_identifier") +func max(comparer = null) -> Observable: + return GDRx.op.max(comparer).call(obs) + +## See: [b]res://addons/reactivex/operators/_maxby.gd[/b] +func max_by(key_mapper : Callable, comparer = null) -> Observable: + return GDRx.op.max_by(key_mapper, comparer).call(obs) + +## See: [b]res://addons/reactivex/operators/_merge.gd[/b] +func merge(sources, max_concorrent : int = -1) -> Observable: + return GDRx.op.merge(sources, max_concorrent).call(obs) + +## See: [b]res://addons/reactivex/operators/_merge.gd[/b] +func merge_all() -> Observable: + return GDRx.op.merge_all().call(obs) + +## See: [b]res://addons/reactivex/operators/_min.gd[/b] +@warning_ignore("shadowed_global_identifier") +func min(comparer = null) -> Observable: + return GDRx.op.min(comparer).call(obs) + +## See: [b]res://addons/reactivex/operators/_minby.gd[/b] +func extrema_by(key_mapper : Callable, comparer : Callable) -> Observable: + return GDRx.op.extrema_by(obs, key_mapper, comparer) + +## See: [b]res://addons/reactivex/operators/_minby.gd[/b] +func min_by(key_mapper : Callable, comparer = null) -> Observable: + return GDRx.op.min_by(key_mapper, comparer).call(obs) + +## See: [b]res://addons/reactivex/operators/_multicast.gd[/b] +func multicast(subject : SubjectBase = null, subject_factory = null, mapper = null) -> Observable: + return GDRx.op.multicast(subject, subject_factory, mapper).call(obs) + +## See: [b]res://addons/reactivex/operators/_observeon.gd[/b] +func observe_on(scheduler : SchedulerBase) -> Observable: + return GDRx.op.observe_on(scheduler).call(obs) + +## See: [b]res://addons/reactivex/operators/_oftype.gd[/b] +func oftype(type, push_err : bool = true, type_equality : Callable = GDRx.basic.default_type_equality) -> Observable: + return GDRx.op.oftype(type, push_err, type_equality).call(obs) + +## See: [b]res://addons/reactivex/operators/_onerrorresumenext.gd[/b] +func on_error_resume_next(second : Observable) -> Observable: + return GDRx.op.on_error_resume_next(second).call(obs) + +## See: [b]res://addons/reactivex/operators/_pairwise.gd[/b] +func pairwise() -> Observable: + return GDRx.op.pairwise().call(obs) + +## See: [b]res://addons/reactivex/operators/_partiton.gd[/b] +func partition(predicate : Callable = GDRx.basic.default_condition) -> Array[Observable]: + return GDRx.op.partition(predicate).call(obs) + +## Alternative to [method partition] but returning type an [IterableBase] containing. +## the partitioned [Observable]s. +func partitionit(predicate : Callable = GDRx.basic.default_condition) -> IterableBase: + return Iterator.to_iterable(self.partition(predicate)) + +## See: [b]res://addons/reactivex/operators/_partition.gd[/b] +func partition_indexed(predicate_indexed : Callable = GDRx.basic.default_condition) -> Observable: + return GDRx.op.partition_indexed(predicate_indexed).call(obs) + +## See: [b]res://addons/reactivex/operators/_pluck.gd[/b] +func pluck(key) -> Observable: + return GDRx.op.pluck(key).call(obs) + +## See: [b]res://addons/reactivex/operators/_pluck.gd[/b] +func pluck_attr(prop : String) -> Observable: + return GDRx.op.pluck_attr(prop).call(obs) + +## See: [b]res://addons/reactivex/operators/_publish.gd[/b] +func publish(mapper = null) -> Observable: + return GDRx.op.publish(mapper).call(obs) + +## See: [b]res://addons/reactivex/operators/_publish.gd[/b] +func share() -> Observable: + return GDRx.op.share().call(obs) + +## See: [b]res://addons/reactivex/operators/_publishvalue.gd[/b] +func publish_value(initial_value, mapper = null) -> Observable: + return GDRx.op.publish_value(initial_value, mapper).call(obs) + +## See: [b]res://addons/reactivex/operators/_reduce.gd[/b] +func reduce(accumulator : Callable, seed_ = GDRx.util.GetNotSet()) -> Observable: + return GDRx.op.reduce(accumulator, seed_).call(obs) + +## See: [b]res://addons/reactivex/operators/_repeat.gd[/b] +func repeat(repeat_count = null) -> Observable: + return GDRx.op.repeat(repeat_count).call(obs) + +## See: [b]res://addons/reactivex/operators/_replay.gd[/b] +func replay(mapper = null, buffer_size = null, window_ = null, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.replay(mapper, buffer_size, window_, scheduler).call(obs) + +## See: [b]res://addons/reactivex/operators/_retry.gd[/b] +func retry(retry_count : int = -1) -> Observable: + return GDRx.op.retry(retry_count).call(obs) + +## See: [b]res://addons/reactivex/operators/_sample.gd[/b] +func sample_observable(sampler : Observable) -> Observable: + return GDRx.op.sample_observable(obs, sampler) + +## See: [b]res://addons/reactivex/operators/_sample.gd[/b] +func sample(sampler : Observable, sampler_time : float = NAN, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.sample(sampler, sampler_time, scheduler).call(obs) + +## See: [b]res://addons/reactivex/operators/_scan.gd[/b] +func scan(accumulator : Callable, seed_ = GDRx.util.GetNotSet()) -> Observable: + return GDRx.op.scan(accumulator, seed_).call(obs) + +## See: [b]res://addons/reactivex/operators/_sequenceequal.gd[/b] +func sequence_equal(second, comparer = null, second_it : IterableBase = null) -> Observable: + return GDRx.op.sequence_equal(second, comparer, second_it).call(obs) + +## See: [b]res://addons/reactivex/operators/_single.gd[/b] +func single(predicate = null) -> Observable: + return GDRx.op.single(predicate).call(obs) + +## See: [b]res://addons/reactivex/operators/_singleordefault.gd[/b] +func single_or_default_async(has_default : bool = false, default_value = null) -> Observable: + return GDRx.op.single_or_default_async(has_default, default_value).call(obs) + +## See: [b]res://addons/reactivex/operators/_singleordefault.gd[/b] +func single_or_default(predicate = null, default_value = null) -> Observable: + return GDRx.op.single_or_default(predicate, default_value).call(obs) + +## See: [b]res://addons/reactivex/operators/_skip.gd[/b] +func skip(count_ : int) -> Observable: + return GDRx.op.skip(count_).call(obs) + +## See: [b]res://addons/reactivex/operators/_skiplast.gd[/b] +func skip_last(count_ : int) -> Observable: + return GDRx.op.skip_last(count_).call(obs) + +## See: [b]res://addons/reactivex/operators/_skiplastwithtime.gd[/b] +func skip_last_with_time(duration : float, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.skip_last_with_time(duration, scheduler).call(obs) + +## See: [b]res://addons/reactivex/operators/_skipuntil.gd[/b] +func skip_until(other : Observable) -> Observable: + return GDRx.op.skip_until(other).call(obs) + +## See: [b]res://addons/reactivex/operators/_skipuntilwithtime.gd[/b] +func skip_until_with_time(start_time : float, time_absolute : bool = false, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.skip_until_with_time(start_time, time_absolute, scheduler).call(obs) + +## See: [b]res://addons/reactivex/operators/_skipwhile.gd[/b] +func skip_while(predicate : Callable) -> Observable: + return GDRx.op.skip_while(predicate).call(obs) + +## See: [b]res://addons/reactivex/operators/_skipwhile.gd[/b] +func skip_while_indexed(predicate : Callable) -> Observable: + return GDRx.op.skip_while_indexed(predicate).call(obs) + +## See: [b]res://addons/reactivex/operators/_skipwithtime.gd[/b] +func skip_with_time(duration : float, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.skip_with_time(duration, scheduler).call(obs) + +## See: [b]res://addons/reactivex/operators/_slice.gd[/b] +func slice(start : int = 0, stop : int = GDRx.util.MAX_SIZE, step : int = 1) -> Observable: + return GDRx.op.slice(start, stop, step).call(obs) + +## See: [b]res://addons/reactivex/operators/_some.gd[/b] +func some(predicate = null) -> Observable: + return GDRx.op.some(predicate).call(obs) + +## See: [b]res://addons/reactivex/operators/_startswith.gd[/b] +func start_with(args) -> Observable: + return GDRx.op.start_with(args).call(obs) + +## See: [b]res://addons/reactivex/operators/_subscribeon.gd[/b] +func subscribe_on(scheduler : SchedulerBase) -> Observable: + return GDRx.op.subscribe_on(scheduler).call(obs) + +## See: [b]res://addons/reactivex/operators/_sum.gd[/b] +func sum(key_mapper = null) -> Observable: + return GDRx.op.sum(key_mapper).call(obs) + +## See: [b]res://addons/reactivex/operators/_switchlatest.gd[/b] +func switch_latest() -> Observable: + return GDRx.op.switch_latest().call(obs) + +## See: [b]res://addons/reactivex/operators/_take.gd[/b] +func take(count_ : int) -> Observable: + return GDRx.op.take(count_).call(obs) + +## See: [b]res://addons/reactivex/operators/_takelast.gd[/b] +func take_last(count_ : int) -> Observable: + return GDRx.op.take_last(count_).call(obs) + +## See: [b]res://addons/reactivex/operators/_takelastbuffer.gd[/b] +func take_last_buffer(count_ : int) -> Observable: + return GDRx.op.take_last_buffer(count_).call(obs) + +## See: [b]res://addons/reactivex/operators/_takelastwithtime.gd[/b] +func take_last_with_time(duration : float, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.take_last_with_time(duration, scheduler).call(obs) + +## See: [b]res://addons/reactivex/operators/_takeuntil.gd[/b] +func take_until(other : Observable) -> Observable: + return GDRx.op.take_until(other).call(obs) + +## See: [b]res://addons/reactivex/operators/_takeuntilwithtime.gd[/b] +func take_until_with_time(end_time : float, absolute : bool = false, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.take_until_with_time(end_time, absolute, scheduler).call(obs) + +## See: [b]res://addons/reactivex/operators/_takewhile.gd[/b] +func take_while(predicate : Callable = GDRx.basic.default_condition, inclusive : bool = false) -> Observable: + return GDRx.op.take_while(predicate, inclusive).call(obs) + +## See: [b]res://addons/reactivex/operators/_takewhile.gd[/b] +func take_while_indexed(predicate : Callable = GDRx.basic.default_condition, inclusive : bool = false) -> Observable: + return GDRx.op.take_while_indexed(predicate, inclusive).call(obs) + +## See: [b]res://addons/reactivex/operators/_takewithtime.gd[/b] +func take_with_time(duration : float, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.take_with_time(duration, scheduler).call(obs) + +## See: [b]res://addons/reactivex/operators/_throttlefirst.gd[/b] +func throttle_first(window_duration : float, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.throttle_first(window_duration, scheduler).call(obs) + +## See: [b]res://addons/reactivex/operators/_timeinterval.gd[/b] +func time_interval(scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.time_interval(scheduler).call(obs) + +## See: [b]res://addons/reactivex/operators/_timeout.gd[/b] +func timeout(duetime : float, absolute : bool = false, other : Observable = null, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.timeout(duetime, absolute, other, scheduler).call(obs) + +## See: [b]res://addons/reactivex/operators/_timeoutwithmapper.gd[/b] +func timeout_with_mapper(first_timeout : Observable = null, timeout_duration_mapper : Callable = func(__) -> Observable: return GDRx.obs.never(), other : Observable = null) -> Observable: + return GDRx.op.timeout_with_mapper(first_timeout, timeout_duration_mapper, other).call(obs) + +## See: [b]res://addons/reactivex/operators/_timestamp.gd[/b] +func timestamp(scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.timestamp(scheduler).call(obs) + +## See: [b]res://addons/reactivex/operators/_todict.gd[/b] +func to_dict(key_mapper : Callable, element_mapper : Callable = GDRx.basic.identity) -> Observable: + return GDRx.op.to_dict(key_mapper, element_mapper).call(obs) + +## See: [b]res://addons/reactivex/operators/_toiterable.gd[/b] +func to_iterable() -> Observable: + return GDRx.op.to_iterable().call(obs) + +## See: [b]res://addons/reactivex/operators/_tolist.gd[/b] +func to_list() -> Observable: + return GDRx.op.to_list().call(obs) + +## See: [b]res://addons/reactivex/operators/_tolist.gd[/b] +func to_array() -> Observable: + return GDRx.op.to_list().call(obs) + +## See: [b]res://addons/reactivex/operators/_toset.gd[/b] +func to_set() -> Observable: + return GDRx.op.to_set().call(obs) + +## See: [b]res://addons/reactivex/operators/_whiledo.gd[/b] +func while_do(condition : Callable = GDRx.basic.default_condition) -> Observable: + return GDRx.op.while_do(condition).call(obs) + +## See: [b]res://addons/reactivex/operators/_window.gd[/b] +func window_toggle(openings : Observable, closing_mapper : Callable) -> Observable: + return GDRx.op.window_toggle(openings, closing_mapper).call(obs) + +## See: [b]res://addons/reactivex/operators/_window.gd[/b] +func window_with_bounds(boundaries : Observable) -> Observable: + return GDRx.op.window(boundaries).call(obs) + +## See: [b]res://addons/reactivex/operators/_window.gd[/b] +func window_when(closing_mapper : Callable) -> Observable: + return GDRx.op.window_when(closing_mapper).call(obs) + +## See: [b]res://addons/reactivex/operators/_windowwithcount.gd[/b] +func window_with_count(count_ : int, skip_ = null) -> Observable: + return GDRx.op.window_with_count(count_, skip_).call(obs) + +## See: [b]res://addons/reactivex/operators/_windowwithtime.gd[/b] +func window_with_time(timespan : float, timeshift = null, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.window_with_time(timespan, timeshift, scheduler).call(obs) + +## See: [b]res://addons/reactivex/operators/_windowwithtimeorcount.gd[/b] +func window_with_time_or_count(timespan : float, count_ : int, scheduler : SchedulerBase = null) -> Observable: + return GDRx.op.window_with_time_or_count(timespan, count_, scheduler).call(obs) + +## See: [b]res://addons/reactivex/operators/_withlatestfrom.gd[/b] +func with_latest_from(sources) -> Observable: + return GDRx.op.with_latest_from(sources).call(obs) + +## See: [b]res://addons/reactivex/operators/_zip.gd[/b] +func zip(args) -> Observable: + return GDRx.op.zip(args).call(obs) + +## See: [b]res://addons/reactivex/operators/_zip.gd[/b] +func zip_with_iterable(seq : IterableBase) -> Observable: + return GDRx.op.zip_with_iterable(seq).call(obs) + +# =========================================================================== # +# Godot-specific Operators +# =========================================================================== # + +## See: [b]"res://addons/reactivex/engine/operators/_processtimeinterval.gd"[/b] +func process_time_interval(initial_time : float = 0.0) -> Observable: + return GDRx.gd.process_time_interval(initial_time).call(obs) + +## See: [b]"res://addons/reactivex/engine/operators/_processtimeinterval.gd"[/b] +func physics_time_interval(initial_time : float = 0.0) -> Observable: + return GDRx.gd.physics_time_interval(initial_time).call(obs) + +## Create a [ReadOnlyReactiveProperty] from the given sequence +func to_reactive_property(initial_value, distinct_until_changed : bool = true, raise_latest_value_on_subscribe = true) -> ReadOnlyReactiveProperty: + return ReadOnlyReactiveProperty.new(obs, initial_value, distinct_until_changed, raise_latest_value_on_subscribe) diff --git a/addons/reactivex/testing/basetest.gd b/addons/reactivex/testing/basetest.gd new file mode 100644 index 0000000..392cdd5 --- /dev/null +++ b/addons/reactivex/testing/basetest.gd @@ -0,0 +1,103 @@ +extends RefCounted +class_name __GDRx_Test__ + +var test_results : Array = [] + +func _init(): + test_results = [] + +func run_tests(): + var test_unit_name = self.get("TEST_UNIT_NAME") if self.get("TEST_UNIT_NAME") \ + != null else "<>" + print("[ReactiveX]: ================================") + print("[ReactiveX]: Running tests for: ", test_unit_name) + print("[ReactiveX]: --------------------------------") + var method_list = get_method_list() + for method in method_list: + if method.name.begins_with("test_"): + print("[ReactiveX]: Running ", method.name) + var result = await call(method.name) + test_results.append({method.name: result}) + self._print_results() + print("[ReactiveX]: ================================") + print("[ReactiveX]:") + +func _print_results(): + print("[ReactiveX]: --------------------------------") + var success = 0 + var failed = 0 + for result in test_results: + for test_name in result.keys(): + if result[test_name]: + failed += 1 + print("[ReactiveX]: ", test_name, ": FAIL") + else: + success += 1 + print("[ReactiveX]: ", test_name, ": PASS") + print("[ReactiveX]: --------------------------------") + print("[ReactiveX]: ", success, " PASS, ", failed, " FAIL - (", success, " / ", success + failed, ")") + +class _Error extends Comparable: + var type : String + func _init(type_ : String): + self.type = type_ + + func eq(other) -> bool: + return other is _Error and other.type == self.type + +class _Completed extends Comparable: + func eq(other) -> bool: + return other is _Completed + +static func Err(type : String = "RxBaseError") -> _Error: + return _Error.new(type) + +static func Comp() -> _Completed: + return _Completed.new() + +static func to_history() -> Callable: + var to_history_ = func(source : Observable) -> Observable: + var subscribe = func( + observer : ObserverBase, + scheduler : SchedulerBase = null + ) -> DisposableBase: + + var result = [] + + var on_next = func(i): + result.append(i) + var on_error = func(e): + result.append(_Error.new(e.tags().back())) + observer.on_next(result) + observer.on_completed() + var on_completed = func(): + result.append(_Completed.new()) + observer.on_next(result) + observer.on_completed() + + return source.subscribe(on_next, on_error, on_completed, scheduler) + + return Observable.new(subscribe) + + return to_history_ + +static func compare(obs : Observable, seq : Array) -> bool: + var history : Observable = to_history().call(obs) + var result : Array = await history.next() + return _compare_sequence(seq, result) + +static func _compare_sequence(seq1 : Array, seq2 : Array) -> bool: + if seq1.size() != seq2.size(): + return true + + for i in range(seq1.size()): + if seq1[i] is Array and seq2[i] is Array: + if _compare_sequence(seq1[i], seq2[i]): + return true + if seq1[i] is Tuple and seq2[i] is Tuple: + if _compare_sequence(seq1[i].as_list(), seq2[i].as_list()): + return true + if not GDRx.eq(seq1[i], seq2[i]): + return true + + return false diff --git a/addons/reactivex/testing/testrunner.gd b/addons/reactivex/testing/testrunner.gd new file mode 100644 index 0000000..c0702af --- /dev/null +++ b/addons/reactivex/testing/testrunner.gd @@ -0,0 +1,26 @@ +extends Node +class_name __GDRx_TestRunner__ + +@export var tests_directory : String = "res://addons/reactivex/testing/tests" + +func _ready(): + await self._run_tests_in_directory(tests_directory) + print("[ReactiveX]: All tests completed.") + get_tree().quit() + +func _run_tests_in_directory(directory: String): + var dir_access = DirAccess.open(directory) + if dir_access: + dir_access.list_dir_begin() + var file_name = dir_access.get_next() + while file_name != "": + if file_name != "." and file_name != "..": + var file_path = directory + "/" + file_name + if dir_access.current_is_dir(): + self._run_tests_in_directory(file_path) + elif file_name.ends_with(".test.gd"): + var test_script = load(file_path) + var test_instance = test_script.new() + await test_instance.run_tests() + file_name = dir_access.get_next() + dir_access.list_dir_end() diff --git a/addons/reactivex/testing/tests/basics.test.gd b/addons/reactivex/testing/tests/basics.test.gd new file mode 100644 index 0000000..d6e6b33 --- /dev/null +++ b/addons/reactivex/testing/tests/basics.test.gd @@ -0,0 +1,32 @@ +extends __GDRx_Test__ + +const TEST_UNIT_NAME = "BASICS" + +func test_rx_map() -> bool: + var observable = GDRx.of([1, 2, 3, 4]) + var mapped_observable = observable.map(func(x): return x * 2) + var result = [2, 4, 6, 8, Comp()] + return await compare(mapped_observable, result) + +func test_rx_filter() -> bool: + var observable = GDRx.of([1, 2, 3, 4]) + var filtered_observable = observable.filter(func(x): return x % 2 == 0) + var result = [2, 4, Comp()] + return await compare(filtered_observable, result) + +func test_rx_zip() -> bool: + var obs1 = GDRx.of([1, 2, 3]) + var obs2 = GDRx.of([4, 5]) + var obs3 = GDRx.of([6, 7, 8, 9]) + var zipped = GDRx.zip([obs1, obs2, obs3]).map(func(x : Tuple): return x.as_list()) + var result = [[1, 4, 6], [2, 5, 7], Comp()] + return await compare(zipped, result) + +func test_rx_safe_divison() -> bool: + var safe_division = func(a, b): + return a / b if b != 0 else DividedByZeroError.raise(-1) + var mapped = GDRx.of([6, 2, 1, 0, 2, 1]) \ + .pairwise() \ + .map(func(tup : Tuple): return safe_division.call(tup.first, tup.second)) + var result = [3, 2, Err("DividedByZeroError")] + return await compare(mapped, result) diff --git a/addons/reactivex/testing/tests/op.test.gd b/addons/reactivex/testing/tests/op.test.gd new file mode 100644 index 0000000..160fd81 --- /dev/null +++ b/addons/reactivex/testing/tests/op.test.gd @@ -0,0 +1,148 @@ +extends __GDRx_Test__ + +const TEST_UNIT_NAME = "OPS" + +func test_op_all() -> bool: + var obs = GDRx.from([1, 2, 3, 4]).all(func(i): return i <= 4) + var result = [true, Comp()] + return await compare(obs, result) + +func test_op_amb() -> bool: + var els = EventLoopScheduler.new() + var t1 = GDRx.start_periodic_timer(0.25).take(3) + var t2 = GDRx.start_periodic_timer(0.1).take(2) + var obs = t1.amb(t2) + var result = [0, 1, Comp()] + return await compare(obs, result) + +func test_op_average() -> bool: + var obs = GDRx.from([1.0, 2.0, 3.0, 4.0]).average() + var result = [2.5, Comp()] + return await compare(obs, result) + +#func test_op_buffer() -> bool: + #var t1 = GDRx.start_periodic_timer(0.1).take(8) + #var t2 = GDRx.start_periodic_timer(0.45) + #var obs = t1.buffer(t2) + #var result = [[0, 1, 2, 3], [4, 5, 6, 7], Comp()] + #return await compare(obs, result) + +func test_op_buffer_with_count() -> bool: + var obs = GDRx.from([1, 2, 3, 4, 5, 6, 7, 8]).buffer_with_count(3) + var result = [[1, 2, 3], [4, 5, 6], [7, 8], Comp()] + return await compare(obs, result) + +#func test_op_buffer_with_time() -> bool: + #var obs = GDRx.start_periodic_timer(0.1).take(8).buffer_with_time(0.45) + #var result = [[0, 1, 2, 3], [4, 5, 6, 7], Comp()] + #return await compare(obs, result) + +func test_op_catch() -> bool: + var obs = GDRx.from([1, 2, 3, 4, "5", 6, "7", 8]) \ + .filter(func(i): return i if i is int else BadArgumentError.raise(-1)) \ + .catch(func(err, source): return GDRx.just(42)) + var result = [1, 2, 3, 4, 42, Comp()] + return await compare(obs, result) + +func test_op_combine_latest() -> bool: + var obs1 = GDRx.from([0, 1, 2, 3]) + var obs2 = GDRx.from([0, 1, 2, 3, 4]) + var t1 = GDRx.start_periodic_timer(0.05).take(3) + var obs = obs1.combine_latest([t1, obs2]) + var result = [ + Tuple.new([3, 0, 4]), + Tuple.new([3, 1, 4]), + Tuple.new([3, 2, 4]), + Comp() + ] + return await compare(obs, result) + +func test_op_concat() -> bool: + var obs1 = GDRx.from(["a", "b", "c"]) + var t1 = GDRx.start_periodic_timer(0.05).take(3) + var obs2 = GDRx.just(42) + var obs = obs1.concat([t1, obs2]) + var result = ["a", "b", "c", 0, 1, 2, 42, Comp()] + return await compare(obs, result) + +func test_op_contains() -> bool: + var seq = GDRx.from(["aaa", "aab", "bb", "baa"]) + var obs1 = seq.contains("bb") + var obs2 = seq.contains("bbb") + return await compare(obs1, [true, Comp()]) and await compare(obs2, [false, Comp()]) + +func test_op_count() -> bool: + var obs = GDRx.from(["aaa", "aab", "bb", "baa"]).count() + var result = [4, Comp()] + return await compare(obs, result) + +func test_op_debounce() -> bool: + var obs = GDRx.merge([ + GDRx.from([1, 2, 3, 4]), + GDRx.start_timer(0.1)]) \ + .debounce(0.05) + var result = [4, 0, Comp()] + return await compare(obs, result) + +func test_op_default_if_empty() -> bool: + var seq = GDRx.from([1, 2, 3, 4]) + var obs1 = seq.filter(func(i): return i > 42).default_if_empty(-1) + var obs2 = seq.filter(func(i): return i < 42).default_if_empty(-1) + return await compare(obs1, [-1, Comp()]) or await compare(obs2, [1, 2, 3, 4, Comp()]) + +func test_op_delay() -> bool: + var obs = GDRx.merge([ + GDRx.from([1, 2, 3]).delay(0.2), + GDRx.start_timer(0.1)]) + var result = [0, 1, 2, 3, Comp()] + return await compare(obs, result) + +func test_op_delay_subscription() -> bool: + var obs = GDRx.merge([ + GDRx.from([1, 2, 3]).delay_subscription(0.2), + GDRx.start_timer(0.1)]) + var result = [0, 1, 2, 3, Comp()] + return await compare(obs, result) + +func test_op_delay_with_mapper() -> bool: + var obs = GDRx.merge([ + GDRx.from([1, 2, 3]).delay_with_mapper(func(x): return GDRx.just(x).delay(0.1) if x > 0 else GDRx.just(x)), + GDRx.start_timer(0.05)]) + var result = [0, 1, 2, 3, Comp()] + return await compare(obs, result) + +func test_op_dematerialize() -> bool: + var obs = GDRx.from([ + OnNextNotification.new(42), + OnNextNotification.new("Foo"), + OnErrorNotification.new(BadArgumentError.new())]) \ + .dematerialize() + var result = [42, "Foo", Err("BadArgumentError")] + return await compare(obs, result) + +func test_op_distinct() -> bool: + var obs = GDRx.from(["aa", "ab", "aa", "aba", "bb", "aba", "abb"]).distinct() + var result = ["aa", "ab", "aba", "bb", "abb", Comp()] + return await compare(obs, result) + +func test_op_distinct_until_changed() -> bool: + var obs = GDRx.from(["a", "a", "b", "a", "b", "b", "c"]).distinct_until_changed() + var result = ["a", "b", "a", "b", "c", Comp()] + return await compare(obs, result) + +func test_op_do(): + var flag_on_next = RefValue.Set(true) + var flag_on_complete = RefValue.Set(true) + var flag_after_next = RefValue.Set(true) + var flag_on_subscribe = RefValue.Set(true) + var flag_on_terminate = RefValue.Set(true) + var flag_after_terminate = RefValue.Set(true) + var flag_finally = RefValue.Set(true) + var obs = GDRx.just(42) \ + .do_action(func(__): flag_on_next.v = false, null, func(): flag_on_complete.v = false) \ + .do_after_next(func(__): flag_after_next.v = false) \ + .do_on_subscribe(func(): flag_on_subscribe.v = false) \ + .do_on_terminate(func(): flag_on_terminate.v = false) \ + .do_after_terminate(func(): flag_after_terminate.v = false) \ + .do_finally(func(): flag_finally.v = false) + return await compare(obs, [42, Comp()]) or flag_on_next.v or flag_on_complete.v or flag_after_next.v or flag_on_subscribe.v or flag_on_terminate.v or flag_after_terminate.v or flag_finally.v diff --git a/addons/reactivex/testing/tests/reactiveproperty.test.gd b/addons/reactivex/testing/tests/reactiveproperty.test.gd new file mode 100644 index 0000000..fc5d710 --- /dev/null +++ b/addons/reactivex/testing/tests/reactiveproperty.test.gd @@ -0,0 +1,103 @@ +extends __GDRx_Test__ + +const TEST_UNIT_NAME = "REACTIVE_PROPERTY" + +func test_gd_reactive_property() -> bool: + var prop = ReactiveProperty.new(0) + var xs : Array = [] + var __ = prop.subscribe(func(x): xs.append(x)) + + prop.Value = 1 + prop.Value = 2 + prop.Value = 3 + var correct_value : bool = prop.Value == 3 + prop.dispose() + + var result = [0, 1, 2, 3, Comp()] + return await compare(GDRx.from_array(xs), result) or !correct_value + +var _member : int = -1 +func test_gd_from_member() -> bool: + self._member = 0 + var prop = ReactiveProperty.FromMember(self, "_member") + var xs : Array = [] + var __ = prop.subscribe(func(x): xs.append(x)) + + prop.Value = 1 + prop.Value = 2 + prop.Value = 3 + var correct_value : bool = prop.Value == 3 + prop.dispose() + + var result = [0, 1, 2, 3, Comp()] + return await compare(GDRx.from_array(xs), result) or !correct_value or self._member != 3 + +var _setget_member : int = -1 +func _set_member(value : int): + self._setget_member = value +func _get_member() -> int: + return self._setget_member +func test_gd_setget_reactive_property() -> bool: + self._set_member(0) + var prop = ReactiveProperty.FromGetSet(self._get_member, self._set_member) + var xs : Array = [] + var __ = prop.subscribe(func(x): xs.append(x)) + + prop.Value = 1 + prop.Value = 2 + prop.Value = 3 + var correct_value : bool = prop.Value == 3 + prop.dispose() + + var result = [0, 1, 2, 3, Comp()] + return await compare(GDRx.from_array(xs), result) or !correct_value or self._get_member() != 3 + +func test_gd_readonly_reactive_property() -> bool: + var prop = ReactiveProperty.new(0) + var ro_prop = prop.to_readonly() + var xs : Array = [] + var __ = ro_prop.subscribe(func(x): xs.append(x)) + + prop.Value = 1 + prop.Value = 2 + prop.Value = 3 + var correct_value : bool = prop.Value == 3 + prop.dispose() + + var result = [0, 1, 2, 3, Comp()] + return await compare(GDRx.from_array(xs), result) or !correct_value + +func test_gd_sourced_reactive_property() -> bool: + var subject = Subject.new() + var prop = ReactiveProperty.new(0, true, true, subject.as_observable()) + var ro_prop = prop.to_readonly() + var xs : Array = [] + var __ = ro_prop.subscribe(func(x): xs.append(x)) + + subject.on_next(1) + subject.on_next(2) + subject.on_next(3) + var correct_value : bool = prop.Value == 3 + subject.dispose() + + var result = [0, 1, 2, 3, Comp()] + return await compare(GDRx.from_array(xs), result) or !correct_value + +func test_gd_computed_reactive_property() -> bool: + var prop1 = ReactiveProperty.new(0) + var prop2 = ReactiveProperty.new(0) + var ro_prop1 = prop1.to_readonly() + var ro_prop2 = prop2.to_readonly() + var prop = ReactiveProperty.Computed2(ro_prop1, ro_prop2, func(x, y): return x + y) + var xs : Array = [] + var __ = prop.subscribe(func(x): xs.append(x)) + + prop1.Value = 1 + prop2.Value = 2 + prop1.Value = 3 + prop2.Value = 4 + var correct_value : bool = prop1.Value == 3 and prop2.Value == 4 + prop1.dispose() + + var result = [0, 1, 3, 5, 7, Comp()] + return await compare(GDRx.from_array(xs), result) or !correct_value diff --git a/addons/reactivex/testing/tests/scheduler.test.gd b/addons/reactivex/testing/tests/scheduler.test.gd new file mode 100644 index 0000000..9fc6265 --- /dev/null +++ b/addons/reactivex/testing/tests/scheduler.test.gd @@ -0,0 +1,8 @@ +extends __GDRx_Test__ + +const TEST_UNIT_NAME = "SCHEDULER" + +func test_nts_priodic_timer() -> bool: + var t = GDRx.start_periodic_timer(0.1, NewThreadScheduler.singleton()).take(2) + var result = [0, 1, Comp()] + return await compare(t, result) diff --git a/addons/reactivex/testing/tests/timer.test.gd b/addons/reactivex/testing/tests/timer.test.gd new file mode 100644 index 0000000..f6d5f29 --- /dev/null +++ b/addons/reactivex/testing/tests/timer.test.gd @@ -0,0 +1,13 @@ +extends __GDRx_Test__ + +const TEST_UNIT_NAME = "TIMERS" + +func test_rx_timer() -> bool: + var obs = GDRx.start_timer(0.1) + var result = [0, Comp()] + return await compare(obs, result) + +func test_rx_periodic_timer() -> bool: + var obs = GDRx.start_periodic_timer(0.1).take(5) + var result = [0, 1, 2, 3, 4, Comp()] + return await compare(obs, result) diff --git a/assets/bubbles.png b/assets/bubbles.png new file mode 100644 index 0000000..86ebfe2 Binary files /dev/null and b/assets/bubbles.png differ diff --git a/assets/bubbles.png.import b/assets/bubbles.png.import new file mode 100644 index 0000000..f7b7f06 --- /dev/null +++ b/assets/bubbles.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://x60cxv42nmsa" +path="res://.godot/imported/bubbles.png-aef2e2d42768f7bd9d203b61dafd4d00.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/bubbles.png" +dest_files=["res://.godot/imported/bubbles.png-aef2e2d42768f7bd9d203b61dafd4d00.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/characters/coco/coco.atlas b/assets/characters/coco/coco.atlas new file mode 100644 index 0000000..6319eb9 --- /dev/null +++ b/assets/characters/coco/coco.atlas @@ -0,0 +1,7 @@ +coco.png +size:661,1178 +filter:Linear,Linear +pma:true +2024-11-19-03-06-41 +bounds:2,2,657,1174 +offsets:21,31,699,1220 diff --git a/assets/characters/coco/coco.atlas.import b/assets/characters/coco/coco.atlas.import new file mode 100644 index 0000000..b440cfd --- /dev/null +++ b/assets/characters/coco/coco.atlas.import @@ -0,0 +1,15 @@ +[remap] + +importer="spine.atlas" +type="SpineAtlasResource" +uid="uid://bc0c8osnp33q4" +path="res://.godot/imported/coco.atlas-5bdb1934763eed051214cbd2636e61ef.spatlas" + +[deps] + +source_file="res://assets/characters/coco/coco.atlas" +dest_files=["res://.godot/imported/coco.atlas-5bdb1934763eed051214cbd2636e61ef.spatlas"] + +[params] + +normal_map_prefix="n" diff --git a/assets/characters/coco/coco.png b/assets/characters/coco/coco.png new file mode 100644 index 0000000..276f836 Binary files /dev/null and b/assets/characters/coco/coco.png differ diff --git a/assets/characters/coco/coco.png.import b/assets/characters/coco/coco.png.import new file mode 100644 index 0000000..1a008c8 --- /dev/null +++ b/assets/characters/coco/coco.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://18l652s6j663" +path="res://.godot/imported/coco.png-1c2aafc8c8e7b29647954b9dff431953.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/characters/coco/coco.png" +dest_files=["res://.godot/imported/coco.png-1c2aafc8c8e7b29647954b9dff431953.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/characters/coco/coco.tres b/assets/characters/coco/coco.tres new file mode 100644 index 0000000..7b67ff1 --- /dev/null +++ b/assets/characters/coco/coco.tres @@ -0,0 +1,13 @@ +[gd_resource type="SpineSkeletonDataResource" load_steps=4 format=3 uid="uid://dn5wtcrr40ahd"] + +[ext_resource type="SpineAtlasResource" uid="uid://bc0c8osnp33q4" path="res://assets/characters/coco/coco.atlas" id="1_pvekc"] +[ext_resource type="SpineSkeletonFileResource" uid="uid://cooseqw4fb232" path="res://assets/characters/coco/skeleton.skel" id="2_x7r6f"] + +[sub_resource type="SpineAnimationMix" id="SpineAnimationMix_tddij"] +from = "animation" +to = "animation" + +[resource] +atlas_res = ExtResource("1_pvekc") +skeleton_file_res = ExtResource("2_x7r6f") +animation_mixes = [SubResource("SpineAnimationMix_tddij")] diff --git a/assets/characters/coco/skeleton.skel b/assets/characters/coco/skeleton.skel new file mode 100644 index 0000000..860f761 Binary files /dev/null and b/assets/characters/coco/skeleton.skel differ diff --git a/assets/characters/coco/skeleton.skel.import b/assets/characters/coco/skeleton.skel.import new file mode 100644 index 0000000..b041869 --- /dev/null +++ b/assets/characters/coco/skeleton.skel.import @@ -0,0 +1,14 @@ +[remap] + +importer="spine.skel" +type="SpineSkeletonFileResource" +uid="uid://cooseqw4fb232" +path="res://.godot/imported/skeleton.skel-403815e13eca2b568aff5bfd4deb933c.spskel" + +[deps] + +source_file="res://assets/characters/coco/skeleton.skel" +dest_files=["res://.godot/imported/skeleton.skel-403815e13eca2b568aff5bfd4deb933c.spskel"] + +[params] + diff --git a/assets/characters/player.gd b/assets/characters/player.gd new file mode 100644 index 0000000..152b975 --- /dev/null +++ b/assets/characters/player.gd @@ -0,0 +1,18 @@ +extends CharacterBody3D + +const SPEED = 150.0 +const JUMP_VELOCITY = 200 + +signal test + +func _physics_process(delta: float) -> void: + # Add the gravity. + if not is_on_floor(): + velocity += get_gravity() * delta + self.velocity.x = delta * SPEED * Input.get_axis("move_left", "move_right") + self.velocity.z = delta * SPEED * Input.get_axis("move_forward", "move_back") + + if Input.is_key_pressed(KEY_SPACE) and is_on_floor(): + self.velocity.y = delta * JUMP_VELOCITY + + move_and_slide() diff --git a/assets/characters/player.tscn b/assets/characters/player.tscn new file mode 100644 index 0000000..6757e07 --- /dev/null +++ b/assets/characters/player.tscn @@ -0,0 +1,67 @@ +[gd_scene load_steps=11 format=3 uid="uid://b2maarsg7u3po"] + +[ext_resource type="Script" path="res://assets/characters/player.gd" id="1_fwkda"] +[ext_resource type="Script" path="res://src/viewport.gd" id="2_0d00w"] +[ext_resource type="SpineSkeletonDataResource" uid="uid://dn5wtcrr40ahd" path="res://assets/characters/coco/coco.tres" id="2_byycm"] +[ext_resource type="Script" path="res://src/camera_3d.gd" id="2_cjtsr"] +[ext_resource type="Script" path="res://src/spine_sprite.gd" id="4_bwb5d"] + +[sub_resource type="ViewportTexture" id="ViewportTexture_ver3c"] +viewport_path = NodePath("MeshInstance3D/SubViewport") + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_a31vv"] +transparency = 1 +albedo_texture = SubResource("ViewportTexture_ver3c") + +[sub_resource type="QuadMesh" id="QuadMesh_iwcmf"] + +[sub_resource type="CylinderShape3D" id="CylinderShape3D_wn1m5"] + +[sub_resource type="CylinderShape3D" id="CylinderShape3D_cbp6h"] + +[node name="Player" type="CharacterBody3D"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.48192, 0) +axis_lock_angular_x = true +axis_lock_angular_y = true +axis_lock_angular_z = true +script = ExtResource("1_fwkda") + +[node name="Camera3D" type="Camera3D" parent="."] +transform = Transform3D(0.999987, -0.00490892, -0.00171441, 0.00514322, 0.982274, 0.187381, 0.000764184, -0.187388, 0.982286, -0.015, 1.154, 5.342) +current = true +fov = 33.4 +size = 2.0 +script = ExtResource("2_cjtsr") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="."] +transform = Transform3D(0.999996, 0.0005929, -0.00274461, 0, 0.977453, 0.211152, 0.00280793, -0.211152, 0.977449, 0, 0, 0) +material_override = SubResource("StandardMaterial3D_a31vv") +cast_shadow = 0 +mesh = SubResource("QuadMesh_iwcmf") +script = ExtResource("2_0d00w") + +[node name="SubViewport" type="SubViewport" parent="MeshInstance3D"] +transparent_bg = true +handle_input_locally = false +canvas_cull_mask = 4294966273 +size = Vector2i(1024, 1024) +size_2d_override_stretch = true + +[node name="SpineSprite" type="SpineSprite" parent="MeshInstance3D/SubViewport"] +position = Vector2(512, 1024) +scale = Vector2(0.5, 0.5) +skeleton_data_res = ExtResource("2_byycm") +preview_skin = "default" +preview_animation = "animation" +preview_frame = true +preview_time = 0.0 +script = ExtResource("4_bwb5d") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +transform = Transform3D(0.4, 0, 0, 0, 0.4, 0, 0, 0, 0.4, 0, 0, 0) +shape = SubResource("CylinderShape3D_wn1m5") + +[node name="Area3D" type="Area3D" parent="." groups=["Player"]] + +[node name="CollisionShape3D" type="CollisionShape3D" parent="Area3D"] +shape = SubResource("CylinderShape3D_cbp6h") diff --git a/assets/characters/player_outline.gdshader b/assets/characters/player_outline.gdshader new file mode 100644 index 0000000..1b70f10 --- /dev/null +++ b/assets/characters/player_outline.gdshader @@ -0,0 +1,69 @@ +shader_type canvas_item; +render_mode blend_premul_alpha; + +uniform float Line_Smoothness : hint_range(0, 0.1) = 0.045; +uniform float Line_Width : hint_range(0, 0.2) = 0.09; +uniform float Brightness = 3.0; +uniform float Rotation_deg : hint_range(-90, 90) = 30; +uniform float Distortion : hint_range(1, 2) = 1.8; +uniform float Speed = 0.7; +uniform float Position : hint_range(0, 1) = 0; +uniform float Position_Min = 0.25; +uniform float Position_Max = 0.5; +uniform float Alpha : hint_range(0, 1) = 1; +uniform sampler2D surface: source_color; + +vec2 rotate_uv(vec2 uv, vec2 center, float rotation, bool use_degrees){ + float _angle = rotation; + if(use_degrees){ + _angle = rotation * (3.1415926/180.0); + } + mat2 _rotation = mat2( + vec2(cos(_angle), -sin(_angle)), + vec2(sin(_angle), cos(_angle)) + ); + vec2 _delta = uv - center; + _delta = _rotation * _delta; + return _delta + center; + } + +void fragment() { + vec2 center_uv = UV - vec2(0.5, 0.5); + float gradient_to_edge = max(abs(center_uv.x), abs(center_uv.y)); + gradient_to_edge = gradient_to_edge * Distortion; + gradient_to_edge = 1.0 - gradient_to_edge; + vec2 rotaded_uv = rotate_uv(UV, vec2(0.5, 0.5), Rotation_deg, true); + + float remapped_position; + { + float output_range = Position_Max - Position_Min; + remapped_position = Position_Min + output_range * Position; + } + + float remapped_time = TIME * Speed + remapped_position; + remapped_time = fract(remapped_time); + { + remapped_time = -2.0 + 4.0 * remapped_time; + } + + vec2 offset_uv = vec2(rotaded_uv.xy) + vec2(remapped_time, 0.0); + float line = vec3(offset_uv, 0.0).x; + line = abs(line); + line = gradient_to_edge * line; + line = sqrt(line); + + float line_smoothness = clamp(Line_Smoothness, 0.001, 1.0); + float offset_plus = Line_Width + line_smoothness; + float offset_minus = Line_Width - line_smoothness; + + float remapped_line; + { + float input_range = offset_minus - offset_plus; + remapped_line = (line - offset_plus) / input_range; + } + remapped_line *= Brightness; + remapped_line = min(remapped_line, Alpha); + + vec4 surface_tex = texture(surface, UV); + COLOR.rgba *= vec4(remapped_line) * surface_tex.a; +} \ No newline at end of file diff --git a/assets/levels/collision_shape_3d.gd b/assets/levels/collision_shape_3d.gd new file mode 100644 index 0000000..5ffd185 --- /dev/null +++ b/assets/levels/collision_shape_3d.gd @@ -0,0 +1,11 @@ +extends CollisionShape3D + + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta: float) -> void: + pass diff --git a/assets/levels/level1.gd b/assets/levels/level1.gd new file mode 100644 index 0000000..9c55771 --- /dev/null +++ b/assets/levels/level1.gd @@ -0,0 +1,6 @@ +extends Node2D + + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + print("test") diff --git a/assets/levels/level1.gdshader b/assets/levels/level1.gdshader new file mode 100644 index 0000000..e620e4b --- /dev/null +++ b/assets/levels/level1.gdshader @@ -0,0 +1,41 @@ +shader_type canvas_item; + +uniform vec4 bottom_color: source_color = vec4(1.0); +uniform vec4 top_color: source_color = vec4(vec3(0.0), 1.0); + +uniform sampler2D tex; +uniform int layer_count: hint_range(2, 80, 1); +uniform float time_scale: hint_range(0.0, 1.0) = 0.2; +uniform float base_intensity: hint_range(0.0, 1.0) = 0.5; +uniform float size: hint_range(0.00001, 1.0) = 0.1; + + +vec4 lerp(vec4 a, vec4 b, float w) { + return a + w * (b - a); +} + + +float fmod(float x, float y) { + return x - floor(x / y) * y; +} + + +float rand(float n){return fract(sin(n) * 43758.5453123);} + + +bool cloud_layer(float x, float y, float h) { + return y - sqrt((1.0 - pow(y - h, 2.0))) * base_intensity * texture(tex, vec2(fmod(x / size + rand(h), 1.0), fmod(y / size - TIME * time_scale, 1.0))).r < h; +} + + +void fragment() { + float y = 1.0 - UV.y; + + for (int i = 0; i < layer_count; i++) { + float h = float(i) / float(layer_count - 1); + if (cloud_layer(UV.x, y, h)) { + COLOR = lerp(bottom_color, top_color, h); + break; + } + } +} \ No newline at end of file diff --git a/assets/levels/level1.tscn b/assets/levels/level1.tscn new file mode 100644 index 0000000..9b8e713 --- /dev/null +++ b/assets/levels/level1.tscn @@ -0,0 +1,107 @@ +[gd_scene load_steps=20 format=3 uid="uid://b2b0sgltiefki"] + +[ext_resource type="Script" path="res://assets/levels/level_1.gd" id="1_4n7jv"] +[ext_resource type="Texture2D" uid="uid://h04a4ju855bp" path="res://assets/objects/kenney_prototype-textures/PNG/Dark/texture_02.png" id="1_xris1"] +[ext_resource type="PackedScene" uid="uid://hylwpibwtg0c" path="res://assets/objects/box/box.tscn" id="2_8p87y"] +[ext_resource type="PackedScene" uid="uid://s1ftlvvluili" path="res://assets/objects/box_small/box_small.tscn" id="2_ml88v"] +[ext_resource type="PackedScene" uid="uid://bs6cfck1f4yp5" path="res://assets/objects/transition.tscn" id="5_if8du"] +[ext_resource type="Script" path="res://src/viewport.gd" id="6_uuo0x"] +[ext_resource type="Texture2D" uid="uid://x60cxv42nmsa" path="res://assets/bubbles.png" id="7_el3mb"] +[ext_resource type="Shader" path="res://assets/levels/test.gdshader" id="8_68tud"] + +[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_ulx7c"] +sky_top_color = Color(0.129, 0.129, 0.129, 1) +sky_horizon_color = Color(0.129, 0.129, 0.129, 1) +ground_bottom_color = Color(0.129, 0.129, 0.129, 1) +ground_horizon_color = Color(0.129, 0.129, 0.129, 1) + +[sub_resource type="Sky" id="Sky_rnwem"] +sky_material = SubResource("ProceduralSkyMaterial_ulx7c") + +[sub_resource type="Environment" id="Environment_06i34"] +background_mode = 2 +sky = SubResource("Sky_rnwem") +tonemap_mode = 2 + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_er7pi"] +albedo_texture = ExtResource("1_xris1") +uv1_triplanar = true + +[sub_resource type="PlaneMesh" id="PlaneMesh_u412x"] +lightmap_size_hint = Vector2i(52, 52) +material = SubResource("StandardMaterial3D_er7pi") +size = Vector2(10, 5) + +[sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_flcen"] +data = PackedVector3Array(5, 0, 5, -5, 0, 5, 5, 0, -5, -5, 0, 5, -5, 0, -5, 5, 0, -5) + +[sub_resource type="ViewportTexture" id="ViewportTexture_ldt4a"] +viewport_path = NodePath("MeshInstance3D/SubViewport") + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_mdr1l"] +transparency = 2 +alpha_scissor_threshold = 0.5 +alpha_antialiasing_mode = 0 +albedo_texture = SubResource("ViewportTexture_ldt4a") + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_lrfyr"] + +[sub_resource type="PlaneMesh" id="PlaneMesh_dquro"] + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_d4amb"] +shader = ExtResource("8_68tud") +shader_parameter/bottom_color = Color(0.34902, 1, 1, 1) +shader_parameter/top_color = Color(0.312314, 0.589497, 0.370244, 1) +shader_parameter/layer_count = 7 +shader_parameter/time_scale = 0.2 +shader_parameter/base_intensity = 0.5 +shader_parameter/size = 0.92001 +shader_parameter/tex = ExtResource("7_el3mb") + +[node name="Level" type="Node3D"] +script = ExtResource("1_4n7jv") + +[node name="WorldEnvironment" type="WorldEnvironment" parent="."] +environment = SubResource("Environment_06i34") + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] +transform = Transform3D(0.990369, 0.119902, -0.0692252, 0, 0.499998, 0.866027, 0.138451, -0.857686, 0.495183, 0, 3.47491, 2.77581) +light_energy = 0.454 +shadow_enabled = true +directional_shadow_max_distance = 54.0 + +[node name="Floor" type="MeshInstance3D" parent="."] +mesh = SubResource("PlaneMesh_u412x") +skeleton = NodePath("../DirectionalLight3D") + +[node name="StaticBody3D" type="StaticBody3D" parent="Floor"] + +[node name="CollisionShape3D" type="CollisionShape3D" parent="Floor/StaticBody3D"] +transform = Transform3D(5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0) +shape = SubResource("ConcavePolygonShape3D_flcen") + +[node name="Box" parent="." instance=ExtResource("2_8p87y")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.31663, 0.5, 0) + +[node name="Box2" parent="." instance=ExtResource("2_ml88v")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.61149, 0.132777, -0.890927) + +[node name="Transition" parent="." instance=ExtResource("5_if8du")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -3, 0.0325054, 1) + +[node name="CanvasLayer" type="CanvasLayer" parent="."] + +[node name="MeshInstance3D" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 0.260875, -0.965373, 0, 0.965373, 0.260875, -0.797396, 0.00474185, -1.24873) +material_override = SubResource("StandardMaterial3D_mdr1l") +material_overlay = SubResource("ShaderMaterial_lrfyr") +mesh = SubResource("PlaneMesh_dquro") +skeleton = NodePath("../CanvasLayer") +script = ExtResource("6_uuo0x") + +[node name="SubViewport" type="SubViewport" parent="MeshInstance3D"] + +[node name="ColorRect" type="ColorRect" parent="MeshInstance3D/SubViewport"] +material = SubResource("ShaderMaterial_d4amb") +offset_right = 512.0 +offset_bottom = 512.0 diff --git a/assets/levels/level2.tscn b/assets/levels/level2.tscn new file mode 100644 index 0000000..54facea --- /dev/null +++ b/assets/levels/level2.tscn @@ -0,0 +1,58 @@ +[gd_scene load_steps=9 format=3 uid="uid://b2hrbmuimfxj8"] + +[ext_resource type="Texture2D" uid="uid://h04a4ju855bp" path="res://assets/objects/kenney_prototype-textures/PNG/Dark/texture_02.png" id="1_54j28"] +[ext_resource type="PackedScene" uid="uid://hylwpibwtg0c" path="res://assets/objects/box/box.tscn" id="2_owuk1"] + +[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_tp8ku"] +sky_top_color = Color(0.129, 0.129, 0.129, 1) +sky_horizon_color = Color(0.129, 0.129, 0.129, 1) +ground_bottom_color = Color(0.129, 0.129, 0.129, 1) +ground_horizon_color = Color(0.129, 0.129, 0.129, 1) + +[sub_resource type="Sky" id="Sky_shl8g"] +sky_material = SubResource("ProceduralSkyMaterial_tp8ku") + +[sub_resource type="Environment" id="Environment_d3cpo"] +background_mode = 2 +sky = SubResource("Sky_shl8g") +tonemap_mode = 2 + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_t7r8y"] +albedo_texture = ExtResource("1_54j28") +uv1_triplanar = true + +[sub_resource type="PlaneMesh" id="PlaneMesh_ka7wb"] +lightmap_size_hint = Vector2i(52, 52) +material = SubResource("StandardMaterial3D_t7r8y") +size = Vector2(50, 50) + +[sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_w1gda"] +data = PackedVector3Array(5, 0, 5, -5, 0, 5, 5, 0, -5, -5, 0, 5, -5, 0, -5, 5, 0, -5) + +[node name="Level" type="Node3D"] + +[node name="WorldEnvironment" type="WorldEnvironment" parent="."] +environment = SubResource("Environment_d3cpo") + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] +transform = Transform3D(0.990369, 0.119902, -0.0692252, 0, 0.499998, 0.866027, 0.138451, -0.857686, 0.495183, 0, 3.47491, 2.77581) +light_energy = 0.454 +shadow_enabled = true +directional_shadow_max_distance = 54.0 + +[node name="Floor" type="MeshInstance3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00175023, -5.96046e-08, 0.00229335) +mesh = SubResource("PlaneMesh_ka7wb") +skeleton = NodePath("../DirectionalLight3D") + +[node name="StaticBody3D" type="StaticBody3D" parent="Floor"] + +[node name="CollisionShape3D" type="CollisionShape3D" parent="Floor/StaticBody3D"] +transform = Transform3D(5, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0) +shape = SubResource("ConcavePolygonShape3D_w1gda") + +[node name="Box" parent="." instance=ExtResource("2_owuk1")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.00805, 0.25778, -0.993837) + +[node name="Box2" parent="." instance=ExtResource("2_owuk1")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.447608, 0.258858, -0.51353) diff --git a/assets/levels/level_1.gd b/assets/levels/level_1.gd new file mode 100644 index 0000000..f0bfbd3 --- /dev/null +++ b/assets/levels/level_1.gd @@ -0,0 +1,18 @@ +extends Node3D + + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta: float) -> void: + pass + + +func _on_area_3d_area_entered(area: Area3D) -> void: + if area.is_in_group("Player"): + #get_tree().change_scene_to_packed() + print("Hit") + print(area) diff --git a/assets/levels/test.gdshader b/assets/levels/test.gdshader new file mode 100644 index 0000000..e620e4b --- /dev/null +++ b/assets/levels/test.gdshader @@ -0,0 +1,41 @@ +shader_type canvas_item; + +uniform vec4 bottom_color: source_color = vec4(1.0); +uniform vec4 top_color: source_color = vec4(vec3(0.0), 1.0); + +uniform sampler2D tex; +uniform int layer_count: hint_range(2, 80, 1); +uniform float time_scale: hint_range(0.0, 1.0) = 0.2; +uniform float base_intensity: hint_range(0.0, 1.0) = 0.5; +uniform float size: hint_range(0.00001, 1.0) = 0.1; + + +vec4 lerp(vec4 a, vec4 b, float w) { + return a + w * (b - a); +} + + +float fmod(float x, float y) { + return x - floor(x / y) * y; +} + + +float rand(float n){return fract(sin(n) * 43758.5453123);} + + +bool cloud_layer(float x, float y, float h) { + return y - sqrt((1.0 - pow(y - h, 2.0))) * base_intensity * texture(tex, vec2(fmod(x / size + rand(h), 1.0), fmod(y / size - TIME * time_scale, 1.0))).r < h; +} + + +void fragment() { + float y = 1.0 - UV.y; + + for (int i = 0; i < layer_count; i++) { + float h = float(i) / float(layer_count - 1); + if (cloud_layer(UV.x, y, h)) { + COLOR = lerp(bottom_color, top_color, h); + break; + } + } +} \ No newline at end of file diff --git a/assets/main.tscn b/assets/main.tscn new file mode 100644 index 0000000..a1c7ade --- /dev/null +++ b/assets/main.tscn @@ -0,0 +1,36 @@ +[gd_scene load_steps=8 format=3 uid="uid://x4qu20qmep66"] + +[ext_resource type="Script" path="res://src/main.gd" id="1_l1pcn"] +[ext_resource type="Script" path="res://src/input_manager.gd" id="2_hvjva"] +[ext_resource type="Script" path="res://src/console_manager.gd" id="3_65iwr"] +[ext_resource type="Script" path="res://src/dialogue_manager.gd" id="4_oir0n"] +[ext_resource type="Script" path="res://src/state_manager.gd" id="5_4aaix"] +[ext_resource type="Script" path="res://src/level_manager.gd" id="6_xklu3"] +[ext_resource type="Script" path="res://src/camera_manager.gd" id="7_i2npv"] + +[node name="Main" type="Node"] +script = ExtResource("1_l1pcn") + +[node name="CharacterManager" type="Node" parent="."] +script = ExtResource("2_hvjva") + +[node name="ConsoleManager" type="Node" parent="."] +script = ExtResource("3_65iwr") + +[node name="DialogueManager" type="Node" parent="."] +script = ExtResource("4_oir0n") + +[node name="StateManager" type="Node" parent="."] +script = ExtResource("5_4aaix") + +[node name="InventoryManager" type="Node" parent="."] + +[node name="CombatManager" type="Node" parent="."] + +[node name="LevelManager" type="Node" parent="."] +script = ExtResource("6_xklu3") + +[node name="CameraManager" type="Node" parent="."] +script = ExtResource("7_i2npv") + +[node name="MainCamera" type="Camera3D" parent="CameraManager"] diff --git a/assets/objects/box/box.tscn b/assets/objects/box/box.tscn new file mode 100644 index 0000000..88ca939 --- /dev/null +++ b/assets/objects/box/box.tscn @@ -0,0 +1,24 @@ +[gd_scene load_steps=5 format=3 uid="uid://hylwpibwtg0c"] + +[ext_resource type="Texture2D" uid="uid://c4vvb6uitdpwj" path="res://assets/objects/kenney_prototype-textures/PNG/Red/texture_01.png" id="1_g7vff"] + +[sub_resource type="BoxShape3D" id="BoxShape3D_w7cyf"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_tx180"] +albedo_texture = ExtResource("1_g7vff") +uv1_triplanar = true + +[sub_resource type="BoxMesh" id="BoxMesh_u56mx"] +material = SubResource("StandardMaterial3D_tx180") +size = Vector3(2, 2, 2) + +[node name="Box" type="Node3D"] + +[node name="StaticBody3D" type="StaticBody3D" parent="."] + +[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D"] +shape = SubResource("BoxShape3D_w7cyf") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="StaticBody3D"] +transform = Transform3D(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, 0, 0) +mesh = SubResource("BoxMesh_u56mx") diff --git a/assets/objects/box_small/box_small.tscn b/assets/objects/box_small/box_small.tscn new file mode 100644 index 0000000..bff32e4 --- /dev/null +++ b/assets/objects/box_small/box_small.tscn @@ -0,0 +1,23 @@ +[gd_scene load_steps=5 format=3 uid="uid://s1ftlvvluili"] + +[ext_resource type="Texture2D" uid="uid://c4vvb6uitdpwj" path="res://assets/objects/kenney_prototype-textures/PNG/Red/texture_01.png" id="1_f4o0k"] + +[sub_resource type="BoxShape3D" id="BoxShape3D_w7cyf"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_tx180"] +albedo_texture = ExtResource("1_f4o0k") + +[sub_resource type="BoxMesh" id="BoxMesh_u56mx"] +material = SubResource("StandardMaterial3D_tx180") + +[node name="Box" type="Node3D"] + +[node name="StaticBody3D" type="StaticBody3D" parent="."] + +[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D"] +transform = Transform3D(0.25, 0, 0, 0, 0.25, 0, 0, 0, 0.25, 0.000184551, -0.000704616, 0.000337839) +shape = SubResource("BoxShape3D_w7cyf") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="StaticBody3D"] +transform = Transform3D(0.25, 0, 0, 0, 0.25, 0, 0, 0, 0.25, 0.000963956, 5.96046e-08, -0.000526607) +mesh = SubResource("BoxMesh_u56mx") diff --git a/assets/objects/kenney_prototype-textures/Kenney.url b/assets/objects/kenney_prototype-textures/Kenney.url new file mode 100644 index 0000000..fbdde43 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Kenney.url @@ -0,0 +1,2 @@ +[InternetShortcut] +URL=http://www.kenney.nl/ \ No newline at end of file diff --git a/assets/objects/kenney_prototype-textures/License.txt b/assets/objects/kenney_prototype-textures/License.txt new file mode 100644 index 0000000..bda3f93 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/License.txt @@ -0,0 +1,23 @@ + + + Prototype Textures 1.0 + + Created/distributed by Kenney (www.kenney.nl) + Creation date: 08-04-2020 + + ------------------------------ + + License: (Creative Commons Zero, CC0) + http://creativecommons.org/publicdomain/zero/1.0/ + + This content is free to use in personal, educational and commercial projects. + Support us by crediting Kenney or www.kenney.nl (this is not mandatory) + + ------------------------------ + + Donate: http://support.kenney.nl + Request: http://request.kenney.nl + Patreon: http://patreon.com/kenney/ + + Follow on Twitter for updates: + http://twitter.com/KenneyNL \ No newline at end of file diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_01.png b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_01.png new file mode 100644 index 0000000..69be211 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_01.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_01.png.import b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_01.png.import new file mode 100644 index 0000000..47a0997 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_01.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cu4b3dnv88y0g" +path="res://.godot/imported/texture_01.png-66c5a5b8f3e8d60a83612771436b49d4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Dark/texture_01.png" +dest_files=["res://.godot/imported/texture_01.png-66c5a5b8f3e8d60a83612771436b49d4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_02.png b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_02.png new file mode 100644 index 0000000..6fb471b Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_02.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_02.png.import b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_02.png.import new file mode 100644 index 0000000..b19fdc4 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_02.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://h04a4ju855bp" +path.s3tc="res://.godot/imported/texture_02.png-ea0049358809f859a60115ecacfba67d.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Dark/texture_02.png" +dest_files=["res://.godot/imported/texture_02.png-ea0049358809f859a60115ecacfba67d.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_03.png b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_03.png new file mode 100644 index 0000000..3f8b186 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_03.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_03.png.import b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_03.png.import new file mode 100644 index 0000000..a488457 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_03.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cwgiuelr2c2sg" +path="res://.godot/imported/texture_03.png-7f86741dcfd2c0afe13ce55b1edac2fc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Dark/texture_03.png" +dest_files=["res://.godot/imported/texture_03.png-7f86741dcfd2c0afe13ce55b1edac2fc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_04.png b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_04.png new file mode 100644 index 0000000..e2bc22b Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_04.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_04.png.import b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_04.png.import new file mode 100644 index 0000000..76c5417 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_04.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c5x75fdbd750g" +path="res://.godot/imported/texture_04.png-e24c6b997de37851bef0da886cd6064c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Dark/texture_04.png" +dest_files=["res://.godot/imported/texture_04.png-e24c6b997de37851bef0da886cd6064c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_05.png b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_05.png new file mode 100644 index 0000000..3fd2e56 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_05.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_05.png.import b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_05.png.import new file mode 100644 index 0000000..cdd9fcf --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_05.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://buudic5cctpev" +path="res://.godot/imported/texture_05.png-d50d924b8605e9c304bc76825e2454ac.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Dark/texture_05.png" +dest_files=["res://.godot/imported/texture_05.png-d50d924b8605e9c304bc76825e2454ac.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_06.png b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_06.png new file mode 100644 index 0000000..45d4a34 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_06.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_06.png.import b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_06.png.import new file mode 100644 index 0000000..0f7b2f3 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_06.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c75vhgu62bhy" +path="res://.godot/imported/texture_06.png-922a57ca01fa758082c74dfafe9c10a5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Dark/texture_06.png" +dest_files=["res://.godot/imported/texture_06.png-922a57ca01fa758082c74dfafe9c10a5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_07.png b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_07.png new file mode 100644 index 0000000..adf5e6f Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_07.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_07.png.import b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_07.png.import new file mode 100644 index 0000000..b2313b8 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_07.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dgtpp1edorct6" +path="res://.godot/imported/texture_07.png-0fd8f787c7e5b5533af3fb4860e70bda.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Dark/texture_07.png" +dest_files=["res://.godot/imported/texture_07.png-0fd8f787c7e5b5533af3fb4860e70bda.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_08.png b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_08.png new file mode 100644 index 0000000..a5a9f24 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_08.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_08.png.import b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_08.png.import new file mode 100644 index 0000000..b62788e --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_08.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bxqwwxbiwsub8" +path="res://.godot/imported/texture_08.png-82c6eabb1c81fa8fccd82d290aa55b03.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Dark/texture_08.png" +dest_files=["res://.godot/imported/texture_08.png-82c6eabb1c81fa8fccd82d290aa55b03.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_09.png b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_09.png new file mode 100644 index 0000000..57cc607 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_09.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_09.png.import b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_09.png.import new file mode 100644 index 0000000..28db8bc --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_09.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bcjjc8626bbli" +path="res://.godot/imported/texture_09.png-866809be6049a99bd7a7e24ae7f86799.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Dark/texture_09.png" +dest_files=["res://.godot/imported/texture_09.png-866809be6049a99bd7a7e24ae7f86799.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_10.png b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_10.png new file mode 100644 index 0000000..4c737d0 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_10.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_10.png.import b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_10.png.import new file mode 100644 index 0000000..dad6d00 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_10.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://m80kd3r4q8ee" +path="res://.godot/imported/texture_10.png-09fea7c7c99bda90109170baed572ba9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Dark/texture_10.png" +dest_files=["res://.godot/imported/texture_10.png-09fea7c7c99bda90109170baed572ba9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_11.png b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_11.png new file mode 100644 index 0000000..424ba71 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_11.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_11.png.import b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_11.png.import new file mode 100644 index 0000000..3edc554 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_11.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://todcrljjbe7p" +path="res://.godot/imported/texture_11.png-58188f476e2871e71a3984711d8e2141.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Dark/texture_11.png" +dest_files=["res://.godot/imported/texture_11.png-58188f476e2871e71a3984711d8e2141.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_12.png b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_12.png new file mode 100644 index 0000000..32169db Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_12.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_12.png.import b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_12.png.import new file mode 100644 index 0000000..d74dbb0 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_12.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b7kafaf7sxyhs" +path="res://.godot/imported/texture_12.png-79f18ec60e30617facb0dc55d1e0c0e8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Dark/texture_12.png" +dest_files=["res://.godot/imported/texture_12.png-79f18ec60e30617facb0dc55d1e0c0e8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_13.png b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_13.png new file mode 100644 index 0000000..13c4388 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_13.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Dark/texture_13.png.import b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_13.png.import new file mode 100644 index 0000000..df52fac --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Dark/texture_13.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://tah44cred6ew" +path="res://.godot/imported/texture_13.png-ddcdb6a7496da01b2209c795b202a309.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Dark/texture_13.png" +dest_files=["res://.godot/imported/texture_13.png-ddcdb6a7496da01b2209c795b202a309.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_01.png b/assets/objects/kenney_prototype-textures/PNG/Green/texture_01.png new file mode 100644 index 0000000..d576514 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Green/texture_01.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_01.png.import b/assets/objects/kenney_prototype-textures/PNG/Green/texture_01.png.import new file mode 100644 index 0000000..7701e65 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Green/texture_01.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://55w061qtapkg" +path="res://.godot/imported/texture_01.png-de3176e1f434921408db5b0b408c3abd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Green/texture_01.png" +dest_files=["res://.godot/imported/texture_01.png-de3176e1f434921408db5b0b408c3abd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_02.png b/assets/objects/kenney_prototype-textures/PNG/Green/texture_02.png new file mode 100644 index 0000000..7bc7cf8 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Green/texture_02.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_02.png.import b/assets/objects/kenney_prototype-textures/PNG/Green/texture_02.png.import new file mode 100644 index 0000000..e8563d3 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Green/texture_02.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://n1wncctbjy0j" +path="res://.godot/imported/texture_02.png-bcd7934ef703348a82ef131a2ac7a0ff.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Green/texture_02.png" +dest_files=["res://.godot/imported/texture_02.png-bcd7934ef703348a82ef131a2ac7a0ff.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_03.png b/assets/objects/kenney_prototype-textures/PNG/Green/texture_03.png new file mode 100644 index 0000000..e2a3889 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Green/texture_03.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_03.png.import b/assets/objects/kenney_prototype-textures/PNG/Green/texture_03.png.import new file mode 100644 index 0000000..0b925ca --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Green/texture_03.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://gipep2fn1gat" +path="res://.godot/imported/texture_03.png-ace6f16900b81c7fe5eac2e0757a45f4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Green/texture_03.png" +dest_files=["res://.godot/imported/texture_03.png-ace6f16900b81c7fe5eac2e0757a45f4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_04.png b/assets/objects/kenney_prototype-textures/PNG/Green/texture_04.png new file mode 100644 index 0000000..3952bef Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Green/texture_04.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_04.png.import b/assets/objects/kenney_prototype-textures/PNG/Green/texture_04.png.import new file mode 100644 index 0000000..4fb4d55 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Green/texture_04.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bucvjofglx63k" +path="res://.godot/imported/texture_04.png-5fc3659046dd76c8ced1d11fe740e1c0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Green/texture_04.png" +dest_files=["res://.godot/imported/texture_04.png-5fc3659046dd76c8ced1d11fe740e1c0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_05.png b/assets/objects/kenney_prototype-textures/PNG/Green/texture_05.png new file mode 100644 index 0000000..84976ba Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Green/texture_05.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_05.png.import b/assets/objects/kenney_prototype-textures/PNG/Green/texture_05.png.import new file mode 100644 index 0000000..876a6b7 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Green/texture_05.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://6xccbnj2ncj7" +path="res://.godot/imported/texture_05.png-8721bdeda3122e01a93f6918cc8a8a30.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Green/texture_05.png" +dest_files=["res://.godot/imported/texture_05.png-8721bdeda3122e01a93f6918cc8a8a30.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_06.png b/assets/objects/kenney_prototype-textures/PNG/Green/texture_06.png new file mode 100644 index 0000000..1128198 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Green/texture_06.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_06.png.import b/assets/objects/kenney_prototype-textures/PNG/Green/texture_06.png.import new file mode 100644 index 0000000..b882370 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Green/texture_06.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dflg6lf13hmkh" +path="res://.godot/imported/texture_06.png-fcf1a601ce5ddbdd872760df4b8e749c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Green/texture_06.png" +dest_files=["res://.godot/imported/texture_06.png-fcf1a601ce5ddbdd872760df4b8e749c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_07.png b/assets/objects/kenney_prototype-textures/PNG/Green/texture_07.png new file mode 100644 index 0000000..3160860 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Green/texture_07.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_07.png.import b/assets/objects/kenney_prototype-textures/PNG/Green/texture_07.png.import new file mode 100644 index 0000000..3b3e0d0 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Green/texture_07.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dxdnsefaokko6" +path="res://.godot/imported/texture_07.png-21ef3dfbb6f4e4f60f41b9f9c3d931c3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Green/texture_07.png" +dest_files=["res://.godot/imported/texture_07.png-21ef3dfbb6f4e4f60f41b9f9c3d931c3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_08.png b/assets/objects/kenney_prototype-textures/PNG/Green/texture_08.png new file mode 100644 index 0000000..386293d Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Green/texture_08.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_08.png.import b/assets/objects/kenney_prototype-textures/PNG/Green/texture_08.png.import new file mode 100644 index 0000000..f7562cb --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Green/texture_08.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b5nkaljr3u6bc" +path="res://.godot/imported/texture_08.png-418d2284502447e67d5e1588bafdca8e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Green/texture_08.png" +dest_files=["res://.godot/imported/texture_08.png-418d2284502447e67d5e1588bafdca8e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_09.png b/assets/objects/kenney_prototype-textures/PNG/Green/texture_09.png new file mode 100644 index 0000000..48234f6 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Green/texture_09.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_09.png.import b/assets/objects/kenney_prototype-textures/PNG/Green/texture_09.png.import new file mode 100644 index 0000000..19b2298 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Green/texture_09.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://be1qwwd6g8yh4" +path="res://.godot/imported/texture_09.png-a3798440a8963de848dee182a14b5978.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Green/texture_09.png" +dest_files=["res://.godot/imported/texture_09.png-a3798440a8963de848dee182a14b5978.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_10.png b/assets/objects/kenney_prototype-textures/PNG/Green/texture_10.png new file mode 100644 index 0000000..e4ab057 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Green/texture_10.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_10.png.import b/assets/objects/kenney_prototype-textures/PNG/Green/texture_10.png.import new file mode 100644 index 0000000..8476508 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Green/texture_10.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c5ei83j3px0lg" +path.s3tc="res://.godot/imported/texture_10.png-83359568a3de4a2e6683902b9753bdcb.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Green/texture_10.png" +dest_files=["res://.godot/imported/texture_10.png-83359568a3de4a2e6683902b9753bdcb.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_11.png b/assets/objects/kenney_prototype-textures/PNG/Green/texture_11.png new file mode 100644 index 0000000..82ad458 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Green/texture_11.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_11.png.import b/assets/objects/kenney_prototype-textures/PNG/Green/texture_11.png.import new file mode 100644 index 0000000..ef08eb8 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Green/texture_11.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cky34tcqbkdvu" +path="res://.godot/imported/texture_11.png-cdf5640b4d9983bd34aabbad12d24f25.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Green/texture_11.png" +dest_files=["res://.godot/imported/texture_11.png-cdf5640b4d9983bd34aabbad12d24f25.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_12.png b/assets/objects/kenney_prototype-textures/PNG/Green/texture_12.png new file mode 100644 index 0000000..a15000d Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Green/texture_12.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_12.png.import b/assets/objects/kenney_prototype-textures/PNG/Green/texture_12.png.import new file mode 100644 index 0000000..70179bf --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Green/texture_12.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://co6bs0ffxga4g" +path.s3tc="res://.godot/imported/texture_12.png-752f91d791bcd7d45e745422ecb665cc.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Green/texture_12.png" +dest_files=["res://.godot/imported/texture_12.png-752f91d791bcd7d45e745422ecb665cc.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_13.png b/assets/objects/kenney_prototype-textures/PNG/Green/texture_13.png new file mode 100644 index 0000000..930fdf0 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Green/texture_13.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Green/texture_13.png.import b/assets/objects/kenney_prototype-textures/PNG/Green/texture_13.png.import new file mode 100644 index 0000000..1dc1d4e --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Green/texture_13.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cvke5b5bv88vn" +path="res://.godot/imported/texture_13.png-d244c6abd8b6b5331e928242ff6ed289.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Green/texture_13.png" +dest_files=["res://.godot/imported/texture_13.png-d244c6abd8b6b5331e928242ff6ed289.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_01.png b/assets/objects/kenney_prototype-textures/PNG/Light/texture_01.png new file mode 100644 index 0000000..60b632b Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Light/texture_01.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_01.png.import b/assets/objects/kenney_prototype-textures/PNG/Light/texture_01.png.import new file mode 100644 index 0000000..c1e9e69 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Light/texture_01.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://crld1nol60dga" +path="res://.godot/imported/texture_01.png-18736b69b693d0d6fce4a9b27f543cb4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Light/texture_01.png" +dest_files=["res://.godot/imported/texture_01.png-18736b69b693d0d6fce4a9b27f543cb4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_02.png b/assets/objects/kenney_prototype-textures/PNG/Light/texture_02.png new file mode 100644 index 0000000..19aad62 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Light/texture_02.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_02.png.import b/assets/objects/kenney_prototype-textures/PNG/Light/texture_02.png.import new file mode 100644 index 0000000..9ac5a42 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Light/texture_02.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cv1v3bob6bo05" +path="res://.godot/imported/texture_02.png-77f126bb44351c18c67d670d0981d4a7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Light/texture_02.png" +dest_files=["res://.godot/imported/texture_02.png-77f126bb44351c18c67d670d0981d4a7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_03.png b/assets/objects/kenney_prototype-textures/PNG/Light/texture_03.png new file mode 100644 index 0000000..a8a6c06 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Light/texture_03.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_03.png.import b/assets/objects/kenney_prototype-textures/PNG/Light/texture_03.png.import new file mode 100644 index 0000000..9632ed2 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Light/texture_03.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bu6d41pbgjoj2" +path="res://.godot/imported/texture_03.png-ca25c76b39aa6819654a00ed5e70d5a4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Light/texture_03.png" +dest_files=["res://.godot/imported/texture_03.png-ca25c76b39aa6819654a00ed5e70d5a4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_04.png b/assets/objects/kenney_prototype-textures/PNG/Light/texture_04.png new file mode 100644 index 0000000..b8270e1 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Light/texture_04.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_04.png.import b/assets/objects/kenney_prototype-textures/PNG/Light/texture_04.png.import new file mode 100644 index 0000000..efd9ca6 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Light/texture_04.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b65okhhmgbhkx" +path="res://.godot/imported/texture_04.png-a69edb8e6df119598535ee32a7963faf.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Light/texture_04.png" +dest_files=["res://.godot/imported/texture_04.png-a69edb8e6df119598535ee32a7963faf.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_05.png b/assets/objects/kenney_prototype-textures/PNG/Light/texture_05.png new file mode 100644 index 0000000..5b985bf Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Light/texture_05.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_05.png.import b/assets/objects/kenney_prototype-textures/PNG/Light/texture_05.png.import new file mode 100644 index 0000000..58b64bc --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Light/texture_05.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://hp6kl8oevxrs" +path="res://.godot/imported/texture_05.png-326b43280dc331f0ff783c449a160988.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Light/texture_05.png" +dest_files=["res://.godot/imported/texture_05.png-326b43280dc331f0ff783c449a160988.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_06.png b/assets/objects/kenney_prototype-textures/PNG/Light/texture_06.png new file mode 100644 index 0000000..195ad77 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Light/texture_06.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_06.png.import b/assets/objects/kenney_prototype-textures/PNG/Light/texture_06.png.import new file mode 100644 index 0000000..9706a8d --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Light/texture_06.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://lbklex6j0ewn" +path="res://.godot/imported/texture_06.png-246bab2356684153016bf3b6eb31ce96.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Light/texture_06.png" +dest_files=["res://.godot/imported/texture_06.png-246bab2356684153016bf3b6eb31ce96.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_07.png b/assets/objects/kenney_prototype-textures/PNG/Light/texture_07.png new file mode 100644 index 0000000..fbf92e0 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Light/texture_07.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_07.png.import b/assets/objects/kenney_prototype-textures/PNG/Light/texture_07.png.import new file mode 100644 index 0000000..de0640c --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Light/texture_07.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://djllagx48mpml" +path="res://.godot/imported/texture_07.png-70251cbcb189d2f360131e51e5f06e53.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Light/texture_07.png" +dest_files=["res://.godot/imported/texture_07.png-70251cbcb189d2f360131e51e5f06e53.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_08.png b/assets/objects/kenney_prototype-textures/PNG/Light/texture_08.png new file mode 100644 index 0000000..72e1a07 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Light/texture_08.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_08.png.import b/assets/objects/kenney_prototype-textures/PNG/Light/texture_08.png.import new file mode 100644 index 0000000..528b41b --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Light/texture_08.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://db7dn7frcyj8l" +path="res://.godot/imported/texture_08.png-a304c39d777f987162633fd92e673764.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Light/texture_08.png" +dest_files=["res://.godot/imported/texture_08.png-a304c39d777f987162633fd92e673764.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_09.png b/assets/objects/kenney_prototype-textures/PNG/Light/texture_09.png new file mode 100644 index 0000000..9ada5dd Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Light/texture_09.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_09.png.import b/assets/objects/kenney_prototype-textures/PNG/Light/texture_09.png.import new file mode 100644 index 0000000..5d7e1be --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Light/texture_09.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://y4yifn61a07s" +path="res://.godot/imported/texture_09.png-113dad9308fd975e3631b2506f126cb6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Light/texture_09.png" +dest_files=["res://.godot/imported/texture_09.png-113dad9308fd975e3631b2506f126cb6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_10.png b/assets/objects/kenney_prototype-textures/PNG/Light/texture_10.png new file mode 100644 index 0000000..4fbcc80 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Light/texture_10.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_10.png.import b/assets/objects/kenney_prototype-textures/PNG/Light/texture_10.png.import new file mode 100644 index 0000000..c3ccdc2 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Light/texture_10.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cdoxphw1c3me0" +path="res://.godot/imported/texture_10.png-d483a05901474981b26a2dec0684acfe.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Light/texture_10.png" +dest_files=["res://.godot/imported/texture_10.png-d483a05901474981b26a2dec0684acfe.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_11.png b/assets/objects/kenney_prototype-textures/PNG/Light/texture_11.png new file mode 100644 index 0000000..840ecec Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Light/texture_11.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_11.png.import b/assets/objects/kenney_prototype-textures/PNG/Light/texture_11.png.import new file mode 100644 index 0000000..b0b53e3 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Light/texture_11.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://go08wbjvdaje" +path="res://.godot/imported/texture_11.png-336a2a81dc821529a5f932361e42ee32.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Light/texture_11.png" +dest_files=["res://.godot/imported/texture_11.png-336a2a81dc821529a5f932361e42ee32.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_12.png b/assets/objects/kenney_prototype-textures/PNG/Light/texture_12.png new file mode 100644 index 0000000..25c2e78 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Light/texture_12.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_12.png.import b/assets/objects/kenney_prototype-textures/PNG/Light/texture_12.png.import new file mode 100644 index 0000000..77559dd --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Light/texture_12.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bk8a2ued8jtcn" +path="res://.godot/imported/texture_12.png-703bbcfbe7a5ef249d61e53b98b3eb6a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Light/texture_12.png" +dest_files=["res://.godot/imported/texture_12.png-703bbcfbe7a5ef249d61e53b98b3eb6a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_13.png b/assets/objects/kenney_prototype-textures/PNG/Light/texture_13.png new file mode 100644 index 0000000..a235965 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Light/texture_13.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Light/texture_13.png.import b/assets/objects/kenney_prototype-textures/PNG/Light/texture_13.png.import new file mode 100644 index 0000000..a839abd --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Light/texture_13.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cm0224duc07jt" +path="res://.godot/imported/texture_13.png-bc8f75187b3d3987f5532ec491bdd174.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Light/texture_13.png" +dest_files=["res://.godot/imported/texture_13.png-bc8f75187b3d3987f5532ec491bdd174.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_01.png b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_01.png new file mode 100644 index 0000000..4f5bf92 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_01.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_01.png.import b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_01.png.import new file mode 100644 index 0000000..0b5f226 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_01.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://i23h2dard5n6" +path="res://.godot/imported/texture_01.png-291f80781efba0099fee9fc783d961e6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Orange/texture_01.png" +dest_files=["res://.godot/imported/texture_01.png-291f80781efba0099fee9fc783d961e6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_02.png b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_02.png new file mode 100644 index 0000000..dec5b59 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_02.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_02.png.import b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_02.png.import new file mode 100644 index 0000000..53321d9 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_02.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b541ytpsvf8nl" +path="res://.godot/imported/texture_02.png-8690530d75dc0c589d6397fe04bde95a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Orange/texture_02.png" +dest_files=["res://.godot/imported/texture_02.png-8690530d75dc0c589d6397fe04bde95a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_03.png b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_03.png new file mode 100644 index 0000000..666197f Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_03.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_03.png.import b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_03.png.import new file mode 100644 index 0000000..682fa1b --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_03.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b106gs7tl2po1" +path="res://.godot/imported/texture_03.png-c7d1d50d3f7a1cc7b3cbca80bff5830a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Orange/texture_03.png" +dest_files=["res://.godot/imported/texture_03.png-c7d1d50d3f7a1cc7b3cbca80bff5830a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_04.png b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_04.png new file mode 100644 index 0000000..23d6fc4 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_04.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_04.png.import b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_04.png.import new file mode 100644 index 0000000..9b56773 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_04.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ce5v4lxw57wew" +path="res://.godot/imported/texture_04.png-0efa4a3510dae08dcd29091e22ea1039.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Orange/texture_04.png" +dest_files=["res://.godot/imported/texture_04.png-0efa4a3510dae08dcd29091e22ea1039.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_05.png b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_05.png new file mode 100644 index 0000000..1e0448a Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_05.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_05.png.import b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_05.png.import new file mode 100644 index 0000000..461526a --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_05.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bvhesb0f82ys2" +path="res://.godot/imported/texture_05.png-62dba98c05b606088c869f79e7723df5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Orange/texture_05.png" +dest_files=["res://.godot/imported/texture_05.png-62dba98c05b606088c869f79e7723df5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_06.png b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_06.png new file mode 100644 index 0000000..5486825 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_06.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_06.png.import b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_06.png.import new file mode 100644 index 0000000..d57af43 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_06.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bo0fn2vodf4ab" +path="res://.godot/imported/texture_06.png-651f2c7d4bb800653897e9b0037cc057.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Orange/texture_06.png" +dest_files=["res://.godot/imported/texture_06.png-651f2c7d4bb800653897e9b0037cc057.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_07.png b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_07.png new file mode 100644 index 0000000..95f2790 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_07.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_07.png.import b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_07.png.import new file mode 100644 index 0000000..bda227e --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_07.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b27nkx3swle6d" +path="res://.godot/imported/texture_07.png-b704b61c1479b5360662389456f28161.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Orange/texture_07.png" +dest_files=["res://.godot/imported/texture_07.png-b704b61c1479b5360662389456f28161.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_08.png b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_08.png new file mode 100644 index 0000000..5a500d9 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_08.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_08.png.import b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_08.png.import new file mode 100644 index 0000000..e5235fd --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_08.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ltc0yq6ubvkj" +path="res://.godot/imported/texture_08.png-b811b4e762bb82bdf8eec6f75e3e816d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Orange/texture_08.png" +dest_files=["res://.godot/imported/texture_08.png-b811b4e762bb82bdf8eec6f75e3e816d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_09.png b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_09.png new file mode 100644 index 0000000..adcfa33 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_09.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_09.png.import b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_09.png.import new file mode 100644 index 0000000..33e9e5c --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_09.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c7jtxlyrdf8od" +path="res://.godot/imported/texture_09.png-c431706ab87783af04c47dbc205a8e73.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Orange/texture_09.png" +dest_files=["res://.godot/imported/texture_09.png-c431706ab87783af04c47dbc205a8e73.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_10.png b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_10.png new file mode 100644 index 0000000..aa227e5 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_10.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_10.png.import b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_10.png.import new file mode 100644 index 0000000..f9322fd --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_10.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cht6g8awiqqep" +path="res://.godot/imported/texture_10.png-fd90072eb596c895b7c6c5a6c2537cbf.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Orange/texture_10.png" +dest_files=["res://.godot/imported/texture_10.png-fd90072eb596c895b7c6c5a6c2537cbf.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_11.png b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_11.png new file mode 100644 index 0000000..dc94567 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_11.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_11.png.import b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_11.png.import new file mode 100644 index 0000000..6826a4f --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_11.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dkj7mxpcauui0" +path="res://.godot/imported/texture_11.png-512925a9cd86fbbaf3065aedeb878b49.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Orange/texture_11.png" +dest_files=["res://.godot/imported/texture_11.png-512925a9cd86fbbaf3065aedeb878b49.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_12.png b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_12.png new file mode 100644 index 0000000..b730544 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_12.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_12.png.import b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_12.png.import new file mode 100644 index 0000000..9075de2 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_12.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bguuobi1ue2og" +path="res://.godot/imported/texture_12.png-769a1f9bc76c295ebbee33022c153a44.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Orange/texture_12.png" +dest_files=["res://.godot/imported/texture_12.png-769a1f9bc76c295ebbee33022c153a44.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_13.png b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_13.png new file mode 100644 index 0000000..01f4aa8 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_13.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Orange/texture_13.png.import b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_13.png.import new file mode 100644 index 0000000..d38db35 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Orange/texture_13.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d3w350w8wg2ix" +path="res://.godot/imported/texture_13.png-afcca936d23ba4c05d1bde8d97a3f489.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Orange/texture_13.png" +dest_files=["res://.godot/imported/texture_13.png-afcca936d23ba4c05d1bde8d97a3f489.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_01.png b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_01.png new file mode 100644 index 0000000..d501875 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_01.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_01.png.import b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_01.png.import new file mode 100644 index 0000000..4de33bf --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_01.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ddw06xoeyfri7" +path="res://.godot/imported/texture_01.png-e32fc622233b97ce139aaa87b520931b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Purple/texture_01.png" +dest_files=["res://.godot/imported/texture_01.png-e32fc622233b97ce139aaa87b520931b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_02.png b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_02.png new file mode 100644 index 0000000..48a51c1 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_02.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_02.png.import b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_02.png.import new file mode 100644 index 0000000..09a578b --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_02.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dqll8rbp1uwc5" +path="res://.godot/imported/texture_02.png-5c65a1a13490ecf828a8382488b4af85.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Purple/texture_02.png" +dest_files=["res://.godot/imported/texture_02.png-5c65a1a13490ecf828a8382488b4af85.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_03.png b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_03.png new file mode 100644 index 0000000..5f97f24 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_03.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_03.png.import b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_03.png.import new file mode 100644 index 0000000..281bc48 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_03.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://coxpor1ymkvl1" +path="res://.godot/imported/texture_03.png-ed00ef2c3b7934b91eca39278730c845.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Purple/texture_03.png" +dest_files=["res://.godot/imported/texture_03.png-ed00ef2c3b7934b91eca39278730c845.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_04.png b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_04.png new file mode 100644 index 0000000..b81951c Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_04.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_04.png.import b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_04.png.import new file mode 100644 index 0000000..120c754 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_04.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dimp3ae4oigok" +path="res://.godot/imported/texture_04.png-0691398e6bebc151adbfffa8cfd13c41.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Purple/texture_04.png" +dest_files=["res://.godot/imported/texture_04.png-0691398e6bebc151adbfffa8cfd13c41.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_05.png b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_05.png new file mode 100644 index 0000000..52352c8 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_05.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_05.png.import b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_05.png.import new file mode 100644 index 0000000..60a8117 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_05.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dr75y5c2ov5p0" +path="res://.godot/imported/texture_05.png-9b64e15c4326e9512b57e670b600d485.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Purple/texture_05.png" +dest_files=["res://.godot/imported/texture_05.png-9b64e15c4326e9512b57e670b600d485.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_06.png b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_06.png new file mode 100644 index 0000000..86deeb3 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_06.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_06.png.import b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_06.png.import new file mode 100644 index 0000000..b891caf --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_06.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://crotgdxqspeu3" +path="res://.godot/imported/texture_06.png-3469609abb99be8a00ae6a917cd4a100.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Purple/texture_06.png" +dest_files=["res://.godot/imported/texture_06.png-3469609abb99be8a00ae6a917cd4a100.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_07.png b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_07.png new file mode 100644 index 0000000..b71ce4e Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_07.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_07.png.import b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_07.png.import new file mode 100644 index 0000000..8c39ec7 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_07.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dlgieulodifoc" +path="res://.godot/imported/texture_07.png-5521d0bc3e95dc718adfef6163582625.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Purple/texture_07.png" +dest_files=["res://.godot/imported/texture_07.png-5521d0bc3e95dc718adfef6163582625.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_08.png b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_08.png new file mode 100644 index 0000000..470cc5c Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_08.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_08.png.import b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_08.png.import new file mode 100644 index 0000000..9117f64 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_08.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b3ngyaofi7ljq" +path="res://.godot/imported/texture_08.png-cf06de9cbf2fc7e3d242724c3b3faecf.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Purple/texture_08.png" +dest_files=["res://.godot/imported/texture_08.png-cf06de9cbf2fc7e3d242724c3b3faecf.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_09.png b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_09.png new file mode 100644 index 0000000..4a3f689 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_09.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_09.png.import b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_09.png.import new file mode 100644 index 0000000..8d4c5eb --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_09.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ddgy1qdb3ibj6" +path="res://.godot/imported/texture_09.png-7deba61ed1a0052d69538ffe6d89cc07.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Purple/texture_09.png" +dest_files=["res://.godot/imported/texture_09.png-7deba61ed1a0052d69538ffe6d89cc07.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_10.png b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_10.png new file mode 100644 index 0000000..ab7e84b Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_10.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_10.png.import b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_10.png.import new file mode 100644 index 0000000..8e55d65 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_10.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://wwq8wuyxleky" +path="res://.godot/imported/texture_10.png-f90620e90715e786e33a1fe6e92da1cb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Purple/texture_10.png" +dest_files=["res://.godot/imported/texture_10.png-f90620e90715e786e33a1fe6e92da1cb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_11.png b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_11.png new file mode 100644 index 0000000..ad7bebc Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_11.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_11.png.import b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_11.png.import new file mode 100644 index 0000000..d440310 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_11.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://blrlpwmlamrqh" +path="res://.godot/imported/texture_11.png-1b4c1e28ad76bd2a29c60435bf8867e8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Purple/texture_11.png" +dest_files=["res://.godot/imported/texture_11.png-1b4c1e28ad76bd2a29c60435bf8867e8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_12.png b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_12.png new file mode 100644 index 0000000..979ac5a Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_12.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_12.png.import b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_12.png.import new file mode 100644 index 0000000..33ac774 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_12.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bc3pu3t356rxg" +path="res://.godot/imported/texture_12.png-38499d92e9cd82f2f6aa4117293d4fd4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Purple/texture_12.png" +dest_files=["res://.godot/imported/texture_12.png-38499d92e9cd82f2f6aa4117293d4fd4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_13.png b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_13.png new file mode 100644 index 0000000..2a9df22 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_13.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Purple/texture_13.png.import b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_13.png.import new file mode 100644 index 0000000..d174f9a --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Purple/texture_13.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c04ftxf4fw4gv" +path="res://.godot/imported/texture_13.png-91cce21567ab29a6bec99214d12f31d1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Purple/texture_13.png" +dest_files=["res://.godot/imported/texture_13.png-91cce21567ab29a6bec99214d12f31d1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_01.png b/assets/objects/kenney_prototype-textures/PNG/Red/texture_01.png new file mode 100644 index 0000000..1aaab41 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Red/texture_01.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_01.png.import b/assets/objects/kenney_prototype-textures/PNG/Red/texture_01.png.import new file mode 100644 index 0000000..b98d21e --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Red/texture_01.png.import @@ -0,0 +1,35 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c4vvb6uitdpwj" +path.s3tc="res://.godot/imported/texture_01.png-5881568de5fa3b1cefaedf4bc7983ed0.s3tc.ctex" +metadata={ +"imported_formats": ["s3tc_bptc"], +"vram_texture": true +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Red/texture_01.png" +dest_files=["res://.godot/imported/texture_01.png-5881568de5fa3b1cefaedf4bc7983ed0.s3tc.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_02.png b/assets/objects/kenney_prototype-textures/PNG/Red/texture_02.png new file mode 100644 index 0000000..bf1cb17 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Red/texture_02.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_02.png.import b/assets/objects/kenney_prototype-textures/PNG/Red/texture_02.png.import new file mode 100644 index 0000000..3aee6bf --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Red/texture_02.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ceu6iirpoxlgf" +path="res://.godot/imported/texture_02.png-5ca36d1cdd66a3ec52a2db0b952a7008.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Red/texture_02.png" +dest_files=["res://.godot/imported/texture_02.png-5ca36d1cdd66a3ec52a2db0b952a7008.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_03.png b/assets/objects/kenney_prototype-textures/PNG/Red/texture_03.png new file mode 100644 index 0000000..ff09c22 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Red/texture_03.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_03.png.import b/assets/objects/kenney_prototype-textures/PNG/Red/texture_03.png.import new file mode 100644 index 0000000..c093a03 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Red/texture_03.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dxi7f3jvckxd0" +path="res://.godot/imported/texture_03.png-c932ea73c5085fe322e9653244ede387.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Red/texture_03.png" +dest_files=["res://.godot/imported/texture_03.png-c932ea73c5085fe322e9653244ede387.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_04.png b/assets/objects/kenney_prototype-textures/PNG/Red/texture_04.png new file mode 100644 index 0000000..b5b77ff Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Red/texture_04.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_04.png.import b/assets/objects/kenney_prototype-textures/PNG/Red/texture_04.png.import new file mode 100644 index 0000000..2e6b2d3 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Red/texture_04.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cbpo20o5g0myc" +path="res://.godot/imported/texture_04.png-d982d35079ff2ec4fd4b3d302e7d829a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Red/texture_04.png" +dest_files=["res://.godot/imported/texture_04.png-d982d35079ff2ec4fd4b3d302e7d829a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_05.png b/assets/objects/kenney_prototype-textures/PNG/Red/texture_05.png new file mode 100644 index 0000000..7827035 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Red/texture_05.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_05.png.import b/assets/objects/kenney_prototype-textures/PNG/Red/texture_05.png.import new file mode 100644 index 0000000..3d6efed --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Red/texture_05.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c4dtw2gpn5pbk" +path="res://.godot/imported/texture_05.png-5e5d6713f0b7cd2e6ec1ce25f886fa36.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Red/texture_05.png" +dest_files=["res://.godot/imported/texture_05.png-5e5d6713f0b7cd2e6ec1ce25f886fa36.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_06.png b/assets/objects/kenney_prototype-textures/PNG/Red/texture_06.png new file mode 100644 index 0000000..914a6f1 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Red/texture_06.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_06.png.import b/assets/objects/kenney_prototype-textures/PNG/Red/texture_06.png.import new file mode 100644 index 0000000..70fe554 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Red/texture_06.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c8dxmnemex5o4" +path="res://.godot/imported/texture_06.png-8f6233126a6b829762347a7a4a0ff2b2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Red/texture_06.png" +dest_files=["res://.godot/imported/texture_06.png-8f6233126a6b829762347a7a4a0ff2b2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_07.png b/assets/objects/kenney_prototype-textures/PNG/Red/texture_07.png new file mode 100644 index 0000000..47d517b Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Red/texture_07.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_07.png.import b/assets/objects/kenney_prototype-textures/PNG/Red/texture_07.png.import new file mode 100644 index 0000000..c46b72b --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Red/texture_07.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://u8holvehrc05" +path="res://.godot/imported/texture_07.png-07141510d0a8c8fa199c2a5fb27d72b2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Red/texture_07.png" +dest_files=["res://.godot/imported/texture_07.png-07141510d0a8c8fa199c2a5fb27d72b2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_08.png b/assets/objects/kenney_prototype-textures/PNG/Red/texture_08.png new file mode 100644 index 0000000..07cfc41 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Red/texture_08.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_08.png.import b/assets/objects/kenney_prototype-textures/PNG/Red/texture_08.png.import new file mode 100644 index 0000000..68f2191 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Red/texture_08.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://6vdvw0x8decw" +path="res://.godot/imported/texture_08.png-78282fb7dc05339cebff0ba21af48e58.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Red/texture_08.png" +dest_files=["res://.godot/imported/texture_08.png-78282fb7dc05339cebff0ba21af48e58.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_09.png b/assets/objects/kenney_prototype-textures/PNG/Red/texture_09.png new file mode 100644 index 0000000..86d67d8 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Red/texture_09.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_09.png.import b/assets/objects/kenney_prototype-textures/PNG/Red/texture_09.png.import new file mode 100644 index 0000000..9861dfc --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Red/texture_09.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bfv1p1ewviqg1" +path="res://.godot/imported/texture_09.png-022f62314dafbf19e3c5e531ec276c68.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Red/texture_09.png" +dest_files=["res://.godot/imported/texture_09.png-022f62314dafbf19e3c5e531ec276c68.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_10.png b/assets/objects/kenney_prototype-textures/PNG/Red/texture_10.png new file mode 100644 index 0000000..a9266d1 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Red/texture_10.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_10.png.import b/assets/objects/kenney_prototype-textures/PNG/Red/texture_10.png.import new file mode 100644 index 0000000..c4c0af8 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Red/texture_10.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://uvxqrs6ogwwp" +path="res://.godot/imported/texture_10.png-922210228e20e9b5268dd697b6ebcde4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Red/texture_10.png" +dest_files=["res://.godot/imported/texture_10.png-922210228e20e9b5268dd697b6ebcde4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_11.png b/assets/objects/kenney_prototype-textures/PNG/Red/texture_11.png new file mode 100644 index 0000000..2acc544 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Red/texture_11.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_11.png.import b/assets/objects/kenney_prototype-textures/PNG/Red/texture_11.png.import new file mode 100644 index 0000000..362ef50 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Red/texture_11.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bhau5o2ps5qsq" +path="res://.godot/imported/texture_11.png-c72146da4ae258260be72454d4fd237f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Red/texture_11.png" +dest_files=["res://.godot/imported/texture_11.png-c72146da4ae258260be72454d4fd237f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_12.png b/assets/objects/kenney_prototype-textures/PNG/Red/texture_12.png new file mode 100644 index 0000000..7054c4d Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Red/texture_12.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_12.png.import b/assets/objects/kenney_prototype-textures/PNG/Red/texture_12.png.import new file mode 100644 index 0000000..cf0c899 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Red/texture_12.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bfvw186iidl7a" +path="res://.godot/imported/texture_12.png-5f1e8b524290c6cf24b3f4fbdae541b1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Red/texture_12.png" +dest_files=["res://.godot/imported/texture_12.png-5f1e8b524290c6cf24b3f4fbdae541b1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_13.png b/assets/objects/kenney_prototype-textures/PNG/Red/texture_13.png new file mode 100644 index 0000000..ab8c7b9 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/PNG/Red/texture_13.png differ diff --git a/assets/objects/kenney_prototype-textures/PNG/Red/texture_13.png.import b/assets/objects/kenney_prototype-textures/PNG/Red/texture_13.png.import new file mode 100644 index 0000000..72d5931 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/PNG/Red/texture_13.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cfvsiohpyae12" +path="res://.godot/imported/texture_13.png-ba0e0b680fa392d1295ed9c3f706548b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/PNG/Red/texture_13.png" +dest_files=["res://.godot/imported/texture_13.png-ba0e0b680fa392d1295ed9c3f706548b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/Patreon.url b/assets/objects/kenney_prototype-textures/Patreon.url new file mode 100644 index 0000000..439ef26 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Patreon.url @@ -0,0 +1,2 @@ +[InternetShortcut] +URL=https://www.patreon.com/kenney/ \ No newline at end of file diff --git a/assets/objects/kenney_prototype-textures/Preview.png b/assets/objects/kenney_prototype-textures/Preview.png new file mode 100644 index 0000000..148cbd8 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/Preview.png differ diff --git a/assets/objects/kenney_prototype-textures/Preview.png.import b/assets/objects/kenney_prototype-textures/Preview.png.import new file mode 100644 index 0000000..9dff750 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Preview.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bg4rsrmul78b8" +path="res://.godot/imported/Preview.png-167cd2a23fa4e8ae8fb2493570618f11.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/Preview.png" +dest_files=["res://.godot/imported/Preview.png-167cd2a23fa4e8ae8fb2493570618f11.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/Sample.png b/assets/objects/kenney_prototype-textures/Sample.png new file mode 100644 index 0000000..5699163 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/Sample.png differ diff --git a/assets/objects/kenney_prototype-textures/Sample.png.import b/assets/objects/kenney_prototype-textures/Sample.png.import new file mode 100644 index 0000000..16c9bbc --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Sample.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bq2jfc80icwyn" +path="res://.godot/imported/Sample.png-44d92f760d4546eb83908a23688419fb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/Sample.png" +dest_files=["res://.godot/imported/Sample.png-44d92f760d4546eb83908a23688419fb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_01.svg b/assets/objects/kenney_prototype-textures/Vector/texture_01.svg new file mode 100644 index 0000000..fc16bc7 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_01.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_01.svg.import b/assets/objects/kenney_prototype-textures/Vector/texture_01.svg.import new file mode 100644 index 0000000..2e58505 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_01.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://80vr2qj4wm1h" +path="res://.godot/imported/texture_01.svg-4dabd657f0265c77ca7242353bbab20a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/Vector/texture_01.svg" +dest_files=["res://.godot/imported/texture_01.svg-4dabd657f0265c77ca7242353bbab20a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_01.swf b/assets/objects/kenney_prototype-textures/Vector/texture_01.swf new file mode 100644 index 0000000..4e60277 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/Vector/texture_01.swf differ diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_02.svg b/assets/objects/kenney_prototype-textures/Vector/texture_02.svg new file mode 100644 index 0000000..6af7715 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_02.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_02.svg.import b/assets/objects/kenney_prototype-textures/Vector/texture_02.svg.import new file mode 100644 index 0000000..4273d5c --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_02.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ckfdsul7se8u4" +path="res://.godot/imported/texture_02.svg-e70904eacf5ada7a333e1a1071b9c17e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/Vector/texture_02.svg" +dest_files=["res://.godot/imported/texture_02.svg-e70904eacf5ada7a333e1a1071b9c17e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_02.swf b/assets/objects/kenney_prototype-textures/Vector/texture_02.swf new file mode 100644 index 0000000..070236f Binary files /dev/null and b/assets/objects/kenney_prototype-textures/Vector/texture_02.swf differ diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_03.svg b/assets/objects/kenney_prototype-textures/Vector/texture_03.svg new file mode 100644 index 0000000..1a23062 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_03.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_03.svg.import b/assets/objects/kenney_prototype-textures/Vector/texture_03.svg.import new file mode 100644 index 0000000..56a5c81 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_03.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://qjfohnghfmw" +path="res://.godot/imported/texture_03.svg-eba021fbd795ffdbde06d1a712b55be4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/Vector/texture_03.svg" +dest_files=["res://.godot/imported/texture_03.svg-eba021fbd795ffdbde06d1a712b55be4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_03.swf b/assets/objects/kenney_prototype-textures/Vector/texture_03.swf new file mode 100644 index 0000000..6c00924 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/Vector/texture_03.swf differ diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_04.svg b/assets/objects/kenney_prototype-textures/Vector/texture_04.svg new file mode 100644 index 0000000..b8891ad --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_04.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_04.svg.import b/assets/objects/kenney_prototype-textures/Vector/texture_04.svg.import new file mode 100644 index 0000000..d975bc3 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_04.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bv86gh686glvi" +path="res://.godot/imported/texture_04.svg-f4df91e49099b8b7ca1f1f6f1282901e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/Vector/texture_04.svg" +dest_files=["res://.godot/imported/texture_04.svg-f4df91e49099b8b7ca1f1f6f1282901e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_04.swf b/assets/objects/kenney_prototype-textures/Vector/texture_04.swf new file mode 100644 index 0000000..1c1efe2 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/Vector/texture_04.swf differ diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_05.svg b/assets/objects/kenney_prototype-textures/Vector/texture_05.svg new file mode 100644 index 0000000..fa737e9 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_05.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_05.svg.import b/assets/objects/kenney_prototype-textures/Vector/texture_05.svg.import new file mode 100644 index 0000000..f69cd0a --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_05.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://7i5tgjnyi7fl" +path="res://.godot/imported/texture_05.svg-218352247c746764853851460ddbe6ae.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/Vector/texture_05.svg" +dest_files=["res://.godot/imported/texture_05.svg-218352247c746764853851460ddbe6ae.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_05.swf b/assets/objects/kenney_prototype-textures/Vector/texture_05.swf new file mode 100644 index 0000000..19b7101 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/Vector/texture_05.swf differ diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_06.svg b/assets/objects/kenney_prototype-textures/Vector/texture_06.svg new file mode 100644 index 0000000..2d7fa36 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_06.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_06.svg.import b/assets/objects/kenney_prototype-textures/Vector/texture_06.svg.import new file mode 100644 index 0000000..a6c1d92 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_06.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b0bc0q62715vk" +path="res://.godot/imported/texture_06.svg-e7314c64362110fcb61a281ce4b7055b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/Vector/texture_06.svg" +dest_files=["res://.godot/imported/texture_06.svg-e7314c64362110fcb61a281ce4b7055b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_06.swf b/assets/objects/kenney_prototype-textures/Vector/texture_06.swf new file mode 100644 index 0000000..a44aae2 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/Vector/texture_06.swf differ diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_07.svg b/assets/objects/kenney_prototype-textures/Vector/texture_07.svg new file mode 100644 index 0000000..2f76dcd --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_07.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_07.svg.import b/assets/objects/kenney_prototype-textures/Vector/texture_07.svg.import new file mode 100644 index 0000000..65409aa --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_07.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cvvoqxx10g5hm" +path="res://.godot/imported/texture_07.svg-e5a9c29d11db98c3957375bcd6547f00.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/Vector/texture_07.svg" +dest_files=["res://.godot/imported/texture_07.svg-e5a9c29d11db98c3957375bcd6547f00.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_07.swf b/assets/objects/kenney_prototype-textures/Vector/texture_07.swf new file mode 100644 index 0000000..f106e21 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/Vector/texture_07.swf differ diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_08.svg b/assets/objects/kenney_prototype-textures/Vector/texture_08.svg new file mode 100644 index 0000000..cb098b8 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_08.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_08.svg.import b/assets/objects/kenney_prototype-textures/Vector/texture_08.svg.import new file mode 100644 index 0000000..4c43de6 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_08.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bdt5chsvyr282" +path="res://.godot/imported/texture_08.svg-6bbd458a8d00fb991ae1db54eb3717a6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/Vector/texture_08.svg" +dest_files=["res://.godot/imported/texture_08.svg-6bbd458a8d00fb991ae1db54eb3717a6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_08.swf b/assets/objects/kenney_prototype-textures/Vector/texture_08.swf new file mode 100644 index 0000000..d4ccf19 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/Vector/texture_08.swf differ diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_09.svg b/assets/objects/kenney_prototype-textures/Vector/texture_09.svg new file mode 100644 index 0000000..d21be51 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_09.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_09.svg.import b/assets/objects/kenney_prototype-textures/Vector/texture_09.svg.import new file mode 100644 index 0000000..840d521 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_09.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dsannijl51thu" +path="res://.godot/imported/texture_09.svg-4bb51163bf37e9662a9362b98f34b64d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/Vector/texture_09.svg" +dest_files=["res://.godot/imported/texture_09.svg-4bb51163bf37e9662a9362b98f34b64d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_09.swf b/assets/objects/kenney_prototype-textures/Vector/texture_09.swf new file mode 100644 index 0000000..f8bb7ec Binary files /dev/null and b/assets/objects/kenney_prototype-textures/Vector/texture_09.swf differ diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_10.svg b/assets/objects/kenney_prototype-textures/Vector/texture_10.svg new file mode 100644 index 0000000..5e998e8 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_10.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_10.svg.import b/assets/objects/kenney_prototype-textures/Vector/texture_10.svg.import new file mode 100644 index 0000000..6f43ee3 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_10.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ddi2ubotivnvg" +path="res://.godot/imported/texture_10.svg-ab61f2b13374ea18e42f2f5f275f1a95.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/Vector/texture_10.svg" +dest_files=["res://.godot/imported/texture_10.svg-ab61f2b13374ea18e42f2f5f275f1a95.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_10.swf b/assets/objects/kenney_prototype-textures/Vector/texture_10.swf new file mode 100644 index 0000000..9ffd88a Binary files /dev/null and b/assets/objects/kenney_prototype-textures/Vector/texture_10.swf differ diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_11.svg b/assets/objects/kenney_prototype-textures/Vector/texture_11.svg new file mode 100644 index 0000000..fea61c5 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_11.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_11.svg.import b/assets/objects/kenney_prototype-textures/Vector/texture_11.svg.import new file mode 100644 index 0000000..06dae78 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_11.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d3aj6vagkxsp" +path="res://.godot/imported/texture_11.svg-e076cd424b937b563d1f47c2c69fc213.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/Vector/texture_11.svg" +dest_files=["res://.godot/imported/texture_11.svg-e076cd424b937b563d1f47c2c69fc213.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_11.swf b/assets/objects/kenney_prototype-textures/Vector/texture_11.swf new file mode 100644 index 0000000..b2c46ed Binary files /dev/null and b/assets/objects/kenney_prototype-textures/Vector/texture_11.swf differ diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_12.svg b/assets/objects/kenney_prototype-textures/Vector/texture_12.svg new file mode 100644 index 0000000..c0bba91 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_12.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_12.svg.import b/assets/objects/kenney_prototype-textures/Vector/texture_12.svg.import new file mode 100644 index 0000000..7808113 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_12.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://do2re2dbl16m2" +path="res://.godot/imported/texture_12.svg-8921545ea5f5f5cb2218ab466be7cad2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/Vector/texture_12.svg" +dest_files=["res://.godot/imported/texture_12.svg-8921545ea5f5f5cb2218ab466be7cad2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_12.swf b/assets/objects/kenney_prototype-textures/Vector/texture_12.swf new file mode 100644 index 0000000..a4f9d18 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/Vector/texture_12.swf differ diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_13.svg b/assets/objects/kenney_prototype-textures/Vector/texture_13.svg new file mode 100644 index 0000000..1d821bf --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_13.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_13.svg.import b/assets/objects/kenney_prototype-textures/Vector/texture_13.svg.import new file mode 100644 index 0000000..f008618 --- /dev/null +++ b/assets/objects/kenney_prototype-textures/Vector/texture_13.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dm3jey0xl5ndq" +path="res://.godot/imported/texture_13.svg-8e025238ca1447597c59e0756503f901.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/objects/kenney_prototype-textures/Vector/texture_13.svg" +dest_files=["res://.godot/imported/texture_13.svg-8e025238ca1447597c59e0756503f901.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/assets/objects/kenney_prototype-textures/Vector/texture_13.swf b/assets/objects/kenney_prototype-textures/Vector/texture_13.swf new file mode 100644 index 0000000..69ddeb9 Binary files /dev/null and b/assets/objects/kenney_prototype-textures/Vector/texture_13.swf differ diff --git a/assets/objects/transition.gd b/assets/objects/transition.gd new file mode 100644 index 0000000..7b675fe --- /dev/null +++ b/assets/objects/transition.gd @@ -0,0 +1,17 @@ +extends Node3D + +@export var to_: Node = null; + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta: float) -> void: + pass + + +func _on_area_3d_area_entered(area: Area3D) -> void: + #emit_signal() + pass diff --git a/assets/objects/transition.tscn b/assets/objects/transition.tscn new file mode 100644 index 0000000..be4ab7a --- /dev/null +++ b/assets/objects/transition.tscn @@ -0,0 +1,28 @@ +[gd_scene load_steps=6 format=3 uid="uid://bs6cfck1f4yp5"] + +[ext_resource type="Script" path="res://assets/objects/transition.gd" id="1_agecu"] +[ext_resource type="Texture2D" uid="uid://c5ei83j3px0lg" path="res://assets/objects/kenney_prototype-textures/PNG/Green/texture_10.png" id="1_crdhu"] + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_mbtyr"] +albedo_texture = ExtResource("1_crdhu") +uv1_triplanar = true +uv2_triplanar = true + +[sub_resource type="PlaneMesh" id="PlaneMesh_p1am3"] +material = SubResource("StandardMaterial3D_mbtyr") + +[sub_resource type="BoxShape3D" id="BoxShape3D_q685r"] + +[node name="Transition" type="Node3D"] +script = ExtResource("1_agecu") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="."] +mesh = SubResource("PlaneMesh_p1am3") + +[node name="Area3D" type="Area3D" parent="MeshInstance3D"] + +[node name="CollisionShape3D" type="CollisionShape3D" parent="MeshInstance3D/Area3D"] +transform = Transform3D(2, 0, 0, 0, 2, 0, 0, 0, 2, 0, 1, 0) +shape = SubResource("BoxShape3D_q685r") + +[connection signal="area_entered" from="MeshInstance3D/Area3D" to="." method="_on_area_3d_area_entered"] diff --git a/build/game.pck b/build/game.pck new file mode 100644 index 0000000..600f45b Binary files /dev/null and b/build/game.pck differ diff --git a/build/game.sh b/build/game.sh new file mode 100755 index 0000000..ed54776 --- /dev/null +++ b/build/game.sh @@ -0,0 +1,4 @@ +#!/bin/sh +echo -ne '\033c\033]0;Test Project\a' +base_path="$(dirname "$(realpath "$0")")" +"$base_path/game.x86_64" "$@" diff --git a/build/game.x86_64 b/build/game.x86_64 new file mode 100755 index 0000000..aa3efdc Binary files /dev/null and b/build/game.x86_64 differ diff --git a/build/web/Test Project.apple-touch-icon.png b/build/web/Test Project.apple-touch-icon.png new file mode 100644 index 0000000..e64341e Binary files /dev/null and b/build/web/Test Project.apple-touch-icon.png differ diff --git a/build/web/Test Project.apple-touch-icon.png.import b/build/web/Test Project.apple-touch-icon.png.import new file mode 100644 index 0000000..ae4cb37 --- /dev/null +++ b/build/web/Test Project.apple-touch-icon.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://hsjifuwo1hbi" +path="res://.godot/imported/Test Project.apple-touch-icon.png-105239907a9c8bfb65ea35a1d1d25b31.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://build/web/Test Project.apple-touch-icon.png" +dest_files=["res://.godot/imported/Test Project.apple-touch-icon.png-105239907a9c8bfb65ea35a1d1d25b31.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/build/web/Test Project.audio.worklet.js b/build/web/Test Project.audio.worklet.js new file mode 100644 index 0000000..3b94cab --- /dev/null +++ b/build/web/Test Project.audio.worklet.js @@ -0,0 +1,213 @@ +/**************************************************************************/ +/* audio.worklet.js */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +class RingBuffer { + constructor(p_buffer, p_state, p_threads) { + this.buffer = p_buffer; + this.avail = p_state; + this.threads = p_threads; + this.rpos = 0; + this.wpos = 0; + } + + data_left() { + return this.threads ? Atomics.load(this.avail, 0) : this.avail; + } + + space_left() { + return this.buffer.length - this.data_left(); + } + + read(output) { + const size = this.buffer.length; + let from = 0; + let to_write = output.length; + if (this.rpos + to_write > size) { + const high = size - this.rpos; + output.set(this.buffer.subarray(this.rpos, size)); + from = high; + to_write -= high; + this.rpos = 0; + } + if (to_write) { + output.set(this.buffer.subarray(this.rpos, this.rpos + to_write), from); + } + this.rpos += to_write; + if (this.threads) { + Atomics.add(this.avail, 0, -output.length); + Atomics.notify(this.avail, 0); + } else { + this.avail -= output.length; + } + } + + write(p_buffer) { + const to_write = p_buffer.length; + const mw = this.buffer.length - this.wpos; + if (mw >= to_write) { + this.buffer.set(p_buffer, this.wpos); + this.wpos += to_write; + if (mw === to_write) { + this.wpos = 0; + } + } else { + const high = p_buffer.subarray(0, mw); + const low = p_buffer.subarray(mw); + this.buffer.set(high, this.wpos); + this.buffer.set(low); + this.wpos = low.length; + } + if (this.threads) { + Atomics.add(this.avail, 0, to_write); + Atomics.notify(this.avail, 0); + } else { + this.avail += to_write; + } + } +} + +class GodotProcessor extends AudioWorkletProcessor { + constructor() { + super(); + this.threads = false; + this.running = true; + this.lock = null; + this.notifier = null; + this.output = null; + this.output_buffer = new Float32Array(); + this.input = null; + this.input_buffer = new Float32Array(); + this.port.onmessage = (event) => { + const cmd = event.data['cmd']; + const data = event.data['data']; + this.parse_message(cmd, data); + }; + } + + process_notify() { + if (this.notifier) { + Atomics.add(this.notifier, 0, 1); + Atomics.notify(this.notifier, 0); + } + } + + parse_message(p_cmd, p_data) { + if (p_cmd === 'start' && p_data) { + const state = p_data[0]; + let idx = 0; + this.threads = true; + this.lock = state.subarray(idx, ++idx); + this.notifier = state.subarray(idx, ++idx); + const avail_in = state.subarray(idx, ++idx); + const avail_out = state.subarray(idx, ++idx); + this.input = new RingBuffer(p_data[1], avail_in, true); + this.output = new RingBuffer(p_data[2], avail_out, true); + } else if (p_cmd === 'stop') { + this.running = false; + this.output = null; + this.input = null; + this.lock = null; + this.notifier = null; + } else if (p_cmd === 'start_nothreads') { + this.output = new RingBuffer(p_data[0], p_data[0].length, false); + } else if (p_cmd === 'chunk') { + this.output.write(p_data); + } + } + + static array_has_data(arr) { + return arr.length && arr[0].length && arr[0][0].length; + } + + process(inputs, outputs, parameters) { + if (!this.running) { + return false; // Stop processing. + } + if (this.output === null) { + return true; // Not ready yet, keep processing. + } + const process_input = GodotProcessor.array_has_data(inputs); + if (process_input) { + const input = inputs[0]; + const chunk = input[0].length * input.length; + if (this.input_buffer.length !== chunk) { + this.input_buffer = new Float32Array(chunk); + } + if (!this.threads) { + GodotProcessor.write_input(this.input_buffer, input); + this.port.postMessage({ 'cmd': 'input', 'data': this.input_buffer }); + } else if (this.input.space_left() >= chunk) { + GodotProcessor.write_input(this.input_buffer, input); + this.input.write(this.input_buffer); + } else { + // this.port.postMessage('Input buffer is full! Skipping input frame.'); // Uncomment this line to debug input buffer. + } + } + const process_output = GodotProcessor.array_has_data(outputs); + if (process_output) { + const output = outputs[0]; + const chunk = output[0].length * output.length; + if (this.output_buffer.length !== chunk) { + this.output_buffer = new Float32Array(chunk); + } + if (this.output.data_left() >= chunk) { + this.output.read(this.output_buffer); + GodotProcessor.write_output(output, this.output_buffer); + if (!this.threads) { + this.port.postMessage({ 'cmd': 'read', 'data': chunk }); + } + } else { + // this.port.postMessage('Output buffer has not enough frames! Skipping output frame.'); // Uncomment this line to debug output buffer. + } + } + this.process_notify(); + return true; + } + + static write_output(dest, source) { + const channels = dest.length; + for (let ch = 0; ch < channels; ch++) { + for (let sample = 0; sample < dest[ch].length; sample++) { + dest[ch][sample] = source[sample * channels + ch]; + } + } + } + + static write_input(dest, source) { + const channels = source.length; + for (let ch = 0; ch < channels; ch++) { + for (let sample = 0; sample < source[ch].length; sample++) { + dest[sample * channels + ch] = source[ch][sample]; + } + } + } +} + +registerProcessor('godot-processor', GodotProcessor); diff --git a/build/web/Test Project.html b/build/web/Test Project.html new file mode 100644 index 0000000..aba8be3 --- /dev/null +++ b/build/web/Test Project.html @@ -0,0 +1,199 @@ + + + + + + Test Project + + + + + + + + Your browser does not support the canvas tag. + + + + +
+ + +
+
+ + + + + + diff --git a/build/web/Test Project.icon.png b/build/web/Test Project.icon.png new file mode 100644 index 0000000..e8ed395 Binary files /dev/null and b/build/web/Test Project.icon.png differ diff --git a/build/web/Test Project.icon.png.import b/build/web/Test Project.icon.png.import new file mode 100644 index 0000000..3f84565 --- /dev/null +++ b/build/web/Test Project.icon.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://vsu5i43afibf" +path="res://.godot/imported/Test Project.icon.png-e81507e635b461cf9c1422d681220a19.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://build/web/Test Project.icon.png" +dest_files=["res://.godot/imported/Test Project.icon.png-e81507e635b461cf9c1422d681220a19.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/build/web/Test Project.js b/build/web/Test Project.js new file mode 100644 index 0000000..dd9977c --- /dev/null +++ b/build/web/Test Project.js @@ -0,0 +1,910 @@ + +var Godot = (() => { + var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined; + + return ( +function(Godot) { + Godot = Godot || {}; + +var Module=typeof Godot!="undefined"?Godot:{};var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise(function(resolve,reject){readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=title=>document.title=title}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime=Module["noExitRuntime"]||false;if(typeof WebAssembly!="object"){abort("no native wasm support detected")}var wasmMemory;var ABORT=false;var EXITSTATUS;function assert(condition,text){if(!condition){abort(text)}}var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(heapOrArray,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||33554432;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATEXIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeExited=false;var runtimeKeepaliveCounter=0;function keepRuntimeAlive(){return noExitRuntime||runtimeKeepaliveCounter>0}function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();FS.ignorePermissions=false;TTY.init();SOCKFS.root=FS.mount(SOCKFS,{},null);callRuntimeCallbacks(__ATINIT__)}function preMain(){callRuntimeCallbacks(__ATMAIN__)}function exitRuntime(){___funcs_on_exit();callRuntimeCallbacks(__ATEXIT__);FS.quit();TTY.shutdown();IDBFS.quit();runtimeExited=true}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return filename.startsWith(dataURIPrefix)}var wasmBinaryFile;wasmBinaryFile="godot.web.template_debug.wasm32.nothreads.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(file){try{if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)){if(typeof fetch=="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary(wasmBinaryFile)})}}return Promise.resolve().then(function(){return getBinary(wasmBinaryFile)})}function createWasm(){var info={"a":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["rf"];updateGlobalBufferAndViews(wasmMemory.buffer);wasmTable=Module["asm"]["tf"];addOnInit(Module["asm"]["sf"]);removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(function(instance){return instance}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(wasmBinaryFile)&&typeof fetch=="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiationResult,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiationResult)})})}else{return instantiateArrayBuffer(receiveInstantiationResult)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);readyPromiseReject(e)}}instantiateAsync().catch(readyPromiseReject);return{}}var tempDouble;var tempI64;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}function callRuntimeCallbacks(callbacks){while(callbacks.length>0){callbacks.shift()(Module)}}function getValue(ptr,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":return HEAP8[ptr>>0];case"i8":return HEAP8[ptr>>0];case"i16":return HEAP16[ptr>>1];case"i32":return HEAP32[ptr>>2];case"i64":return HEAP32[ptr>>2];case"float":return HEAPF32[ptr>>2];case"double":return HEAPF64[ptr>>3];case"*":return HEAPU32[ptr>>2];default:abort("invalid type for getValue: "+type)}return null}function setValue(ptr,value,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":HEAP8[ptr>>0]=value;break;case"i8":HEAP8[ptr>>0]=value;break;case"i16":HEAP16[ptr>>1]=value;break;case"i32":HEAP32[ptr>>2]=value;break;case"i64":tempI64=[value>>>0,(tempDouble=value,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[ptr>>2]=tempI64[0],HEAP32[ptr+4>>2]=tempI64[1];break;case"float":HEAPF32[ptr>>2]=value;break;case"double":HEAPF64[ptr>>3]=value;break;case"*":HEAPU32[ptr>>2]=value;break;default:abort("invalid type for setValue: "+type)}}function getWasmTableEntry(funcPtr){return wasmTable.get(funcPtr)}function ___call_sighandler(fp,sig){getWasmTableEntry(fp)(sig)}var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:path=>{if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},join:function(){var paths=Array.prototype.slice.call(arguments);return PATH.normalize(paths.join("/"))},join2:(l,r)=>{return PATH.normalize(l+"/"+r)}};function getRandomDevice(){if(typeof crypto=="object"&&typeof crypto["getRandomValues"]=="function"){var randomBuffer=new Uint8Array(1);return()=>{crypto.getRandomValues(randomBuffer);return randomBuffer[0]}}else return()=>abort("randomDevice")}var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}var TTY={ttys:[],init:function(){},shutdown:function(){},register:function(dev,ops){TTY.ttys[dev]={input:[],output:[],ops:ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open:function(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close:function(stream){stream.tty.ops.fsync(stream.tty)},fsync:function(stream){stream.tty.ops.fsync(stream.tty)},read:function(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};function zeroMemory(address,size){HEAPU8.fill(0,address,address+size);return address}function mmapAlloc(size){abort()}var MEMFS={ops_table:null,mount:function(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode:function(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}if(!MEMFS.ops_table){MEMFS.ops_table={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}}}var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node;parent.timestamp=node.timestamp}return node},getFileDataAsTypedArray:function(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage:function(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr:function(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.timestamp);attr.mtime=new Date(node.timestamp);attr.ctime=new Date(node.timestamp);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr:function(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup:function(parent,name){throw FS.genericErrors[44]},mknod:function(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename:function(old_node,new_dir,new_name){if(FS.isDir(old_node.mode)){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}}delete old_node.parent.contents[old_node.name];old_node.parent.timestamp=Date.now();old_node.name=new_name;new_dir.contents[new_name]=old_node;new_dir.timestamp=old_node.parent.timestamp;old_node.parent=new_dir},unlink:function(parent,name){delete parent.contents[name];parent.timestamp=Date.now()},rmdir:function(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.timestamp=Date.now()},readdir:function(node){var entries=[".",".."];for(var key in node.contents){if(!node.contents.hasOwnProperty(key)){continue}entries.push(key)}return entries},symlink:function(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink:function(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read:function(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length{assert(arrayBuffer,'Loading data file "'+url+'" failed (no arrayBuffer).');onload(new Uint8Array(arrayBuffer));if(dep)removeRunDependency(dep)},event=>{if(onerror){onerror()}else{throw'Loading data file "'+url+'" failed.'}});if(dep)addRunDependency(dep)}var IDBFS={dbs:{},indexedDB:()=>{if(typeof indexedDB!="undefined")return indexedDB;var ret=null;if(typeof window=="object")ret=window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB;assert(ret,"IDBFS used, but indexedDB not supported");return ret},DB_VERSION:21,DB_STORE_NAME:"FILE_DATA",mount:function(mount){return MEMFS.mount.apply(null,arguments)},syncfs:(mount,populate,callback)=>{IDBFS.getLocalSet(mount,(err,local)=>{if(err)return callback(err);IDBFS.getRemoteSet(mount,(err,remote)=>{if(err)return callback(err);var src=populate?remote:local;var dst=populate?local:remote;IDBFS.reconcile(src,dst,callback)})})},quit:()=>{Object.values(IDBFS.dbs).forEach(value=>value.close());IDBFS.dbs={}},getDB:(name,callback)=>{var db=IDBFS.dbs[name];if(db){return callback(null,db)}var req;try{req=IDBFS.indexedDB().open(name,IDBFS.DB_VERSION)}catch(e){return callback(e)}if(!req){return callback("Unable to connect to IndexedDB")}req.onupgradeneeded=e=>{var db=e.target.result;var transaction=e.target.transaction;var fileStore;if(db.objectStoreNames.contains(IDBFS.DB_STORE_NAME)){fileStore=transaction.objectStore(IDBFS.DB_STORE_NAME)}else{fileStore=db.createObjectStore(IDBFS.DB_STORE_NAME)}if(!fileStore.indexNames.contains("timestamp")){fileStore.createIndex("timestamp","timestamp",{unique:false})}};req.onsuccess=()=>{db=req.result;IDBFS.dbs[name]=db;callback(null,db)};req.onerror=e=>{callback(this.error);e.preventDefault()}},getLocalSet:(mount,callback)=>{var entries={};function isRealDir(p){return p!=="."&&p!==".."}function toAbsolute(root){return p=>{return PATH.join2(root,p)}}var check=FS.readdir(mount.mountpoint).filter(isRealDir).map(toAbsolute(mount.mountpoint));while(check.length){var path=check.pop();var stat;try{stat=FS.stat(path)}catch(e){return callback(e)}if(FS.isDir(stat.mode)){check.push.apply(check,FS.readdir(path).filter(isRealDir).map(toAbsolute(path)))}entries[path]={"timestamp":stat.mtime}}return callback(null,{type:"local",entries:entries})},getRemoteSet:(mount,callback)=>{var entries={};IDBFS.getDB(mount.mountpoint,(err,db)=>{if(err)return callback(err);try{var transaction=db.transaction([IDBFS.DB_STORE_NAME],"readonly");transaction.onerror=e=>{callback(this.error);e.preventDefault()};var store=transaction.objectStore(IDBFS.DB_STORE_NAME);var index=store.index("timestamp");index.openKeyCursor().onsuccess=event=>{var cursor=event.target.result;if(!cursor){return callback(null,{type:"remote",db:db,entries:entries})}entries[cursor.primaryKey]={"timestamp":cursor.key};cursor.continue()}}catch(e){return callback(e)}})},loadLocalEntry:(path,callback)=>{var stat,node;try{var lookup=FS.lookupPath(path);node=lookup.node;stat=FS.stat(path)}catch(e){return callback(e)}if(FS.isDir(stat.mode)){return callback(null,{"timestamp":stat.mtime,"mode":stat.mode})}else if(FS.isFile(stat.mode)){node.contents=MEMFS.getFileDataAsTypedArray(node);return callback(null,{"timestamp":stat.mtime,"mode":stat.mode,"contents":node.contents})}else{return callback(new Error("node type not supported"))}},storeLocalEntry:(path,entry,callback)=>{try{if(FS.isDir(entry["mode"])){FS.mkdirTree(path,entry["mode"])}else if(FS.isFile(entry["mode"])){FS.writeFile(path,entry["contents"],{canOwn:true})}else{return callback(new Error("node type not supported"))}FS.chmod(path,entry["mode"]);FS.utime(path,entry["timestamp"],entry["timestamp"])}catch(e){return callback(e)}callback(null)},removeLocalEntry:(path,callback)=>{try{var stat=FS.stat(path);if(FS.isDir(stat.mode)){FS.rmdir(path)}else if(FS.isFile(stat.mode)){FS.unlink(path)}}catch(e){return callback(e)}callback(null)},loadRemoteEntry:(store,path,callback)=>{var req=store.get(path);req.onsuccess=event=>{callback(null,event.target.result)};req.onerror=e=>{callback(this.error);e.preventDefault()}},storeRemoteEntry:(store,path,entry,callback)=>{try{var req=store.put(entry,path)}catch(e){callback(e);return}req.onsuccess=()=>{callback(null)};req.onerror=e=>{callback(this.error);e.preventDefault()}},removeRemoteEntry:(store,path,callback)=>{var req=store.delete(path);req.onsuccess=()=>{callback(null)};req.onerror=e=>{callback(this.error);e.preventDefault()}},reconcile:(src,dst,callback)=>{var total=0;var create=[];Object.keys(src.entries).forEach(function(key){var e=src.entries[key];var e2=dst.entries[key];if(!e2||e["timestamp"].getTime()!=e2["timestamp"].getTime()){create.push(key);total++}});var remove=[];Object.keys(dst.entries).forEach(function(key){if(!src.entries[key]){remove.push(key);total++}});if(!total){return callback(null)}var errored=false;var db=src.type==="remote"?src.db:dst.db;var transaction=db.transaction([IDBFS.DB_STORE_NAME],"readwrite");var store=transaction.objectStore(IDBFS.DB_STORE_NAME);function done(err){if(err&&!errored){errored=true;return callback(err)}}transaction.onerror=e=>{done(this.error);e.preventDefault()};transaction.oncomplete=e=>{if(!errored){callback(null)}};create.sort().forEach(path=>{if(dst.type==="local"){IDBFS.loadRemoteEntry(store,path,(err,entry)=>{if(err)return done(err);IDBFS.storeLocalEntry(path,entry,done)})}else{IDBFS.loadLocalEntry(path,(err,entry)=>{if(err)return done(err);IDBFS.storeRemoteEntry(store,path,entry,done)})}});remove.sort().reverse().forEach(path=>{if(dst.type==="local"){IDBFS.removeLocalEntry(path,done)}else{IDBFS.removeRemoteEntry(store,path,done)}})}};var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,ErrnoError:null,genericErrors:{},filesystems:null,syncFSRequests:0,lookupPath:(path,opts={})=>{path=PATH_FS.resolve(path);if(!path)return{path:"",node:null};var defaults={follow_mount:true,recurse_count:0};opts=Object.assign(defaults,opts);if(opts.recurse_count>8){throw new FS.ErrnoError(32)}var parts=path.split("/").filter(p=>!!p);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:node=>{var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?mount+"/"+path:mount+path}path=path?node.name+"/"+path:node.name;node=node.parent}},hashName:(parentid,name)=>{var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode:node=>{var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:node=>{var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:(parent,name)=>{var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:(parent,name,mode,rdev)=>{var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:node=>{FS.hashRemoveNode(node)},isRoot:node=>{return node===node.parent},isMountpoint:node=>{return!!node.mounted},isFile:mode=>{return(mode&61440)===32768},isDir:mode=>{return(mode&61440)===16384},isLink:mode=>{return(mode&61440)===40960},isChrdev:mode=>{return(mode&61440)===8192},isBlkdev:mode=>{return(mode&61440)===24576},isFIFO:mode=>{return(mode&61440)===4096},isSocket:mode=>{return(mode&49152)===49152},flagModes:{"r":0,"r+":2,"w":577,"w+":578,"a":1089,"a+":1090},modeStringToFlags:str=>{var flags=FS.flagModes[str];if(typeof flags=="undefined"){throw new Error("Unknown file open mode: "+str)}return flags},flagsToPermissionString:flag=>{var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:(node,perms)=>{if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup:dir=>{var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:(dir,name)=>{try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:(dir,name,isdir)=>{var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:(node,flags)=>{if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:(fd_start=0,fd_end=FS.MAX_OPEN_FDS)=>{for(var fd=fd_start;fd<=fd_end;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:fd=>FS.streams[fd],createStream:(stream,fd_start,fd_end)=>{if(!FS.FSStream){FS.FSStream=function(){this.shared={}};FS.FSStream.prototype={};Object.defineProperties(FS.FSStream.prototype,{object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}},flags:{get:function(){return this.shared.flags},set:function(val){this.shared.flags=val}},position:{get:function(){return this.shared.position},set:function(val){this.shared.position=val}}})}stream=Object.assign(new FS.FSStream,stream);var fd=FS.nextfd(fd_start,fd_end);stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:fd=>{FS.streams[fd]=null},chrdev_stream_ops:{open:stream=>{var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:()=>{throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice:(dev,ops)=>{FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts:mount=>{var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:(populate,callback)=>{if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err("warning: "+FS.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work")}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(mount=>{if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:(type,opts,mountpoint)=>{var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:mountpoint=>{var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(hash=>{var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:(parent,name)=>{return parent.node_ops.lookup(parent,name)},mknod:(path,mode,dev)=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:(path,mode)=>{mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:(path,mode)=>{mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:(path,mode)=>{var dirs=path.split("/");var d="";for(var i=0;i{if(typeof dev=="undefined"){dev=mode;mode=438}mode|=8192;return FS.mknod(path,mode,dev)},symlink:(oldpath,newpath)=>{if(!PATH_FS.resolve(oldpath)){throw new FS.ErrnoError(44)}var lookup=FS.lookupPath(newpath,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var newname=PATH.basename(newpath);var errCode=FS.mayCreate(parent,newname);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.symlink){throw new FS.ErrnoError(63)}return parent.node_ops.symlink(parent,newname,oldpath)},rename:(old_path,new_path)=>{var old_dirname=PATH.dirname(old_path);var new_dirname=PATH.dirname(new_path);var old_name=PATH.basename(old_path);var new_name=PATH.basename(new_path);var lookup,old_dir,new_dir;lookup=FS.lookupPath(old_path,{parent:true});old_dir=lookup.node;lookup=FS.lookupPath(new_path,{parent:true});new_dir=lookup.node;if(!old_dir||!new_dir)throw new FS.ErrnoError(44);if(old_dir.mount!==new_dir.mount){throw new FS.ErrnoError(75)}var old_node=FS.lookupNode(old_dir,old_name);var relative=PATH_FS.relative(old_path,new_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(28)}relative=PATH_FS.relative(new_path,old_dirname);if(relative.charAt(0)!=="."){throw new FS.ErrnoError(55)}var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(old_node===new_node){return}var isdir=FS.isDir(old_node.mode);var errCode=FS.mayDelete(old_dir,old_name,isdir);if(errCode){throw new FS.ErrnoError(errCode)}errCode=new_node?FS.mayDelete(new_dir,new_name,isdir):FS.mayCreate(new_dir,new_name);if(errCode){throw new FS.ErrnoError(errCode)}if(!old_dir.node_ops.rename){throw new FS.ErrnoError(63)}if(FS.isMountpoint(old_node)||new_node&&FS.isMountpoint(new_node)){throw new FS.ErrnoError(10)}if(new_dir!==old_dir){errCode=FS.nodePermissions(old_dir,"w");if(errCode){throw new FS.ErrnoError(errCode)}}FS.hashRemoveNode(old_node);try{old_dir.node_ops.rename(old_node,new_dir,new_name)}catch(e){throw e}finally{FS.hashAddNode(old_node)}},rmdir:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,true);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.rmdir){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.rmdir(parent,name);FS.destroyNode(node)},readdir:path=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node.node_ops.readdir){throw new FS.ErrnoError(54)}return node.node_ops.readdir(node)},unlink:path=>{var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;if(!parent){throw new FS.ErrnoError(44)}var name=PATH.basename(path);var node=FS.lookupNode(parent,name);var errCode=FS.mayDelete(parent,name,false);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.unlink){throw new FS.ErrnoError(63)}if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}parent.node_ops.unlink(parent,name);FS.destroyNode(node)},readlink:path=>{var lookup=FS.lookupPath(path);var link=lookup.node;if(!link){throw new FS.ErrnoError(44)}if(!link.node_ops.readlink){throw new FS.ErrnoError(28)}return PATH_FS.resolve(FS.getPath(link.parent),link.node_ops.readlink(link))},stat:(path,dontFollow)=>{var lookup=FS.lookupPath(path,{follow:!dontFollow});var node=lookup.node;if(!node){throw new FS.ErrnoError(44)}if(!node.node_ops.getattr){throw new FS.ErrnoError(63)}return node.node_ops.getattr(node)},lstat:path=>{return FS.stat(path,true)},chmod:(path,mode,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{mode:mode&4095|node.mode&~4095,timestamp:Date.now()})},lchmod:(path,mode)=>{FS.chmod(path,mode,true)},fchmod:(fd,mode)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chmod(stream.node,mode)},chown:(path,uid,gid,dontFollow)=>{var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:!dontFollow});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}node.node_ops.setattr(node,{timestamp:Date.now()})},lchown:(path,uid,gid)=>{FS.chown(path,uid,gid,true)},fchown:(fd,uid,gid)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}FS.chown(stream.node,uid,gid)},truncate:(path,len)=>{if(len<0){throw new FS.ErrnoError(28)}var node;if(typeof path=="string"){var lookup=FS.lookupPath(path,{follow:true});node=lookup.node}else{node=path}if(!node.node_ops.setattr){throw new FS.ErrnoError(63)}if(FS.isDir(node.mode)){throw new FS.ErrnoError(31)}if(!FS.isFile(node.mode)){throw new FS.ErrnoError(28)}var errCode=FS.nodePermissions(node,"w");if(errCode){throw new FS.ErrnoError(errCode)}node.node_ops.setattr(node,{size:len,timestamp:Date.now()})},ftruncate:(fd,len)=>{var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(28)}FS.truncate(stream.node,len)},utime:(path,atime,mtime)=>{var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;node.node_ops.setattr(node,{timestamp:Math.max(atime,mtime)})},open:(path,flags,mode)=>{if(path===""){throw new FS.ErrnoError(44)}flags=typeof flags=="string"?FS.modeStringToFlags(flags):flags;mode=typeof mode=="undefined"?438:mode;if(flags&64){mode=mode&4095|32768}else{mode=0}var node;if(typeof path=="object"){node=path}else{path=PATH.normalize(path);try{var lookup=FS.lookupPath(path,{follow:!(flags&131072)});node=lookup.node}catch(e){}}var created=false;if(flags&64){if(node){if(flags&128){throw new FS.ErrnoError(20)}}else{node=FS.mknod(path,mode,0);created=true}}if(!node){throw new FS.ErrnoError(44)}if(FS.isChrdev(node.mode)){flags&=~512}if(flags&65536&&!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}if(!created){var errCode=FS.mayOpen(node,flags);if(errCode){throw new FS.ErrnoError(errCode)}}if(flags&512&&!created){FS.truncate(node,0)}flags&=~(128|512|131072);var stream=FS.createStream({node:node,path:FS.getPath(node),flags:flags,seekable:true,position:0,stream_ops:node.stream_ops,ungotten:[],error:false});if(stream.stream_ops.open){stream.stream_ops.open(stream)}if(Module["logReadFiles"]&&!(flags&1)){if(!FS.readFiles)FS.readFiles={};if(!(path in FS.readFiles)){FS.readFiles[path]=1}}return stream},close:stream=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(stream.getdents)stream.getdents=null;try{if(stream.stream_ops.close){stream.stream_ops.close(stream)}}catch(e){throw e}finally{FS.closeStream(stream.fd)}stream.fd=null},isClosed:stream=>{return stream.fd===null},llseek:(stream,offset,whence)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(!stream.seekable||!stream.stream_ops.llseek){throw new FS.ErrnoError(70)}if(whence!=0&&whence!=1&&whence!=2){throw new FS.ErrnoError(28)}stream.position=stream.stream_ops.llseek(stream,offset,whence);stream.ungotten=[];return stream.position},read:(stream,buffer,offset,length,position)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.read){throw new FS.ErrnoError(28)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesRead=stream.stream_ops.read(stream,buffer,offset,length,position);if(!seeking)stream.position+=bytesRead;return bytesRead},write:(stream,buffer,offset,length,position,canOwn)=>{if(length<0||position<0){throw new FS.ErrnoError(28)}if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(FS.isDir(stream.node.mode)){throw new FS.ErrnoError(31)}if(!stream.stream_ops.write){throw new FS.ErrnoError(28)}if(stream.seekable&&stream.flags&1024){FS.llseek(stream,0,2)}var seeking=typeof position!="undefined";if(!seeking){position=stream.position}else if(!stream.seekable){throw new FS.ErrnoError(70)}var bytesWritten=stream.stream_ops.write(stream,buffer,offset,length,position,canOwn);if(!seeking)stream.position+=bytesWritten;return bytesWritten},allocate:(stream,offset,length)=>{if(FS.isClosed(stream)){throw new FS.ErrnoError(8)}if(offset<0||length<=0){throw new FS.ErrnoError(28)}if((stream.flags&2097155)===0){throw new FS.ErrnoError(8)}if(!FS.isFile(stream.node.mode)&&!FS.isDir(stream.node.mode)){throw new FS.ErrnoError(43)}if(!stream.stream_ops.allocate){throw new FS.ErrnoError(138)}stream.stream_ops.allocate(stream,offset,length)},mmap:(stream,length,position,prot,flags)=>{if((prot&2)!==0&&(flags&2)===0&&(stream.flags&2097155)!==2){throw new FS.ErrnoError(2)}if((stream.flags&2097155)===1){throw new FS.ErrnoError(2)}if(!stream.stream_ops.mmap){throw new FS.ErrnoError(43)}return stream.stream_ops.mmap(stream,length,position,prot,flags)},msync:(stream,buffer,offset,length,mmapFlags)=>{if(!stream.stream_ops.msync){return 0}return stream.stream_ops.msync(stream,buffer,offset,length,mmapFlags)},munmap:stream=>0,ioctl:(stream,cmd,arg)=>{if(!stream.stream_ops.ioctl){throw new FS.ErrnoError(59)}return stream.stream_ops.ioctl(stream,cmd,arg)},readFile:(path,opts={})=>{opts.flags=opts.flags||0;opts.encoding=opts.encoding||"binary";if(opts.encoding!=="utf8"&&opts.encoding!=="binary"){throw new Error('Invalid encoding type "'+opts.encoding+'"')}var ret;var stream=FS.open(path,opts.flags);var stat=FS.stat(path);var length=stat.size;var buf=new Uint8Array(length);FS.read(stream,buf,0,length,0);if(opts.encoding==="utf8"){ret=UTF8ArrayToString(buf,0)}else if(opts.encoding==="binary"){ret=buf}FS.close(stream);return ret},writeFile:(path,data,opts={})=>{opts.flags=opts.flags||577;var stream=FS.open(path,opts.flags,opts.mode);if(typeof data=="string"){var buf=new Uint8Array(lengthBytesUTF8(data)+1);var actualNumBytes=stringToUTF8Array(data,buf,0,buf.length);FS.write(stream,buf,0,actualNumBytes,undefined,opts.canOwn)}else if(ArrayBuffer.isView(data)){FS.write(stream,data,0,data.byteLength,undefined,opts.canOwn)}else{throw new Error("Unsupported data type")}FS.close(stream)},cwd:()=>FS.currentPath,chdir:path=>{var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories:()=>{FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices:()=>{FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var random_device=getRandomDevice();FS.createDevice("/dev","random",random_device);FS.createDevice("/dev","urandom",random_device);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories:()=>{FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount:()=>{var node=FS.createNode(proc_self,"fd",16384|511,73);node.node_ops={lookup:(parent,name)=>{var fd=+name;var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path}};ret.parent=ret;return ret}};return node}},{},"/proc/self/fd")},createStandardStreams:()=>{if(Module["stdin"]){FS.createDevice("/dev","stdin",Module["stdin"])}else{FS.symlink("/dev/tty","/dev/stdin")}if(Module["stdout"]){FS.createDevice("/dev","stdout",null,Module["stdout"])}else{FS.symlink("/dev/tty","/dev/stdout")}if(Module["stderr"]){FS.createDevice("/dev","stderr",null,Module["stderr"])}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},ensureErrnoError:()=>{if(FS.ErrnoError)return;FS.ErrnoError=function ErrnoError(errno,node){this.node=node;this.setErrno=function(errno){this.errno=errno};this.setErrno(errno);this.message="FS error"};FS.ErrnoError.prototype=new Error;FS.ErrnoError.prototype.constructor=FS.ErrnoError;[44].forEach(code=>{FS.genericErrors[code]=new FS.ErrnoError(code);FS.genericErrors[code].stack=""})},staticInit:()=>{FS.ensureErrnoError();FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={"MEMFS":MEMFS,"IDBFS":IDBFS}},init:(input,output,error)=>{FS.init.initialized=true;FS.ensureErrnoError();Module["stdin"]=input||Module["stdin"];Module["stdout"]=output||Module["stdout"];Module["stderr"]=error||Module["stderr"];FS.createStandardStreams()},quit:()=>{FS.init.initialized=false;_fflush(0);for(var i=0;i{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode},findObject:(path,dontResolveLastLink)=>{var ret=FS.analyzePath(path,dontResolveLastLink);if(!ret.exists){return null}return ret.object},analyzePath:(path,dontResolveLastLink)=>{try{var lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});path=lookup.path}catch(e){}var ret={isRoot:false,exists:false,error:0,name:null,path:null,object:null,parentExists:false,parentPath:null,parentObject:null};try{var lookup=FS.lookupPath(path,{parent:true});ret.parentExists=true;ret.parentPath=lookup.path;ret.parentObject=lookup.node;ret.name=PATH.basename(path);lookup=FS.lookupPath(path,{follow:!dontResolveLastLink});ret.exists=true;ret.path=lookup.path;ret.object=lookup.node;ret.name=lookup.node.name;ret.isRoot=lookup.path==="/"}catch(e){ret.error=e.errno}return ret},createPath:(parent,path,canRead,canWrite)=>{parent=typeof parent=="string"?parent:FS.getPath(parent);var parts=path.split("/").reverse();while(parts.length){var part=parts.pop();if(!part)continue;var current=PATH.join2(parent,part);try{FS.mkdir(current)}catch(e){}parent=current}return current},createFile:(parent,name,properties,canRead,canWrite)=>{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(canRead,canWrite);return FS.create(path,mode)},createDataFile:(parent,name,data,canRead,canWrite,canOwn)=>{var path=name;if(parent){parent=typeof parent=="string"?parent:FS.getPath(parent);path=name?PATH.join2(parent,name):parent}var mode=FS.getMode(canRead,canWrite);var node=FS.create(path,mode);if(data){if(typeof data=="string"){var arr=new Array(data.length);for(var i=0,len=data.length;i{var path=PATH.join2(typeof parent=="string"?parent:FS.getPath(parent),name);var mode=FS.getMode(!!input,!!output);if(!FS.createDevice.major)FS.createDevice.major=64;var dev=FS.makedev(FS.createDevice.major++,0);FS.registerDevice(dev,{open:stream=>{stream.seekable=false},close:stream=>{if(output&&output.buffer&&output.buffer.length){output(10)}},read:(stream,buffer,offset,length,pos)=>{var bytesRead=0;for(var i=0;i{for(var i=0;i{if(obj.isDevice||obj.isFolder||obj.link||obj.contents)return true;if(typeof XMLHttpRequest!="undefined"){throw new Error("Lazy loading should have been performed (contents set) in createLazyFile, but it was not. Lazy loading only works in web workers. Use --embed-file or --preload-file in emcc on the main thread.")}else if(read_){try{obj.contents=intArrayFromString(read_(obj.url),true);obj.usedBytes=obj.contents.length}catch(e){throw new FS.ErrnoError(29)}}else{throw new Error("Cannot load without read() or XMLHttpRequest.")}},createLazyFile:(parent,name,url,canRead,canWrite)=>{function LazyUint8Array(){this.lengthKnown=false;this.chunks=[]}LazyUint8Array.prototype.get=function LazyUint8Array_get(idx){if(idx>this.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(key=>{var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr:ptr,allocated:true}};node.stream_ops=stream_ops;return node},createPreloadedFile:(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency("cp "+fullname);function processData(byteArray){function finish(byteArray){if(preFinish)preFinish();if(!dontCreateFile){FS.createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}if(onload)onload();removeRunDependency(dep)}if(Browser.handledByPreloadPlugin(byteArray,fullname,finish,()=>{if(onerror)onerror();removeRunDependency(dep)})){return}finish(byteArray)}addRunDependency(dep);if(typeof url=="string"){asyncLoad(url,byteArray=>processData(byteArray),onerror)}else{processData(url)}},indexedDB:()=>{return window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB},DB_NAME:()=>{return"EM_FS_"+window.location.pathname},DB_VERSION:20,DB_STORE_NAME:"FILE_DATA",saveFilesToDB:(paths,onload,onerror)=>{onload=onload||(()=>{});onerror=onerror||(()=>{});var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=()=>{out("creating db");var db=openRequest.result;db.createObjectStore(FS.DB_STORE_NAME)};openRequest.onsuccess=()=>{var db=openRequest.result;var transaction=db.transaction([FS.DB_STORE_NAME],"readwrite");var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(path=>{var putRequest=files.put(FS.analyzePath(path).object.contents,path);putRequest.onsuccess=()=>{ok++;if(ok+fail==total)finish()};putRequest.onerror=()=>{fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror},loadFilesFromDB:(paths,onload,onerror)=>{onload=onload||(()=>{});onerror=onerror||(()=>{});var indexedDB=FS.indexedDB();try{var openRequest=indexedDB.open(FS.DB_NAME(),FS.DB_VERSION)}catch(e){return onerror(e)}openRequest.onupgradeneeded=onerror;openRequest.onsuccess=()=>{var db=openRequest.result;try{var transaction=db.transaction([FS.DB_STORE_NAME],"readonly")}catch(e){onerror(e);return}var files=transaction.objectStore(FS.DB_STORE_NAME);var ok=0,fail=0,total=paths.length;function finish(){if(fail==0)onload();else onerror()}paths.forEach(path=>{var getRequest=files.get(path);getRequest.onsuccess=()=>{if(FS.analyzePath(path).exists){FS.unlink(path)}FS.createDataFile(PATH.dirname(path),PATH.basename(path),getRequest.result,true,true,true);ok++;if(ok+fail==total)finish()};getRequest.onerror=()=>{fail++;if(ok+fail==total)finish()}});transaction.onerror=onerror};openRequest.onerror=onerror}};var SYSCALLS={DEFAULT_POLLMASK:5,calculateAt:function(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return PATH.join2(dir,path)},doStat:function(func,path,buf){try{var stat=func(path)}catch(e){if(e&&e.node&&PATH.normalize(path)!==PATH.normalize(FS.getPath(e.node))){return-54}throw e}HEAP32[buf>>2]=stat.dev;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAPU32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;tempI64=[Math.floor(stat.atime.getTime()/1e3)>>>0,(tempDouble=Math.floor(stat.atime.getTime()/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+56>>2]=tempI64[0],HEAP32[buf+60>>2]=tempI64[1];HEAPU32[buf+64>>2]=0;tempI64=[Math.floor(stat.mtime.getTime()/1e3)>>>0,(tempDouble=Math.floor(stat.mtime.getTime()/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+72>>2]=tempI64[0],HEAP32[buf+76>>2]=tempI64[1];HEAPU32[buf+80>>2]=0;tempI64=[Math.floor(stat.ctime.getTime()/1e3)>>>0,(tempDouble=Math.floor(stat.ctime.getTime()/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+88>>2]=tempI64[0],HEAP32[buf+92>>2]=tempI64[1];HEAPU32[buf+96>>2]=0;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+104>>2]=tempI64[0],HEAP32[buf+108>>2]=tempI64[1];return 0},doMsync:function(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream}};function ___syscall__newselect(nfds,readfds,writefds,exceptfds,timeout){try{var total=0;var srcReadLow=readfds?HEAP32[readfds>>2]:0,srcReadHigh=readfds?HEAP32[readfds+4>>2]:0;var srcWriteLow=writefds?HEAP32[writefds>>2]:0,srcWriteHigh=writefds?HEAP32[writefds+4>>2]:0;var srcExceptLow=exceptfds?HEAP32[exceptfds>>2]:0,srcExceptHigh=exceptfds?HEAP32[exceptfds+4>>2]:0;var dstReadLow=0,dstReadHigh=0;var dstWriteLow=0,dstWriteHigh=0;var dstExceptLow=0,dstExceptHigh=0;var allLow=(readfds?HEAP32[readfds>>2]:0)|(writefds?HEAP32[writefds>>2]:0)|(exceptfds?HEAP32[exceptfds>>2]:0);var allHigh=(readfds?HEAP32[readfds+4>>2]:0)|(writefds?HEAP32[writefds+4>>2]:0)|(exceptfds?HEAP32[exceptfds+4>>2]:0);var check=function(fd,low,high,val){return fd<32?low&val:high&val};for(var fd=0;fd>2]=dstReadLow;HEAP32[readfds+4>>2]=dstReadHigh}if(writefds){HEAP32[writefds>>2]=dstWriteLow;HEAP32[writefds+4>>2]=dstWriteHigh}if(exceptfds){HEAP32[exceptfds>>2]=dstExceptLow;HEAP32[exceptfds+4>>2]=dstExceptHigh}return total}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}var SOCKFS={mount:function(mount){Module["websocket"]=Module["websocket"]&&"object"===typeof Module["websocket"]?Module["websocket"]:{};Module["websocket"]._callbacks={};Module["websocket"]["on"]=function(event,callback){if("function"===typeof callback){this._callbacks[event]=callback}return this};Module["websocket"].emit=function(event,param){if("function"===typeof this._callbacks[event]){this._callbacks[event].call(this,param)}};return FS.createNode(null,"/",16384|511,0)},createSocket:function(family,type,protocol){type&=~526336;var streaming=type==1;if(streaming&&protocol&&protocol!=6){throw new FS.ErrnoError(66)}var sock={family:family,type:type,protocol:protocol,server:null,error:null,peers:{},pending:[],recv_queue:[],sock_ops:SOCKFS.websocket_sock_ops};var name=SOCKFS.nextname();var node=FS.createNode(SOCKFS.root,name,49152,0);node.sock=sock;var stream=FS.createStream({path:name,node:node,flags:2,seekable:false,stream_ops:SOCKFS.stream_ops});sock.stream=stream;return sock},getSocket:function(fd){var stream=FS.getStream(fd);if(!stream||!FS.isSocket(stream.node.mode)){return null}return stream.node.sock},stream_ops:{poll:function(stream){var sock=stream.node.sock;return sock.sock_ops.poll(sock)},ioctl:function(stream,request,varargs){var sock=stream.node.sock;return sock.sock_ops.ioctl(sock,request,varargs)},read:function(stream,buffer,offset,length,position){var sock=stream.node.sock;var msg=sock.sock_ops.recvmsg(sock,length);if(!msg){return 0}buffer.set(msg.buffer,offset);return msg.buffer.length},write:function(stream,buffer,offset,length,position){var sock=stream.node.sock;return sock.sock_ops.sendmsg(sock,buffer,offset,length)},close:function(stream){var sock=stream.node.sock;sock.sock_ops.close(sock)}},nextname:function(){if(!SOCKFS.nextname.current){SOCKFS.nextname.current=0}return"socket["+SOCKFS.nextname.current+++"]"},websocket_sock_ops:{createPeer:function(sock,addr,port){var ws;if(typeof addr=="object"){ws=addr;addr=null;port=null}if(ws){if(ws._socket){addr=ws._socket.remoteAddress;port=ws._socket.remotePort}else{var result=/ws[s]?:\/\/([^:]+):(\d+)/.exec(ws.url);if(!result){throw new Error("WebSocket URL must be in the format ws(s)://address:port")}addr=result[1];port=parseInt(result[2],10)}}else{try{var runtimeConfig=Module["websocket"]&&"object"===typeof Module["websocket"];var url="ws:#".replace("#","//");if(runtimeConfig){if("string"===typeof Module["websocket"]["url"]){url=Module["websocket"]["url"]}}if(url==="ws://"||url==="wss://"){var parts=addr.split("/");url=url+parts[0]+":"+port+"/"+parts.slice(1).join("/")}var subProtocols="binary";if(runtimeConfig){if("string"===typeof Module["websocket"]["subprotocol"]){subProtocols=Module["websocket"]["subprotocol"]}}var opts=undefined;if(subProtocols!=="null"){subProtocols=subProtocols.replace(/^ +| +$/g,"").split(/ *, */);opts=subProtocols}if(runtimeConfig&&null===Module["websocket"]["subprotocol"]){subProtocols="null";opts=undefined}var WebSocketConstructor;{WebSocketConstructor=WebSocket}ws=new WebSocketConstructor(url,opts);ws.binaryType="arraybuffer"}catch(e){throw new FS.ErrnoError(23)}}var peer={addr:addr,port:port,socket:ws,dgram_send_queue:[]};SOCKFS.websocket_sock_ops.addPeer(sock,peer);SOCKFS.websocket_sock_ops.handlePeerEvents(sock,peer);if(sock.type===2&&typeof sock.sport!="undefined"){peer.dgram_send_queue.push(new Uint8Array([255,255,255,255,"p".charCodeAt(0),"o".charCodeAt(0),"r".charCodeAt(0),"t".charCodeAt(0),(sock.sport&65280)>>8,sock.sport&255]))}return peer},getPeer:function(sock,addr,port){return sock.peers[addr+":"+port]},addPeer:function(sock,peer){sock.peers[peer.addr+":"+peer.port]=peer},removePeer:function(sock,peer){delete sock.peers[peer.addr+":"+peer.port]},handlePeerEvents:function(sock,peer){var first=true;var handleOpen=function(){Module["websocket"].emit("open",sock.stream.fd);try{var queued=peer.dgram_send_queue.shift();while(queued){peer.socket.send(queued);queued=peer.dgram_send_queue.shift()}}catch(e){peer.socket.close()}};function handleMessage(data){if(typeof data=="string"){var encoder=new TextEncoder;data=encoder.encode(data)}else{assert(data.byteLength!==undefined);if(data.byteLength==0){return}data=new Uint8Array(data)}var wasfirst=first;first=false;if(wasfirst&&data.length===10&&data[0]===255&&data[1]===255&&data[2]===255&&data[3]===255&&data[4]==="p".charCodeAt(0)&&data[5]==="o".charCodeAt(0)&&data[6]==="r".charCodeAt(0)&&data[7]==="t".charCodeAt(0)){var newport=data[8]<<8|data[9];SOCKFS.websocket_sock_ops.removePeer(sock,peer);peer.port=newport;SOCKFS.websocket_sock_ops.addPeer(sock,peer);return}sock.recv_queue.push({addr:peer.addr,port:peer.port,data:data});Module["websocket"].emit("message",sock.stream.fd)}if(ENVIRONMENT_IS_NODE){peer.socket.on("open",handleOpen);peer.socket.on("message",function(data,isBinary){if(!isBinary){return}handleMessage(new Uint8Array(data).buffer)});peer.socket.on("close",function(){Module["websocket"].emit("close",sock.stream.fd)});peer.socket.on("error",function(error){sock.error=14;Module["websocket"].emit("error",[sock.stream.fd,sock.error,"ECONNREFUSED: Connection refused"])})}else{peer.socket.onopen=handleOpen;peer.socket.onclose=function(){Module["websocket"].emit("close",sock.stream.fd)};peer.socket.onmessage=function peer_socket_onmessage(event){handleMessage(event.data)};peer.socket.onerror=function(error){sock.error=14;Module["websocket"].emit("error",[sock.stream.fd,sock.error,"ECONNREFUSED: Connection refused"])}}},poll:function(sock){if(sock.type===1&&sock.server){return sock.pending.length?64|1:0}var mask=0;var dest=sock.type===1?SOCKFS.websocket_sock_ops.getPeer(sock,sock.daddr,sock.dport):null;if(sock.recv_queue.length||!dest||dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED){mask|=64|1}if(!dest||dest&&dest.socket.readyState===dest.socket.OPEN){mask|=4}if(dest&&dest.socket.readyState===dest.socket.CLOSING||dest&&dest.socket.readyState===dest.socket.CLOSED){mask|=16}return mask},ioctl:function(sock,request,arg){switch(request){case 21531:var bytes=0;if(sock.recv_queue.length){bytes=sock.recv_queue[0].data.length}HEAP32[arg>>2]=bytes;return 0;default:return 28}},close:function(sock){if(sock.server){try{sock.server.close()}catch(e){}sock.server=null}var peers=Object.keys(sock.peers);for(var i=0;i>2]=value;return value}function inetPton4(str){var b=str.split(".");for(var i=0;i<4;i++){var tmp=Number(b[i]);if(isNaN(tmp))return null;b[i]=tmp}return(b[0]|b[1]<<8|b[2]<<16|b[3]<<24)>>>0}function jstoi_q(str){return parseInt(str)}function inetPton6(str){var words;var w,offset,z;var valid6regx=/^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i;var parts=[];if(!valid6regx.test(str)){return null}if(str==="::"){return[0,0,0,0,0,0,0,0]}if(str.startsWith("::")){str=str.replace("::","Z:")}else{str=str.replace("::",":Z:")}if(str.indexOf(".")>0){str=str.replace(new RegExp("[.]","g"),":");words=str.split(":");words[words.length-4]=jstoi_q(words[words.length-4])+jstoi_q(words[words.length-3])*256;words[words.length-3]=jstoi_q(words[words.length-2])+jstoi_q(words[words.length-1])*256;words=words.slice(0,words.length-2)}else{words=str.split(":")}offset=0;z=0;for(w=0;w>2]=16}HEAP16[sa>>1]=family;HEAP32[sa+4>>2]=addr;HEAP16[sa+2>>1]=_htons(port);break;case 10:addr=inetPton6(addr);zeroMemory(sa,28);if(addrlen){HEAP32[addrlen>>2]=28}HEAP32[sa>>2]=family;HEAP32[sa+8>>2]=addr[0];HEAP32[sa+12>>2]=addr[1];HEAP32[sa+16>>2]=addr[2];HEAP32[sa+20>>2]=addr[3];HEAP16[sa+2>>1]=_htons(port);break;default:return 5}return 0}var DNS={address_map:{id:1,addrs:{},names:{}},lookup_name:function(name){var res=inetPton4(name);if(res!==null){return name}res=inetPton6(name);if(res!==null){return name}var addr;if(DNS.address_map.addrs[name]){addr=DNS.address_map.addrs[name]}else{var id=DNS.address_map.id++;assert(id<65535,"exceeded max address mappings of 65535");addr="172.29."+(id&255)+"."+(id&65280);DNS.address_map.names[addr]=name;DNS.address_map.addrs[name]=addr}return addr},lookup_addr:function(addr){if(DNS.address_map.names[addr]){return DNS.address_map.names[addr]}return null}};function ___syscall_accept4(fd,addr,addrlen,flags){try{var sock=getSocketFromFD(fd);var newsock=sock.sock_ops.accept(sock);if(addr){var errno=writeSockaddr(addr,newsock.family,DNS.lookup_name(newsock.daddr),newsock.dport,addrlen)}return newsock.stream.fd}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function inetNtop4(addr){return(addr&255)+"."+(addr>>8&255)+"."+(addr>>16&255)+"."+(addr>>24&255)}function inetNtop6(ints){var str="";var word=0;var longest=0;var lastzero=0;var zstart=0;var len=0;var i=0;var parts=[ints[0]&65535,ints[0]>>16,ints[1]&65535,ints[1]>>16,ints[2]&65535,ints[2]>>16,ints[3]&65535,ints[3]>>16];var hasipv4=true;var v4part="";for(i=0;i<5;i++){if(parts[i]!==0){hasipv4=false;break}}if(hasipv4){v4part=inetNtop4(parts[6]|parts[7]<<16);if(parts[5]===-1){str="::ffff:";str+=v4part;return str}if(parts[5]===0){str="::";if(v4part==="0.0.0.0")v4part="";if(v4part==="0.0.0.1")v4part="1";str+=v4part;return str}}for(word=0;word<8;word++){if(parts[word]===0){if(word-lastzero>1){len=0}lastzero=word;len++}if(len>longest){longest=len;zstart=word-longest+1}}for(word=0;word<8;word++){if(longest>1){if(parts[word]===0&&word>=zstart&&word>1];var port=_ntohs(HEAPU16[sa+2>>1]);var addr;switch(family){case 2:if(salen!==16){return{errno:28}}addr=HEAP32[sa+4>>2];addr=inetNtop4(addr);break;case 10:if(salen!==28){return{errno:28}}addr=[HEAP32[sa+8>>2],HEAP32[sa+12>>2],HEAP32[sa+16>>2],HEAP32[sa+20>>2]];addr=inetNtop6(addr);break;default:return{errno:5}}return{family:family,addr:addr,port:port}}function getSocketAddress(addrp,addrlen,allowNull){if(allowNull&&addrp===0)return null;var info=readSockaddr(addrp,addrlen);if(info.errno)throw new FS.ErrnoError(info.errno);info.addr=DNS.lookup_addr(info.addr)||info.addr;return info}function ___syscall_bind(fd,addr,addrlen){try{var sock=getSocketFromFD(fd);var info=getSocketAddress(addr,addrlen);sock.sock_ops.bind(sock,info.addr,info.port);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_chdir(path){try{path=SYSCALLS.getStr(path);FS.chdir(path);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_chmod(path,mode){try{path=SYSCALLS.getStr(path);FS.chmod(path,mode);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_connect(fd,addr,addrlen){try{var sock=getSocketFromFD(fd);var info=getSocketAddress(addr,addrlen);sock.sock_ops.connect(sock,info.addr,info.port);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_faccessat(dirfd,path,amode,flags){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(amode&~7){return-28}var lookup=FS.lookupPath(path,{follow:true});var node=lookup.node;if(!node){return-44}var perms="";if(amode&4)perms+="r";if(amode&2)perms+="w";if(amode&1)perms+="x";if(perms&&FS.nodePermissions(node,perms)){return-2}return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_fchmod(fd,mode){try{FS.fchmod(fd,mode);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}var newStream;newStream=FS.createStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 5:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 6:case 7:return 0;case 16:case 8:return-28;case 9:setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function convertI32PairToI53Checked(lo,hi){return hi+2097152>>>0<4194305-!!lo?(lo>>>0)+hi*4294967296:NaN}function ___syscall_ftruncate64(fd,length_low,length_high){try{var length=convertI32PairToI53Checked(length_low,length_high);if(isNaN(length))return-61;FS.ftruncate(fd,length);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_getcwd(buf,size){try{if(size===0)return-28;var cwd=FS.cwd();var cwdLengthInBytes=lengthBytesUTF8(cwd)+1;if(size>>0,(tempDouble=id,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[dirp+pos>>2]=tempI64[0],HEAP32[dirp+pos+4>>2]=tempI64[1];tempI64=[(idx+1)*struct_size>>>0,(tempDouble=(idx+1)*struct_size,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[dirp+pos+8>>2]=tempI64[0],HEAP32[dirp+pos+12>>2]=tempI64[1];HEAP16[dirp+pos+16>>1]=280;HEAP8[dirp+pos+18>>0]=type;stringToUTF8(name,dirp+pos+19,256);pos+=struct_size;idx+=1}FS.llseek(stream,idx*struct_size,0);return pos}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_getsockname(fd,addr,addrlen){try{err("__syscall_getsockname "+fd);var sock=getSocketFromFD(fd);var errno=writeSockaddr(addr,sock.family,DNS.lookup_name(sock.saddr||"0.0.0.0"),sock.sport,addrlen);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_getsockopt(fd,level,optname,optval,optlen){try{var sock=getSocketFromFD(fd);if(level===1){if(optname===4){HEAP32[optval>>2]=sock.error;HEAP32[optlen>>2]=4;sock.error=null;return 0}}return-50}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:case 21505:{if(!stream.tty)return-59;return 0}case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:{if(!stream.tty)return-59;return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.get();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.get();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;return 0}case 21524:{if(!stream.tty)return-59;return 0}default:return-28}}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_listen(fd,backlog){try{var sock=getSocketFromFD(fd);sock.sock_ops.listen(sock,backlog);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_lstat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.lstat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_mkdirat(dirfd,path,mode){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);path=PATH.normalize(path);if(path[path.length-1]==="/")path=path.substr(0,path.length-1);FS.mkdir(path,mode,0);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_mknodat(dirfd,path,mode,dev){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);switch(mode&61440){case 32768:case 8192:case 24576:case 4096:case 49152:break;default:return-28}FS.mknod(path,mode,dev);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_newfstatat(dirfd,path,buf,flags){try{path=SYSCALLS.getStr(path);var nofollow=flags&256;var allowEmpty=flags&4096;flags=flags&~4352;path=SYSCALLS.calculateAt(dirfd,path,allowEmpty);return SYSCALLS.doStat(nofollow?FS.lstat:FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?SYSCALLS.get():0;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_poll(fds,nfds,timeout){try{var nonzero=0;for(var i=0;i>2];var events=HEAP16[pollfd+4>>1];var mask=32;var stream=FS.getStream(fd);if(stream){mask=SYSCALLS.DEFAULT_POLLMASK;if(stream.stream_ops.poll){mask=stream.stream_ops.poll(stream)}}mask&=events|8|16;if(mask)nonzero++;HEAP16[pollfd+6>>1]=mask}return nonzero}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_readlinkat(dirfd,path,buf,bufsize){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(bufsize<=0)return-28;var ret=FS.readlink(path);var len=Math.min(bufsize,lengthBytesUTF8(ret));var endChar=HEAP8[buf+len];stringToUTF8(ret,buf,bufsize+1);HEAP8[buf+len]=endChar;return len}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_recvfrom(fd,buf,len,flags,addr,addrlen){try{var sock=getSocketFromFD(fd);var msg=sock.sock_ops.recvmsg(sock,len);if(!msg)return 0;if(addr){var errno=writeSockaddr(addr,sock.family,DNS.lookup_name(msg.addr),msg.port,addrlen)}HEAPU8.set(msg.buffer,buf);return msg.buffer.byteLength}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_renameat(olddirfd,oldpath,newdirfd,newpath){try{oldpath=SYSCALLS.getStr(oldpath);newpath=SYSCALLS.getStr(newpath);oldpath=SYSCALLS.calculateAt(olddirfd,oldpath);newpath=SYSCALLS.calculateAt(newdirfd,newpath);FS.rename(oldpath,newpath);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_rmdir(path){try{path=SYSCALLS.getStr(path);FS.rmdir(path);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_sendto(fd,message,length,flags,addr,addr_len){try{var sock=getSocketFromFD(fd);var dest=getSocketAddress(addr,addr_len,true);if(!dest){return FS.write(sock.stream,HEAP8,message,length)}return sock.sock_ops.sendmsg(sock,HEAP8,message,length,dest.addr,dest.port)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_socket(domain,type,protocol){try{var sock=SOCKFS.createSocket(domain,type,protocol);return sock.stream.fd}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_statfs64(path,size,buf){try{path=SYSCALLS.getStr(path);HEAP32[buf+4>>2]=4096;HEAP32[buf+40>>2]=4096;HEAP32[buf+8>>2]=1e6;HEAP32[buf+12>>2]=5e5;HEAP32[buf+16>>2]=5e5;HEAP32[buf+20>>2]=FS.nextInode;HEAP32[buf+24>>2]=1e6;HEAP32[buf+28>>2]=42;HEAP32[buf+44>>2]=2;HEAP32[buf+36>>2]=255;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_symlink(target,linkpath){try{target=SYSCALLS.getStr(target);linkpath=SYSCALLS.getStr(linkpath);FS.symlink(target,linkpath);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function ___syscall_unlinkat(dirfd,path,flags){try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);if(flags===0){FS.unlink(path)}else if(flags===512){FS.rmdir(path)}else{abort("Invalid flags passed to unlinkat")}return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return-e.errno}}function __dlinit(main_dso_handle){}var dlopenMissingError="To use dlopen, you need enable dynamic linking, see https://github.com/emscripten-core/emscripten/wiki/Linking";function __dlopen_js(filename,flag){abort(dlopenMissingError)}function __dlsym_js(handle,symbol){abort(dlopenMissingError)}var nowIsMonotonic=true;function __emscripten_get_now_is_monotonic(){return nowIsMonotonic}function readI53FromI64(ptr){return HEAPU32[ptr>>2]+HEAP32[ptr+4>>2]*4294967296}function __gmtime_js(time,tmPtr){var date=new Date(readI53FromI64(time)*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday}function __isLeapYear(year){return year%4===0&&(year%100!==0||year%400===0)}var __MONTH_DAYS_LEAP_CUMULATIVE=[0,31,60,91,121,152,182,213,244,274,305,335];var __MONTH_DAYS_REGULAR_CUMULATIVE=[0,31,59,90,120,151,181,212,243,273,304,334];function __yday_from_date(date){var isLeapYear=__isLeapYear(date.getFullYear());var monthDaysCumulative=isLeapYear?__MONTH_DAYS_LEAP_CUMULATIVE:__MONTH_DAYS_REGULAR_CUMULATIVE;var yday=monthDaysCumulative[date.getMonth()]+date.getDate()-1;return yday}function __localtime_js(time,tmPtr){var date=new Date(readI53FromI64(time)*1e3);HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getDay();var yday=__yday_from_date(date)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+36>>2]=-(date.getTimezoneOffset()*60);var start=new Date(date.getFullYear(),0,1);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dst=(summerOffset!=winterOffset&&date.getTimezoneOffset()==Math.min(winterOffset,summerOffset))|0;HEAP32[tmPtr+32>>2]=dst}function allocateUTF8(str){var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8Array(str,HEAP8,ret,size);return ret}function __tzset_js(timezone,daylight,tzname){var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);var winterOffset=winter.getTimezoneOffset();var summerOffset=summer.getTimezoneOffset();var stdTimezoneOffset=Math.max(winterOffset,summerOffset);HEAPU32[timezone>>2]=stdTimezoneOffset*60;HEAP32[daylight>>2]=Number(winterOffset!=summerOffset);function extractZone(date){var match=date.toTimeString().match(/\(([A-Za-z ]+)\)$/);return match?match[1]:"GMT"}var winterName=extractZone(winter);var summerName=extractZone(summer);var winterNamePtr=allocateUTF8(winterName);var summerNamePtr=allocateUTF8(summerName);if(summerOffset>2]=winterNamePtr;HEAPU32[tzname+4>>2]=summerNamePtr}else{HEAPU32[tzname>>2]=summerNamePtr;HEAPU32[tzname+4>>2]=winterNamePtr}}function _abort(){abort("")}function runtimeKeepalivePush(){runtimeKeepaliveCounter+=1}function _emscripten_set_main_loop_timing(mode,value){Browser.mainLoop.timingMode=mode;Browser.mainLoop.timingValue=value;if(!Browser.mainLoop.func){return 1}if(!Browser.mainLoop.running){runtimeKeepalivePush();Browser.mainLoop.running=true}if(mode==0){Browser.mainLoop.scheduler=function Browser_mainLoop_scheduler_setTimeout(){var timeUntilNextTick=Math.max(0,Browser.mainLoop.tickStartTime+value-_emscripten_get_now())|0;setTimeout(Browser.mainLoop.runner,timeUntilNextTick)};Browser.mainLoop.method="timeout"}else if(mode==1){Browser.mainLoop.scheduler=function Browser_mainLoop_scheduler_rAF(){Browser.requestAnimationFrame(Browser.mainLoop.runner)};Browser.mainLoop.method="rAF"}else if(mode==2){if(typeof setImmediate=="undefined"){var setImmediates=[];var emscriptenMainLoopMessageId="setimmediate";var Browser_setImmediate_messageHandler=event=>{if(event.data===emscriptenMainLoopMessageId||event.data.target===emscriptenMainLoopMessageId){event.stopPropagation();setImmediates.shift()()}};addEventListener("message",Browser_setImmediate_messageHandler,true);setImmediate=function Browser_emulated_setImmediate(func){setImmediates.push(func);if(ENVIRONMENT_IS_WORKER){if(Module["setImmediates"]===undefined)Module["setImmediates"]=[];Module["setImmediates"].push(func);postMessage({target:emscriptenMainLoopMessageId})}else postMessage(emscriptenMainLoopMessageId,"*")}}Browser.mainLoop.scheduler=function Browser_mainLoop_scheduler_setImmediate(){setImmediate(Browser.mainLoop.runner)};Browser.mainLoop.method="immediate"}return 0}var _emscripten_get_now;_emscripten_get_now=()=>performance.now();function _emscripten_webgl_do_commit_frame(){if(!GL.currentContext||!GL.currentContext.GLctx){return-3}if(GL.currentContext.defaultFbo){GL.blitOffscreenFramebuffer(GL.currentContext);return 0}if(!GL.currentContext.attributes.explicitSwapControl){return-3}return 0}var _emscripten_webgl_commit_frame=_emscripten_webgl_do_commit_frame;function _proc_exit(code){EXITSTATUS=code;if(!keepRuntimeAlive()){if(Module["onExit"])Module["onExit"](code);ABORT=true}quit_(code,new ExitStatus(code))}function exitJS(status,implicit){EXITSTATUS=status;if(!keepRuntimeAlive()){exitRuntime()}_proc_exit(status)}var _exit=exitJS;function handleException(e){if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)}function maybeExit(){if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}}function runtimeKeepalivePop(){runtimeKeepaliveCounter-=1}function setMainLoop(browserIterationFunc,fps,simulateInfiniteLoop,arg,noSetTiming){assert(!Browser.mainLoop.func,"emscripten_set_main_loop: there can only be one main loop function at once: call emscripten_cancel_main_loop to cancel the previous one before setting a new one with different parameters.");Browser.mainLoop.func=browserIterationFunc;Browser.mainLoop.arg=arg;var thisMainLoopId=Browser.mainLoop.currentlyRunningMainloop;function checkIsRunning(){if(thisMainLoopId0){var start=Date.now();var blocker=Browser.mainLoop.queue.shift();blocker.func(blocker.arg);if(Browser.mainLoop.remainingBlockers){var remaining=Browser.mainLoop.remainingBlockers;var next=remaining%1==0?remaining-1:Math.floor(remaining);if(blocker.counted){Browser.mainLoop.remainingBlockers=next}else{next=next+.5;Browser.mainLoop.remainingBlockers=(8*remaining+next)/9}}out('main loop blocker "'+blocker.name+'" took '+(Date.now()-start)+" ms");Browser.mainLoop.updateStatus();if(!checkIsRunning())return;setTimeout(Browser.mainLoop.runner,0);return}if(!checkIsRunning())return;Browser.mainLoop.currentFrameNumber=Browser.mainLoop.currentFrameNumber+1|0;if(Browser.mainLoop.timingMode==1&&Browser.mainLoop.timingValue>1&&Browser.mainLoop.currentFrameNumber%Browser.mainLoop.timingValue!=0){Browser.mainLoop.scheduler();return}else if(Browser.mainLoop.timingMode==0){Browser.mainLoop.tickStartTime=_emscripten_get_now()}Browser.mainLoop.runIter(browserIterationFunc);if(!checkIsRunning())return;if(typeof SDL=="object"&&SDL.audio&&SDL.audio.queueNewAudioData)SDL.audio.queueNewAudioData();Browser.mainLoop.scheduler()};if(!noSetTiming){if(fps&&fps>0)_emscripten_set_main_loop_timing(0,1e3/fps);else _emscripten_set_main_loop_timing(1,1);Browser.mainLoop.scheduler()}if(simulateInfiniteLoop){throw"unwind"}}function callUserCallback(func){if(runtimeExited||ABORT){return}try{func();maybeExit()}catch(e){handleException(e)}}function safeSetTimeout(func,timeout){runtimeKeepalivePush();return setTimeout(function(){runtimeKeepalivePop();callUserCallback(func)},timeout)}function warnOnce(text){if(!warnOnce.shown)warnOnce.shown={};if(!warnOnce.shown[text]){warnOnce.shown[text]=1;err(text)}}var Browser={mainLoop:{running:false,scheduler:null,method:"",currentlyRunningMainloop:0,func:null,arg:0,timingMode:0,timingValue:0,currentFrameNumber:0,queue:[],pause:function(){Browser.mainLoop.scheduler=null;Browser.mainLoop.currentlyRunningMainloop++},resume:function(){Browser.mainLoop.currentlyRunningMainloop++;var timingMode=Browser.mainLoop.timingMode;var timingValue=Browser.mainLoop.timingValue;var func=Browser.mainLoop.func;Browser.mainLoop.func=null;setMainLoop(func,0,false,Browser.mainLoop.arg,true);_emscripten_set_main_loop_timing(timingMode,timingValue);Browser.mainLoop.scheduler()},updateStatus:function(){if(Module["setStatus"]){var message=Module["statusMessage"]||"Please wait...";var remaining=Browser.mainLoop.remainingBlockers;var expected=Browser.mainLoop.expectedBlockers;if(remaining){if(remaining{assert(img.complete,"Image "+name+" could not be decoded");var canvas=document.createElement("canvas");canvas.width=img.width;canvas.height=img.height;var ctx=canvas.getContext("2d");ctx.drawImage(img,0,0);preloadedImages[name]=canvas;Browser.URLObject.revokeObjectURL(url);if(onload)onload(byteArray)};img.onerror=event=>{out("Image "+url+" could not be decoded");if(onerror)onerror()};img.src=url};Module["preloadPlugins"].push(imagePlugin);var audioPlugin={};audioPlugin["canHandle"]=function audioPlugin_canHandle(name){return!Module.noAudioDecoding&&name.substr(-4)in{".ogg":1,".wav":1,".mp3":1}};audioPlugin["handle"]=function audioPlugin_handle(byteArray,name,onload,onerror){var done=false;function finish(audio){if(done)return;done=true;preloadedAudios[name]=audio;if(onload)onload(byteArray)}function fail(){if(done)return;done=true;preloadedAudios[name]=new Audio;if(onerror)onerror()}if(Browser.hasBlobConstructor){try{var b=new Blob([byteArray],{type:Browser.getMimetype(name)})}catch(e){return fail()}var url=Browser.URLObject.createObjectURL(b);var audio=new Audio;audio.addEventListener("canplaythrough",()=>finish(audio),false);audio.onerror=function audio_onerror(event){if(done)return;err("warning: browser could not fully decode audio "+name+", trying slower base64 approach");function encode64(data){var BASE="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var PAD="=";var ret="";var leftchar=0;var leftbits=0;for(var i=0;i=6){var curr=leftchar>>leftbits-6&63;leftbits-=6;ret+=BASE[curr]}}if(leftbits==2){ret+=BASE[(leftchar&3)<<4];ret+=PAD+PAD}else if(leftbits==4){ret+=BASE[(leftchar&15)<<2];ret+=PAD}return ret}audio.src="data:audio/x-"+name.substr(-3)+";base64,"+encode64(byteArray);finish(audio)};audio.src=url;safeSetTimeout(function(){finish(audio)},1e4)}else{return fail()}};Module["preloadPlugins"].push(audioPlugin);function pointerLockChange(){Browser.pointerLock=document["pointerLockElement"]===Module["canvas"]||document["mozPointerLockElement"]===Module["canvas"]||document["webkitPointerLockElement"]===Module["canvas"]||document["msPointerLockElement"]===Module["canvas"]}var canvas=Module["canvas"];if(canvas){canvas.requestPointerLock=canvas["requestPointerLock"]||canvas["mozRequestPointerLock"]||canvas["webkitRequestPointerLock"]||canvas["msRequestPointerLock"]||(()=>{});canvas.exitPointerLock=document["exitPointerLock"]||document["mozExitPointerLock"]||document["webkitExitPointerLock"]||document["msExitPointerLock"]||(()=>{});canvas.exitPointerLock=canvas.exitPointerLock.bind(document);document.addEventListener("pointerlockchange",pointerLockChange,false);document.addEventListener("mozpointerlockchange",pointerLockChange,false);document.addEventListener("webkitpointerlockchange",pointerLockChange,false);document.addEventListener("mspointerlockchange",pointerLockChange,false);if(Module["elementPointerLock"]){canvas.addEventListener("click",ev=>{if(!Browser.pointerLock&&Module["canvas"].requestPointerLock){Module["canvas"].requestPointerLock();ev.preventDefault()}},false)}}},handledByPreloadPlugin:function(byteArray,fullname,finish,onerror){Browser.init();var handled=false;Module["preloadPlugins"].forEach(function(plugin){if(handled)return;if(plugin["canHandle"](fullname)){plugin["handle"](byteArray,fullname,finish,onerror);handled=true}});return handled},createContext:function(canvas,useWebGL,setInModule,webGLContextAttributes){if(useWebGL&&Module.ctx&&canvas==Module.canvas)return Module.ctx;var ctx;var contextHandle;if(useWebGL){var contextAttributes={antialias:false,alpha:false,majorVersion:typeof WebGL2RenderingContext!="undefined"?2:1};if(webGLContextAttributes){for(var attribute in webGLContextAttributes){contextAttributes[attribute]=webGLContextAttributes[attribute]}}if(typeof GL!="undefined"){contextHandle=GL.createContext(canvas,contextAttributes);if(contextHandle){ctx=GL.getContext(contextHandle).GLctx}}}else{ctx=canvas.getContext("2d")}if(!ctx)return null;if(setInModule){if(!useWebGL)assert(typeof GLctx=="undefined","cannot set in module if GLctx is used, but we are a non-GL context that would replace it");Module.ctx=ctx;if(useWebGL)GL.makeContextCurrent(contextHandle);Module.useWebGL=useWebGL;Browser.moduleContextCreatedCallbacks.forEach(function(callback){callback()});Browser.init()}return ctx},destroyContext:function(canvas,useWebGL,setInModule){},fullscreenHandlersInstalled:false,lockPointer:undefined,resizeCanvas:undefined,requestFullscreen:function(lockPointer,resizeCanvas){Browser.lockPointer=lockPointer;Browser.resizeCanvas=resizeCanvas;if(typeof Browser.lockPointer=="undefined")Browser.lockPointer=true;if(typeof Browser.resizeCanvas=="undefined")Browser.resizeCanvas=false;var canvas=Module["canvas"];function fullscreenChange(){Browser.isFullscreen=false;var canvasContainer=canvas.parentNode;if((document["fullscreenElement"]||document["mozFullScreenElement"]||document["msFullscreenElement"]||document["webkitFullscreenElement"]||document["webkitCurrentFullScreenElement"])===canvasContainer){canvas.exitFullscreen=Browser.exitFullscreen;if(Browser.lockPointer)canvas.requestPointerLock();Browser.isFullscreen=true;if(Browser.resizeCanvas){Browser.setFullscreenCanvasSize()}else{Browser.updateCanvasDimensions(canvas)}}else{canvasContainer.parentNode.insertBefore(canvas,canvasContainer);canvasContainer.parentNode.removeChild(canvasContainer);if(Browser.resizeCanvas){Browser.setWindowedCanvasSize()}else{Browser.updateCanvasDimensions(canvas)}}if(Module["onFullScreen"])Module["onFullScreen"](Browser.isFullscreen);if(Module["onFullscreen"])Module["onFullscreen"](Browser.isFullscreen)}if(!Browser.fullscreenHandlersInstalled){Browser.fullscreenHandlersInstalled=true;document.addEventListener("fullscreenchange",fullscreenChange,false);document.addEventListener("mozfullscreenchange",fullscreenChange,false);document.addEventListener("webkitfullscreenchange",fullscreenChange,false);document.addEventListener("MSFullscreenChange",fullscreenChange,false)}var canvasContainer=document.createElement("div");canvas.parentNode.insertBefore(canvasContainer,canvas);canvasContainer.appendChild(canvas);canvasContainer.requestFullscreen=canvasContainer["requestFullscreen"]||canvasContainer["mozRequestFullScreen"]||canvasContainer["msRequestFullscreen"]||(canvasContainer["webkitRequestFullscreen"]?()=>canvasContainer["webkitRequestFullscreen"](Element["ALLOW_KEYBOARD_INPUT"]):null)||(canvasContainer["webkitRequestFullScreen"]?()=>canvasContainer["webkitRequestFullScreen"](Element["ALLOW_KEYBOARD_INPUT"]):null);canvasContainer.requestFullscreen()},exitFullscreen:function(){if(!Browser.isFullscreen){return false}var CFS=document["exitFullscreen"]||document["cancelFullScreen"]||document["mozCancelFullScreen"]||document["msExitFullscreen"]||document["webkitCancelFullScreen"]||function(){};CFS.apply(document,[]);return true},nextRAF:0,fakeRequestAnimationFrame:function(func){var now=Date.now();if(Browser.nextRAF===0){Browser.nextRAF=now+1e3/60}else{while(now+2>=Browser.nextRAF){Browser.nextRAF+=1e3/60}}var delay=Math.max(Browser.nextRAF-now,0);setTimeout(func,delay)},requestAnimationFrame:function(func){if(typeof requestAnimationFrame=="function"){requestAnimationFrame(func);return}var RAF=Browser.fakeRequestAnimationFrame;RAF(func)},safeSetTimeout:function(func,timeout){return safeSetTimeout(func,timeout)},safeRequestAnimationFrame:function(func){runtimeKeepalivePush();return Browser.requestAnimationFrame(function(){runtimeKeepalivePop();callUserCallback(func)})},getMimetype:function(name){return{"jpg":"image/jpeg","jpeg":"image/jpeg","png":"image/png","bmp":"image/bmp","ogg":"audio/ogg","wav":"audio/wav","mp3":"audio/mpeg"}[name.substr(name.lastIndexOf(".")+1)]},getUserMedia:function(func){if(!window.getUserMedia){window.getUserMedia=navigator["getUserMedia"]||navigator["mozGetUserMedia"]}window.getUserMedia(func)},getMovementX:function(event){return event["movementX"]||event["mozMovementX"]||event["webkitMovementX"]||0},getMovementY:function(event){return event["movementY"]||event["mozMovementY"]||event["webkitMovementY"]||0},getMouseWheelDelta:function(event){var delta=0;switch(event.type){case"DOMMouseScroll":delta=event.detail/3;break;case"mousewheel":delta=event.wheelDelta/120;break;case"wheel":delta=event.deltaY;switch(event.deltaMode){case 0:delta/=100;break;case 1:delta/=3;break;case 2:delta*=80;break;default:throw"unrecognized mouse wheel delta mode: "+event.deltaMode}break;default:throw"unrecognized mouse wheel event: "+event.type}return delta},mouseX:0,mouseY:0,mouseMovementX:0,mouseMovementY:0,touches:{},lastTouches:{},calculateMouseEvent:function(event){if(Browser.pointerLock){if(event.type!="mousemove"&&"mozMovementX"in event){Browser.mouseMovementX=Browser.mouseMovementY=0}else{Browser.mouseMovementX=Browser.getMovementX(event);Browser.mouseMovementY=Browser.getMovementY(event)}if(typeof SDL!="undefined"){Browser.mouseX=SDL.mouseX+Browser.mouseMovementX;Browser.mouseY=SDL.mouseY+Browser.mouseMovementY}else{Browser.mouseX+=Browser.mouseMovementX;Browser.mouseY+=Browser.mouseMovementY}}else{var rect=Module["canvas"].getBoundingClientRect();var cw=Module["canvas"].width;var ch=Module["canvas"].height;var scrollX=typeof window.scrollX!="undefined"?window.scrollX:window.pageXOffset;var scrollY=typeof window.scrollY!="undefined"?window.scrollY:window.pageYOffset;if(event.type==="touchstart"||event.type==="touchend"||event.type==="touchmove"){var touch=event.touch;if(touch===undefined){return}var adjustedX=touch.pageX-(scrollX+rect.left);var adjustedY=touch.pageY-(scrollY+rect.top);adjustedX=adjustedX*(cw/rect.width);adjustedY=adjustedY*(ch/rect.height);var coords={x:adjustedX,y:adjustedY};if(event.type==="touchstart"){Browser.lastTouches[touch.identifier]=coords;Browser.touches[touch.identifier]=coords}else if(event.type==="touchend"||event.type==="touchmove"){var last=Browser.touches[touch.identifier];if(!last)last=coords;Browser.lastTouches[touch.identifier]=last;Browser.touches[touch.identifier]=coords}return}var x=event.pageX-(scrollX+rect.left);var y=event.pageY-(scrollY+rect.top);x=x*(cw/rect.width);y=y*(ch/rect.height);Browser.mouseMovementX=x-Browser.mouseX;Browser.mouseMovementY=y-Browser.mouseY;Browser.mouseX=x;Browser.mouseY=y}},resizeListeners:[],updateResizeListeners:function(){var canvas=Module["canvas"];Browser.resizeListeners.forEach(function(listener){listener(canvas.width,canvas.height)})},setCanvasSize:function(width,height,noUpdates){var canvas=Module["canvas"];Browser.updateCanvasDimensions(canvas,width,height);if(!noUpdates)Browser.updateResizeListeners()},windowedWidth:0,windowedHeight:0,setFullscreenCanvasSize:function(){if(typeof SDL!="undefined"){var flags=HEAPU32[SDL.screen>>2];flags=flags|8388608;HEAP32[SDL.screen>>2]=flags}Browser.updateCanvasDimensions(Module["canvas"]);Browser.updateResizeListeners()},setWindowedCanvasSize:function(){if(typeof SDL!="undefined"){var flags=HEAPU32[SDL.screen>>2];flags=flags&~8388608;HEAP32[SDL.screen>>2]=flags}Browser.updateCanvasDimensions(Module["canvas"]);Browser.updateResizeListeners()},updateCanvasDimensions:function(canvas,wNative,hNative){if(wNative&&hNative){canvas.widthNative=wNative;canvas.heightNative=hNative}else{wNative=canvas.widthNative;hNative=canvas.heightNative}var w=wNative;var h=hNative;if(Module["forcedAspectRatio"]&&Module["forcedAspectRatio"]>0){if(w/h>>16);updateGlobalBufferAndViews(wasmMemory.buffer);return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){var oldSize=HEAPU8.length;requestedSize=requestedSize>>>0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}let alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var JSEvents={inEventHandler:0,removeAllEventListeners:function(){for(var i=JSEvents.eventHandlers.length-1;i>=0;--i){JSEvents._removeHandler(i)}JSEvents.eventHandlers=[];JSEvents.deferredCalls=[]},registerRemoveEventListeners:function(){if(!JSEvents.removeEventListenersRegistered){__ATEXIT__.push(JSEvents.removeAllEventListeners);JSEvents.removeEventListenersRegistered=true}},deferredCalls:[],deferCall:function(targetFunction,precedence,argsList){function arraysHaveEqualContent(arrA,arrB){if(arrA.length!=arrB.length)return false;for(var i in arrA){if(arrA[i]!=arrB[i])return false}return true}for(var i in JSEvents.deferredCalls){var call=JSEvents.deferredCalls[i];if(call.targetFunction==targetFunction&&arraysHaveEqualContent(call.argsList,argsList)){return}}JSEvents.deferredCalls.push({targetFunction:targetFunction,precedence:precedence,argsList:argsList});JSEvents.deferredCalls.sort(function(x,y){return x.precedence2?UTF8ToString(cString):cString}var specialHTMLTargets=[0,typeof document!="undefined"?document:0,typeof window!="undefined"?window:0];function findEventTarget(target){target=maybeCStringToJsString(target);var domElement=specialHTMLTargets[target]||(typeof document!="undefined"?document.querySelector(target):undefined);return domElement}function findCanvasEventTarget(target){return findEventTarget(target)}function _emscripten_set_canvas_element_size(target,width,height){var canvas=findCanvasEventTarget(target);if(!canvas)return-4;canvas.width=width;canvas.height=height;if(canvas.GLctxObject)GL.resizeOffscreenFramebuffer(canvas.GLctxObject);return 0}function _emscripten_set_main_loop(func,fps,simulateInfiniteLoop){var browserIterationFunc=getWasmTableEntry(func);setMainLoop(browserIterationFunc,fps,simulateInfiniteLoop)}function __webgl_enable_ANGLE_instanced_arrays(ctx){var ext=ctx.getExtension("ANGLE_instanced_arrays");if(ext){ctx["vertexAttribDivisor"]=function(index,divisor){ext["vertexAttribDivisorANGLE"](index,divisor)};ctx["drawArraysInstanced"]=function(mode,first,count,primcount){ext["drawArraysInstancedANGLE"](mode,first,count,primcount)};ctx["drawElementsInstanced"]=function(mode,count,type,indices,primcount){ext["drawElementsInstancedANGLE"](mode,count,type,indices,primcount)};return 1}}function __webgl_enable_OES_vertex_array_object(ctx){var ext=ctx.getExtension("OES_vertex_array_object");if(ext){ctx["createVertexArray"]=function(){return ext["createVertexArrayOES"]()};ctx["deleteVertexArray"]=function(vao){ext["deleteVertexArrayOES"](vao)};ctx["bindVertexArray"]=function(vao){ext["bindVertexArrayOES"](vao)};ctx["isVertexArray"]=function(vao){return ext["isVertexArrayOES"](vao)};return 1}}function __webgl_enable_WEBGL_draw_buffers(ctx){var ext=ctx.getExtension("WEBGL_draw_buffers");if(ext){ctx["drawBuffers"]=function(n,bufs){ext["drawBuffersWEBGL"](n,bufs)};return 1}}function __webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance(ctx){return!!(ctx.dibvbi=ctx.getExtension("WEBGL_draw_instanced_base_vertex_base_instance"))}function __webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance(ctx){return!!(ctx.mdibvbi=ctx.getExtension("WEBGL_multi_draw_instanced_base_vertex_base_instance"))}function __webgl_enable_WEBGL_multi_draw(ctx){return!!(ctx.multiDrawWebgl=ctx.getExtension("WEBGL_multi_draw"))}var GL={counter:1,buffers:[],programs:[],framebuffers:[],renderbuffers:[],textures:[],shaders:[],vaos:[],contexts:[],offscreenCanvases:{},queries:[],samplers:[],transformFeedbacks:[],syncs:[],stringCache:{},stringiCache:{},unpackAlignment:4,recordError:function recordError(errorCode){if(!GL.lastError){GL.lastError=errorCode}},getNewId:function(table){var ret=GL.counter++;for(var i=table.length;i>2]:-1;source+=UTF8ToString(HEAP32[string+i*4>>2],len<0?undefined:len)}return source},createContext:function(canvas,webGLContextAttributes){if(webGLContextAttributes.renderViaOffscreenBackBuffer)webGLContextAttributes["preserveDrawingBuffer"]=true;var ctx=webGLContextAttributes.majorVersion>1?canvas.getContext("webgl2",webGLContextAttributes):canvas.getContext("webgl",webGLContextAttributes);if(!ctx)return 0;var handle=GL.registerContext(ctx,webGLContextAttributes);return handle},enableOffscreenFramebufferAttributes:function(webGLContextAttributes){webGLContextAttributes.renderViaOffscreenBackBuffer=true;webGLContextAttributes.preserveDrawingBuffer=true},createOffscreenFramebuffer:function(context){var gl=context.GLctx;var fbo=gl.createFramebuffer();gl.bindFramebuffer(36160,fbo);context.defaultFbo=fbo;context.defaultFboForbidBlitFramebuffer=false;if(gl.getContextAttributes().antialias){context.defaultFboForbidBlitFramebuffer=true}else{var firefoxMatch=navigator.userAgent.toLowerCase().match(/firefox\/(\d\d)/);if(firefoxMatch!=null){var firefoxVersion=firefoxMatch[1];context.defaultFboForbidBlitFramebuffer=firefoxVersion<67}}context.defaultColorTarget=gl.createTexture();context.defaultDepthTarget=gl.createRenderbuffer();GL.resizeOffscreenFramebuffer(context);gl.bindTexture(3553,context.defaultColorTarget);gl.texParameteri(3553,10241,9728);gl.texParameteri(3553,10240,9728);gl.texParameteri(3553,10242,33071);gl.texParameteri(3553,10243,33071);gl.texImage2D(3553,0,6408,gl.canvas.width,gl.canvas.height,0,6408,5121,null);gl.framebufferTexture2D(36160,36064,3553,context.defaultColorTarget,0);gl.bindTexture(3553,null);var depthTarget=gl.createRenderbuffer();gl.bindRenderbuffer(36161,context.defaultDepthTarget);gl.renderbufferStorage(36161,33189,gl.canvas.width,gl.canvas.height);gl.framebufferRenderbuffer(36160,36096,36161,context.defaultDepthTarget);gl.bindRenderbuffer(36161,null);var vertices=[-1,-1,-1,1,1,-1,1,1];var vb=gl.createBuffer();gl.bindBuffer(34962,vb);gl.bufferData(34962,new Float32Array(vertices),35044);gl.bindBuffer(34962,null);context.blitVB=vb;var vsCode="attribute vec2 pos;"+"varying lowp vec2 tex;"+"void main() { tex = pos * 0.5 + vec2(0.5,0.5); gl_Position = vec4(pos, 0.0, 1.0); }";var vs=gl.createShader(35633);gl.shaderSource(vs,vsCode);gl.compileShader(vs);var fsCode="varying lowp vec2 tex;"+"uniform sampler2D sampler;"+"void main() { gl_FragColor = texture2D(sampler, tex); }";var fs=gl.createShader(35632);gl.shaderSource(fs,fsCode);gl.compileShader(fs);var blitProgram=gl.createProgram();gl.attachShader(blitProgram,vs);gl.attachShader(blitProgram,fs);gl.linkProgram(blitProgram);context.blitProgram=blitProgram;context.blitPosLoc=gl.getAttribLocation(blitProgram,"pos");gl.useProgram(blitProgram);gl.uniform1i(gl.getUniformLocation(blitProgram,"sampler"),0);gl.useProgram(null);context.defaultVao=undefined;if(gl.createVertexArray){context.defaultVao=gl.createVertexArray();gl.bindVertexArray(context.defaultVao);gl.enableVertexAttribArray(context.blitPosLoc);gl.bindVertexArray(null)}},resizeOffscreenFramebuffer:function(context){var gl=context.GLctx;if(context.defaultColorTarget){var prevTextureBinding=gl.getParameter(32873);gl.bindTexture(3553,context.defaultColorTarget);gl.texImage2D(3553,0,6408,gl.drawingBufferWidth,gl.drawingBufferHeight,0,6408,5121,null);gl.bindTexture(3553,prevTextureBinding)}if(context.defaultDepthTarget){var prevRenderBufferBinding=gl.getParameter(36007);gl.bindRenderbuffer(36161,context.defaultDepthTarget);gl.renderbufferStorage(36161,33189,gl.drawingBufferWidth,gl.drawingBufferHeight);gl.bindRenderbuffer(36161,prevRenderBufferBinding)}},blitOffscreenFramebuffer:function(context){var gl=context.GLctx;var prevScissorTest=gl.getParameter(3089);if(prevScissorTest)gl.disable(3089);var prevFbo=gl.getParameter(36006);if(gl.blitFramebuffer&&!context.defaultFboForbidBlitFramebuffer){gl.bindFramebuffer(36008,context.defaultFbo);gl.bindFramebuffer(36009,null);gl.blitFramebuffer(0,0,gl.canvas.width,gl.canvas.height,0,0,gl.canvas.width,gl.canvas.height,16384,9728)}else{gl.bindFramebuffer(36160,null);var prevProgram=gl.getParameter(35725);gl.useProgram(context.blitProgram);var prevVB=gl.getParameter(34964);gl.bindBuffer(34962,context.blitVB);var prevActiveTexture=gl.getParameter(34016);gl.activeTexture(33984);var prevTextureBinding=gl.getParameter(32873);gl.bindTexture(3553,context.defaultColorTarget);var prevBlend=gl.getParameter(3042);if(prevBlend)gl.disable(3042);var prevCullFace=gl.getParameter(2884);if(prevCullFace)gl.disable(2884);var prevDepthTest=gl.getParameter(2929);if(prevDepthTest)gl.disable(2929);var prevStencilTest=gl.getParameter(2960);if(prevStencilTest)gl.disable(2960);function draw(){gl.vertexAttribPointer(context.blitPosLoc,2,5126,false,0,0);gl.drawArrays(5,0,4)}if(context.defaultVao){var prevVAO=gl.getParameter(34229);gl.bindVertexArray(context.defaultVao);draw();gl.bindVertexArray(prevVAO)}else{var prevVertexAttribPointer={buffer:gl.getVertexAttrib(context.blitPosLoc,34975),size:gl.getVertexAttrib(context.blitPosLoc,34339),stride:gl.getVertexAttrib(context.blitPosLoc,34340),type:gl.getVertexAttrib(context.blitPosLoc,34341),normalized:gl.getVertexAttrib(context.blitPosLoc,34922),pointer:gl.getVertexAttribOffset(context.blitPosLoc,34373)};var maxVertexAttribs=gl.getParameter(34921);var prevVertexAttribEnables=[];for(var i=0;i=2){GLctx.disjointTimerQueryExt=GLctx.getExtension("EXT_disjoint_timer_query_webgl2")}if(context.version<2||!GLctx.disjointTimerQueryExt){GLctx.disjointTimerQueryExt=GLctx.getExtension("EXT_disjoint_timer_query")}__webgl_enable_WEBGL_multi_draw(GLctx);var exts=GLctx.getSupportedExtensions()||[];exts.forEach(function(ext){if(!ext.includes("lose_context")&&!ext.includes("debug")){GLctx.getExtension(ext)}})}};var __emscripten_webgl_power_preferences=["default","low-power","high-performance"];function _emscripten_webgl_do_create_context(target,attributes){var a=attributes>>2;var powerPreference=HEAP32[a+(24>>2)];var contextAttributes={"alpha":!!HEAP32[a+(0>>2)],"depth":!!HEAP32[a+(4>>2)],"stencil":!!HEAP32[a+(8>>2)],"antialias":!!HEAP32[a+(12>>2)],"premultipliedAlpha":!!HEAP32[a+(16>>2)],"preserveDrawingBuffer":!!HEAP32[a+(20>>2)],"powerPreference":__emscripten_webgl_power_preferences[powerPreference],"failIfMajorPerformanceCaveat":!!HEAP32[a+(28>>2)],majorVersion:HEAP32[a+(32>>2)],minorVersion:HEAP32[a+(36>>2)],enableExtensionsByDefault:HEAP32[a+(40>>2)],explicitSwapControl:HEAP32[a+(44>>2)],proxyContextToMainThread:HEAP32[a+(48>>2)],renderViaOffscreenBackBuffer:HEAP32[a+(52>>2)]};var canvas=findCanvasEventTarget(target);if(!canvas){return 0}if(contextAttributes.explicitSwapControl&&!contextAttributes.renderViaOffscreenBackBuffer){contextAttributes.renderViaOffscreenBackBuffer=true}var contextHandle=GL.createContext(canvas,contextAttributes);return contextHandle}var _emscripten_webgl_create_context=_emscripten_webgl_do_create_context;function _emscripten_webgl_destroy_context(contextHandle){if(GL.currentContext==contextHandle)GL.currentContext=0;GL.deleteContext(contextHandle)}function _emscripten_webgl_enable_extension(contextHandle,extension){var context=GL.getContext(contextHandle);var extString=UTF8ToString(extension);if(extString.startsWith("GL_"))extString=extString.substr(3);if(extString=="ANGLE_instanced_arrays")__webgl_enable_ANGLE_instanced_arrays(GLctx);if(extString=="OES_vertex_array_object")__webgl_enable_OES_vertex_array_object(GLctx);if(extString=="WEBGL_draw_buffers")__webgl_enable_WEBGL_draw_buffers(GLctx);if(extString=="WEBGL_draw_instanced_base_vertex_base_instance")__webgl_enable_WEBGL_draw_instanced_base_vertex_base_instance(GLctx);if(extString=="WEBGL_multi_draw_instanced_base_vertex_base_instance")__webgl_enable_WEBGL_multi_draw_instanced_base_vertex_base_instance(GLctx);if(extString=="WEBGL_multi_draw")__webgl_enable_WEBGL_multi_draw(GLctx);var ext=context.GLctx.getExtension(extString);return!!ext}function stringToNewUTF8(jsString){var length=lengthBytesUTF8(jsString)+1;var cString=_malloc(length);stringToUTF8(jsString,cString,length);return cString}function _emscripten_webgl_get_supported_extensions(){return stringToNewUTF8(GLctx.getSupportedExtensions().join(" "))}function _emscripten_webgl_init_context_attributes(attributes){var a=attributes>>2;for(var i=0;i<56>>2;++i){HEAP32[a+i]=0}HEAP32[a+(0>>2)]=HEAP32[a+(4>>2)]=HEAP32[a+(12>>2)]=HEAP32[a+(16>>2)]=HEAP32[a+(32>>2)]=HEAP32[a+(40>>2)]=1}function _emscripten_webgl_make_context_current(contextHandle){var success=GL.makeContextCurrent(contextHandle);return success?0:-5}var ENV={};function getExecutableName(){return thisProgram||"./this.program"}function getEnvStrings(){if(!getEnvStrings.strings){var lang=(typeof navigator=="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={"USER":"web_user","LOGNAME":"web_user","PATH":"/","PWD":"/","HOME":"/home/web_user","LANG":lang,"_":getExecutableName()};for(var x in ENV){if(ENV[x]===undefined)delete env[x];else env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(x+"="+env[x])}getEnvStrings.strings=strings}return getEnvStrings.strings}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}function _environ_get(__environ,environ_buf){var bufSize=0;getEnvStrings().forEach(function(string,i){var ptr=environ_buf+bufSize;HEAPU32[__environ+i*4>>2]=ptr;writeAsciiToMemory(string,ptr);bufSize+=string.length+1});return 0}function _environ_sizes_get(penviron_count,penviron_buf_size){var strings=getEnvStrings();HEAPU32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAPU32[penviron_buf_size>>2]=bufSize;return 0}function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function _fd_fdstat_get(fd,pbuf){try{var stream=SYSCALLS.getStreamFromFD(fd);var type=stream.tty?2:FS.isDir(stream.mode)?3:FS.isLink(stream.mode)?7:4;HEAP8[pbuf>>0]=type;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function doReadv(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){try{var offset=convertI32PairToI53Checked(offset_low,offset_high);if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function doWritev(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAPU32[iov+4>>2];iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr}return ret}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt);HEAPU32[pnum>>2]=num;return 0}catch(e){if(typeof FS=="undefined"||!(e instanceof FS.ErrnoError))throw e;return e.errno}}function _getaddrinfo(node,service,hint,out){var addr=0;var port=0;var flags=0;var family=0;var type=0;var proto=0;var ai;function allocaddrinfo(family,type,proto,canon,addr,port){var sa,salen,ai;var errno;salen=family===10?28:16;addr=family===10?inetNtop6(addr):inetNtop4(addr);sa=_malloc(salen);errno=writeSockaddr(sa,family,addr,port);assert(!errno);ai=_malloc(32);HEAP32[ai+4>>2]=family;HEAP32[ai+8>>2]=type;HEAP32[ai+12>>2]=proto;HEAP32[ai+24>>2]=canon;HEAPU32[ai+20>>2]=sa;if(family===10){HEAP32[ai+16>>2]=28}else{HEAP32[ai+16>>2]=16}HEAP32[ai+28>>2]=0;return ai}if(hint){flags=HEAP32[hint>>2];family=HEAP32[hint+4>>2];type=HEAP32[hint+8>>2];proto=HEAP32[hint+12>>2]}if(type&&!proto){proto=type===2?17:6}if(!type&&proto){type=proto===17?2:1}if(proto===0){proto=6}if(type===0){type=1}if(!node&&!service){return-2}if(flags&~(1|2|4|1024|8|16|32)){return-1}if(hint!==0&&HEAP32[hint>>2]&2&&!node){return-1}if(flags&32){return-2}if(type!==0&&type!==1&&type!==2){return-7}if(family!==0&&family!==2&&family!==10){return-6}if(service){service=UTF8ToString(service);port=parseInt(service,10);if(isNaN(port)){if(flags&1024){return-2}return-8}}if(!node){if(family===0){family=2}if((flags&1)===0){if(family===2){addr=_htonl(2130706433)}else{addr=[0,0,0,1]}}ai=allocaddrinfo(family,type,proto,null,addr,port);HEAPU32[out>>2]=ai;return 0}node=UTF8ToString(node);addr=inetPton4(node);if(addr!==null){if(family===0||family===2){family=2}else if(family===10&&flags&8){addr=[0,0,_htonl(65535),addr];family=10}else{return-2}}else{addr=inetPton6(node);if(addr!==null){if(family===0||family===10){family=10}else{return-2}}}if(addr!=null){ai=allocaddrinfo(family,type,proto,node,addr,port);HEAPU32[out>>2]=ai;return 0}if(flags&4){return-2}node=DNS.lookup_name(node);addr=inetPton4(node);if(family===0){family=2}else if(family===10){addr=[0,0,_htonl(65535),addr]}ai=allocaddrinfo(family,type,proto,null,addr,port);HEAPU32[out>>2]=ai;return 0}function _getnameinfo(sa,salen,node,nodelen,serv,servlen,flags){var info=readSockaddr(sa,salen);if(info.errno){return-6}var port=info.port;var addr=info.addr;var overflowed=false;if(node&&nodelen){var lookup;if(flags&1||!(lookup=DNS.lookup_addr(addr))){if(flags&8){return-2}}else{addr=lookup}var numBytesWrittenExclNull=stringToUTF8(addr,node,nodelen);if(numBytesWrittenExclNull+1>=nodelen){overflowed=true}}if(serv&&servlen){port=""+port;var numBytesWrittenExclNull=stringToUTF8(port,serv,servlen);if(numBytesWrittenExclNull+1>=servlen){overflowed=true}}if(overflowed){return-12}return 0}function _glActiveTexture(x0){GLctx["activeTexture"](x0)}function _glAttachShader(program,shader){GLctx.attachShader(GL.programs[program],GL.shaders[shader])}function _glBeginTransformFeedback(x0){GLctx["beginTransformFeedback"](x0)}function _glBindBuffer(target,buffer){if(target==35051){GLctx.currentPixelPackBufferBinding=buffer}else if(target==35052){GLctx.currentPixelUnpackBufferBinding=buffer}GLctx.bindBuffer(target,GL.buffers[buffer])}function _glBindBufferBase(target,index,buffer){GLctx["bindBufferBase"](target,index,GL.buffers[buffer])}function _glBindBufferRange(target,index,buffer,offset,ptrsize){GLctx["bindBufferRange"](target,index,GL.buffers[buffer],offset,ptrsize)}function _glBindFramebuffer(target,framebuffer){GLctx.bindFramebuffer(target,framebuffer?GL.framebuffers[framebuffer]:GL.currentContext.defaultFbo)}function _glBindRenderbuffer(target,renderbuffer){GLctx.bindRenderbuffer(target,GL.renderbuffers[renderbuffer])}function _glBindTexture(target,texture){GLctx.bindTexture(target,GL.textures[texture])}function _glBindVertexArray(vao){GLctx["bindVertexArray"](GL.vaos[vao])}function _glBlendColor(x0,x1,x2,x3){GLctx["blendColor"](x0,x1,x2,x3)}function _glBlendEquation(x0){GLctx["blendEquation"](x0)}function _glBlendFunc(x0,x1){GLctx["blendFunc"](x0,x1)}function _glBlendFuncSeparate(x0,x1,x2,x3){GLctx["blendFuncSeparate"](x0,x1,x2,x3)}function _glBlitFramebuffer(x0,x1,x2,x3,x4,x5,x6,x7,x8,x9){GLctx["blitFramebuffer"](x0,x1,x2,x3,x4,x5,x6,x7,x8,x9)}function _glBufferData(target,size,data,usage){if(GL.currentContext.version>=2){if(data&&size){GLctx.bufferData(target,HEAPU8,usage,data,size)}else{GLctx.bufferData(target,size,usage)}}else{GLctx.bufferData(target,data?HEAPU8.subarray(data,data+size):size,usage)}}function _glBufferSubData(target,offset,size,data){if(GL.currentContext.version>=2){size&&GLctx.bufferSubData(target,offset,HEAPU8,data,size);return}GLctx.bufferSubData(target,offset,HEAPU8.subarray(data,data+size))}function _glCheckFramebufferStatus(x0){return GLctx["checkFramebufferStatus"](x0)}function _glClear(x0){GLctx["clear"](x0)}function _glClearBufferfv(buffer,drawbuffer,value){GLctx["clearBufferfv"](buffer,drawbuffer,HEAPF32,value>>2)}function _glClearColor(x0,x1,x2,x3){GLctx["clearColor"](x0,x1,x2,x3)}function _glClearDepthf(x0){GLctx["clearDepth"](x0)}function _glColorMask(red,green,blue,alpha){GLctx.colorMask(!!red,!!green,!!blue,!!alpha)}function _glCompileShader(shader){GLctx.compileShader(GL.shaders[shader])}function _glCompressedTexImage2D(target,level,internalFormat,width,height,border,imageSize,data){if(GL.currentContext.version>=2){if(GLctx.currentPixelUnpackBufferBinding||!imageSize){GLctx["compressedTexImage2D"](target,level,internalFormat,width,height,border,imageSize,data)}else{GLctx["compressedTexImage2D"](target,level,internalFormat,width,height,border,HEAPU8,data,imageSize)}return}GLctx["compressedTexImage2D"](target,level,internalFormat,width,height,border,data?HEAPU8.subarray(data,data+imageSize):null)}function _glCompressedTexImage3D(target,level,internalFormat,width,height,depth,border,imageSize,data){if(GLctx.currentPixelUnpackBufferBinding){GLctx["compressedTexImage3D"](target,level,internalFormat,width,height,depth,border,imageSize,data)}else{GLctx["compressedTexImage3D"](target,level,internalFormat,width,height,depth,border,HEAPU8,data,imageSize)}}function _glCompressedTexSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,imageSize,data){if(GLctx.currentPixelUnpackBufferBinding){GLctx["compressedTexSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,imageSize,data)}else{GLctx["compressedTexSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,HEAPU8,data,imageSize)}}function _glCopyBufferSubData(x0,x1,x2,x3,x4){GLctx["copyBufferSubData"](x0,x1,x2,x3,x4)}function _glCreateProgram(){var id=GL.getNewId(GL.programs);var program=GLctx.createProgram();program.name=id;program.maxUniformLength=program.maxAttributeLength=program.maxUniformBlockNameLength=0;program.uniformIdCounter=1;GL.programs[id]=program;return id}function _glCreateShader(shaderType){var id=GL.getNewId(GL.shaders);GL.shaders[id]=GLctx.createShader(shaderType);return id}function _glCullFace(x0){GLctx["cullFace"](x0)}function _glDeleteBuffers(n,buffers){for(var i=0;i>2];var buffer=GL.buffers[id];if(!buffer)continue;GLctx.deleteBuffer(buffer);buffer.name=0;GL.buffers[id]=null;if(id==GLctx.currentPixelPackBufferBinding)GLctx.currentPixelPackBufferBinding=0;if(id==GLctx.currentPixelUnpackBufferBinding)GLctx.currentPixelUnpackBufferBinding=0}}function _glDeleteFramebuffers(n,framebuffers){for(var i=0;i>2];var framebuffer=GL.framebuffers[id];if(!framebuffer)continue;GLctx.deleteFramebuffer(framebuffer);framebuffer.name=0;GL.framebuffers[id]=null}}function _glDeleteProgram(id){if(!id)return;var program=GL.programs[id];if(!program){GL.recordError(1281);return}GLctx.deleteProgram(program);program.name=0;GL.programs[id]=null}function _glDeleteQueries(n,ids){for(var i=0;i>2];var query=GL.queries[id];if(!query)continue;GLctx["deleteQuery"](query);GL.queries[id]=null}}function _glDeleteRenderbuffers(n,renderbuffers){for(var i=0;i>2];var renderbuffer=GL.renderbuffers[id];if(!renderbuffer)continue;GLctx.deleteRenderbuffer(renderbuffer);renderbuffer.name=0;GL.renderbuffers[id]=null}}function _glDeleteShader(id){if(!id)return;var shader=GL.shaders[id];if(!shader){GL.recordError(1281);return}GLctx.deleteShader(shader);GL.shaders[id]=null}function _glDeleteSync(id){if(!id)return;var sync=GL.syncs[id];if(!sync){GL.recordError(1281);return}GLctx.deleteSync(sync);sync.name=0;GL.syncs[id]=null}function _glDeleteTextures(n,textures){for(var i=0;i>2];var texture=GL.textures[id];if(!texture)continue;GLctx.deleteTexture(texture);texture.name=0;GL.textures[id]=null}}function _glDeleteVertexArrays(n,vaos){for(var i=0;i>2];GLctx["deleteVertexArray"](GL.vaos[id]);GL.vaos[id]=null}}function _glDepthFunc(x0){GLctx["depthFunc"](x0)}function _glDepthMask(flag){GLctx.depthMask(!!flag)}function _glDisable(x0){GLctx["disable"](x0)}function _glDisableVertexAttribArray(index){GLctx.disableVertexAttribArray(index)}function _glDrawArrays(mode,first,count){GLctx.drawArrays(mode,first,count)}function _glDrawArraysInstanced(mode,first,count,primcount){GLctx["drawArraysInstanced"](mode,first,count,primcount)}var tempFixedLengthArray=[];function _glDrawBuffers(n,bufs){var bufArray=tempFixedLengthArray[n];for(var i=0;i>2]}GLctx["drawBuffers"](bufArray)}function _glDrawElements(mode,count,type,indices){GLctx.drawElements(mode,count,type,indices)}function _glDrawElementsInstanced(mode,count,type,indices,primcount){GLctx["drawElementsInstanced"](mode,count,type,indices,primcount)}function _glEnable(x0){GLctx["enable"](x0)}function _glEnableVertexAttribArray(index){GLctx.enableVertexAttribArray(index)}function _glEndTransformFeedback(){GLctx["endTransformFeedback"]()}function _glFenceSync(condition,flags){var sync=GLctx.fenceSync(condition,flags);if(sync){var id=GL.getNewId(GL.syncs);sync.name=id;GL.syncs[id]=sync;return id}return 0}function _glFinish(){GLctx["finish"]()}function _glFramebufferRenderbuffer(target,attachment,renderbuffertarget,renderbuffer){GLctx.framebufferRenderbuffer(target,attachment,renderbuffertarget,GL.renderbuffers[renderbuffer])}function _glFramebufferTexture2D(target,attachment,textarget,texture,level){GLctx.framebufferTexture2D(target,attachment,textarget,GL.textures[texture],level)}function _glFramebufferTextureLayer(target,attachment,texture,level,layer){GLctx.framebufferTextureLayer(target,attachment,GL.textures[texture],level,layer)}function _glFrontFace(x0){GLctx["frontFace"](x0)}function __glGenObject(n,buffers,createFunction,objectTable){for(var i=0;i>2]=id}}function _glGenBuffers(n,buffers){__glGenObject(n,buffers,"createBuffer",GL.buffers)}function _glGenFramebuffers(n,ids){__glGenObject(n,ids,"createFramebuffer",GL.framebuffers)}function _glGenQueries(n,ids){__glGenObject(n,ids,"createQuery",GL.queries)}function _glGenRenderbuffers(n,renderbuffers){__glGenObject(n,renderbuffers,"createRenderbuffer",GL.renderbuffers)}function _glGenTextures(n,textures){__glGenObject(n,textures,"createTexture",GL.textures)}function _glGenVertexArrays(n,arrays){__glGenObject(n,arrays,"createVertexArray",GL.vaos)}function _glGenerateMipmap(x0){GLctx["generateMipmap"](x0)}function writeI53ToI64(ptr,num){HEAPU32[ptr>>2]=num;HEAPU32[ptr+4>>2]=(num-HEAPU32[ptr>>2])/4294967296}function emscriptenWebGLGet(name_,p,type){if(!p){GL.recordError(1281);return}var ret=undefined;switch(name_){case 36346:ret=1;break;case 36344:if(type!=0&&type!=1){GL.recordError(1280)}return;case 34814:case 36345:ret=0;break;case 34466:var formats=GLctx.getParameter(34467);ret=formats?formats.length:0;break;case 33309:if(GL.currentContext.version<2){GL.recordError(1282);return}var exts=GLctx.getSupportedExtensions()||[];ret=2*exts.length;break;case 33307:case 33308:if(GL.currentContext.version<2){GL.recordError(1280);return}ret=name_==33307?3:0;break}if(ret===undefined){var result=GLctx.getParameter(name_);switch(typeof result){case"number":ret=result;break;case"boolean":ret=result?1:0;break;case"string":GL.recordError(1280);return;case"object":if(result===null){switch(name_){case 34964:case 35725:case 34965:case 36006:case 36007:case 32873:case 34229:case 36662:case 36663:case 35053:case 35055:case 36010:case 35097:case 35869:case 32874:case 36389:case 35983:case 35368:case 34068:{ret=0;break}default:{GL.recordError(1280);return}}}else if(result instanceof Float32Array||result instanceof Uint32Array||result instanceof Int32Array||result instanceof Array){for(var i=0;i>2]=result[i];break;case 2:HEAPF32[p+i*4>>2]=result[i];break;case 4:HEAP8[p+i>>0]=result[i]?1:0;break}}return}else{try{ret=result.name|0}catch(e){GL.recordError(1280);err("GL_INVALID_ENUM in glGet"+type+"v: Unknown object returned from WebGL getParameter("+name_+")! (error: "+e+")");return}}break;default:GL.recordError(1280);err("GL_INVALID_ENUM in glGet"+type+"v: Native code calling glGet"+type+"v("+name_+") and it returns "+result+" of type "+typeof result+"!");return}}switch(type){case 1:writeI53ToI64(p,ret);break;case 0:HEAP32[p>>2]=ret;break;case 2:HEAPF32[p>>2]=ret;break;case 4:HEAP8[p>>0]=ret?1:0;break}}function _glGetFloatv(name_,p){emscriptenWebGLGet(name_,p,2)}function _glGetInteger64v(name_,p){emscriptenWebGLGet(name_,p,1)}function _glGetIntegerv(name_,p){emscriptenWebGLGet(name_,p,0)}function _glGetProgramInfoLog(program,maxLength,length,infoLog){var log=GLctx.getProgramInfoLog(GL.programs[program]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull}function _glGetProgramiv(program,pname,p){if(!p){GL.recordError(1281);return}if(program>=GL.counter){GL.recordError(1281);return}program=GL.programs[program];if(pname==35716){var log=GLctx.getProgramInfoLog(program);if(log===null)log="(unknown error)";HEAP32[p>>2]=log.length+1}else if(pname==35719){if(!program.maxUniformLength){for(var i=0;i>2]=program.maxUniformLength}else if(pname==35722){if(!program.maxAttributeLength){for(var i=0;i>2]=program.maxAttributeLength}else if(pname==35381){if(!program.maxUniformBlockNameLength){for(var i=0;i>2]=program.maxUniformBlockNameLength}else{HEAP32[p>>2]=GLctx.getProgramParameter(program,pname)}}function _glGetShaderInfoLog(shader,maxLength,length,infoLog){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull}function _glGetShaderiv(shader,pname,p){if(!p){GL.recordError(1281);return}if(pname==35716){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var logLength=log?log.length+1:0;HEAP32[p>>2]=logLength}else if(pname==35720){var source=GLctx.getShaderSource(GL.shaders[shader]);var sourceLength=source?source.length+1:0;HEAP32[p>>2]=sourceLength}else{HEAP32[p>>2]=GLctx.getShaderParameter(GL.shaders[shader],pname)}}function _glGetString(name_){var ret=GL.stringCache[name_];if(!ret){switch(name_){case 7939:var exts=GLctx.getSupportedExtensions()||[];exts=exts.concat(exts.map(function(e){return"GL_"+e}));ret=stringToNewUTF8(exts.join(" "));break;case 7936:case 7937:case 37445:case 37446:var s=GLctx.getParameter(name_);if(!s){GL.recordError(1280)}ret=s&&stringToNewUTF8(s);break;case 7938:var glVersion=GLctx.getParameter(7938);if(GL.currentContext.version>=2)glVersion="OpenGL ES 3.0 ("+glVersion+")";else{glVersion="OpenGL ES 2.0 ("+glVersion+")"}ret=stringToNewUTF8(glVersion);break;case 35724:var glslVersion=GLctx.getParameter(35724);var ver_re=/^WebGL GLSL ES ([0-9]\.[0-9][0-9]?)(?:$| .*)/;var ver_num=glslVersion.match(ver_re);if(ver_num!==null){if(ver_num[1].length==3)ver_num[1]=ver_num[1]+"0";glslVersion="OpenGL ES GLSL ES "+ver_num[1]+" ("+glslVersion+")"}ret=stringToNewUTF8(glslVersion);break;default:GL.recordError(1280)}GL.stringCache[name_]=ret}return ret}function _glGetSynciv(sync,pname,bufSize,length,values){if(bufSize<0){GL.recordError(1281);return}if(!values){GL.recordError(1281);return}var ret=GLctx.getSyncParameter(GL.syncs[sync],pname);if(ret!==null){HEAP32[values>>2]=ret;if(length)HEAP32[length>>2]=1}}function _glGetUniformBlockIndex(program,uniformBlockName){return GLctx["getUniformBlockIndex"](GL.programs[program],UTF8ToString(uniformBlockName))}function webglGetLeftBracePos(name){return name.slice(-1)=="]"&&name.lastIndexOf("[")}function webglPrepareUniformLocationsBeforeFirstUse(program){var uniformLocsById=program.uniformLocsById,uniformSizeAndIdsByName=program.uniformSizeAndIdsByName,i,j;if(!uniformLocsById){program.uniformLocsById=uniformLocsById={};program.uniformArrayNamesById={};for(i=0;i0?nm.slice(0,lb):nm;var id=program.uniformIdCounter;program.uniformIdCounter+=sz;uniformSizeAndIdsByName[arrayName]=[sz,id];for(j=0;j0){arrayIndex=jstoi_q(name.slice(leftBrace+1))>>>0;uniformBaseName=name.slice(0,leftBrace)}var sizeAndId=program.uniformSizeAndIdsByName[uniformBaseName];if(sizeAndId&&arrayIndex>shift,pixels+bytes>>shift)}function _glReadPixels(x,y,width,height,format,type,pixels){if(GL.currentContext.version>=2){if(GLctx.currentPixelPackBufferBinding){GLctx.readPixels(x,y,width,height,format,type,pixels)}else{var heap=heapObjectForWebGLType(type);GLctx.readPixels(x,y,width,height,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}return}var pixelData=emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,format);if(!pixelData){GL.recordError(1280);return}GLctx.readPixels(x,y,width,height,format,type,pixelData)}function _glRenderbufferStorage(x0,x1,x2,x3){GLctx["renderbufferStorage"](x0,x1,x2,x3)}function _glRenderbufferStorageMultisample(x0,x1,x2,x3,x4){GLctx["renderbufferStorageMultisample"](x0,x1,x2,x3,x4)}function _glScissor(x0,x1,x2,x3){GLctx["scissor"](x0,x1,x2,x3)}function _glShaderSource(shader,count,string,length){var source=GL.getSource(shader,count,string,length);GLctx.shaderSource(GL.shaders[shader],source)}function _glTexImage2D(target,level,internalFormat,width,height,border,format,type,pixels){if(GL.currentContext.version>=2){if(GLctx.currentPixelUnpackBufferBinding){GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}else{GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,null)}return}GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixels?emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,internalFormat):null)}function _glTexImage3D(target,level,internalFormat,width,height,depth,border,format,type,pixels){if(GLctx.currentPixelUnpackBufferBinding){GLctx["texImage3D"](target,level,internalFormat,width,height,depth,border,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx["texImage3D"](target,level,internalFormat,width,height,depth,border,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}else{GLctx["texImage3D"](target,level,internalFormat,width,height,depth,border,format,type,null)}}function _glTexParameterf(x0,x1,x2){GLctx["texParameterf"](x0,x1,x2)}function _glTexParameteri(x0,x1,x2){GLctx["texParameteri"](x0,x1,x2)}function _glTexStorage2D(x0,x1,x2,x3,x4){GLctx["texStorage2D"](x0,x1,x2,x3,x4)}function _glTexSubImage3D(target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,pixels){if(GLctx.currentPixelUnpackBufferBinding){GLctx["texSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,pixels)}else if(pixels){var heap=heapObjectForWebGLType(type);GLctx["texSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,heap,pixels>>heapAccessShiftForWebGLHeap(heap))}else{GLctx["texSubImage3D"](target,level,xoffset,yoffset,zoffset,width,height,depth,format,type,null)}}function _glTransformFeedbackVaryings(program,count,varyings,bufferMode){program=GL.programs[program];var vars=[];for(var i=0;i>2]));GLctx["transformFeedbackVaryings"](program,vars,bufferMode)}function webglGetUniformLocation(location){var p=GLctx.currentProgram;if(p){var webglLoc=p.uniformLocsById[location];if(typeof webglLoc=="number"){p.uniformLocsById[location]=webglLoc=GLctx.getUniformLocation(p,p.uniformArrayNamesById[location]+(webglLoc>0?"["+webglLoc+"]":""))}return webglLoc}else{GL.recordError(1282)}}function _glUniform1f(location,v0){GLctx.uniform1f(webglGetUniformLocation(location),v0)}function _glUniform1i(location,v0){GLctx.uniform1i(webglGetUniformLocation(location),v0)}var __miniTempWebGLIntBuffers=[];function _glUniform1iv(location,count,value){if(GL.currentContext.version>=2){count&&GLctx.uniform1iv(webglGetUniformLocation(location),HEAP32,value>>2,count);return}if(count<=288){var view=__miniTempWebGLIntBuffers[count-1];for(var i=0;i>2]}}else{var view=HEAP32.subarray(value>>2,value+count*4>>2)}GLctx.uniform1iv(webglGetUniformLocation(location),view)}function _glUniform1ui(location,v0){GLctx.uniform1ui(webglGetUniformLocation(location),v0)}function _glUniform1uiv(location,count,value){count&&GLctx.uniform1uiv(webglGetUniformLocation(location),HEAPU32,value>>2,count)}function _glUniform2f(location,v0,v1){GLctx.uniform2f(webglGetUniformLocation(location),v0,v1)}var miniTempWebGLFloatBuffers=[];function _glUniform2fv(location,count,value){if(GL.currentContext.version>=2){count&&GLctx.uniform2fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*2);return}if(count<=144){var view=miniTempWebGLFloatBuffers[2*count-1];for(var i=0;i<2*count;i+=2){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*8>>2)}GLctx.uniform2fv(webglGetUniformLocation(location),view)}function _glUniform2iv(location,count,value){if(GL.currentContext.version>=2){count&&GLctx.uniform2iv(webglGetUniformLocation(location),HEAP32,value>>2,count*2);return}if(count<=144){var view=__miniTempWebGLIntBuffers[2*count-1];for(var i=0;i<2*count;i+=2){view[i]=HEAP32[value+4*i>>2];view[i+1]=HEAP32[value+(4*i+4)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*8>>2)}GLctx.uniform2iv(webglGetUniformLocation(location),view)}function _glUniform3fv(location,count,value){if(GL.currentContext.version>=2){count&&GLctx.uniform3fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*3);return}if(count<=96){var view=miniTempWebGLFloatBuffers[3*count-1];for(var i=0;i<3*count;i+=3){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*12>>2)}GLctx.uniform3fv(webglGetUniformLocation(location),view)}function _glUniform4f(location,v0,v1,v2,v3){GLctx.uniform4f(webglGetUniformLocation(location),v0,v1,v2,v3)}function _glUniform4fv(location,count,value){if(GL.currentContext.version>=2){count&&GLctx.uniform4fv(webglGetUniformLocation(location),HEAPF32,value>>2,count*4);return}if(count<=72){var view=miniTempWebGLFloatBuffers[4*count-1];var heap=HEAPF32;value>>=2;for(var i=0;i<4*count;i+=4){var dst=value+i;view[i]=heap[dst];view[i+1]=heap[dst+1];view[i+2]=heap[dst+2];view[i+3]=heap[dst+3]}}else{var view=HEAPF32.subarray(value>>2,value+count*16>>2)}GLctx.uniform4fv(webglGetUniformLocation(location),view)}function _glUniformBlockBinding(program,uniformBlockIndex,uniformBlockBinding){program=GL.programs[program];GLctx["uniformBlockBinding"](program,uniformBlockIndex,uniformBlockBinding)}function _glUniformMatrix3fv(location,count,transpose,value){if(GL.currentContext.version>=2){count&&GLctx.uniformMatrix3fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*9);return}if(count<=32){var view=miniTempWebGLFloatBuffers[9*count-1];for(var i=0;i<9*count;i+=9){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2];view[i+3]=HEAPF32[value+(4*i+12)>>2];view[i+4]=HEAPF32[value+(4*i+16)>>2];view[i+5]=HEAPF32[value+(4*i+20)>>2];view[i+6]=HEAPF32[value+(4*i+24)>>2];view[i+7]=HEAPF32[value+(4*i+28)>>2];view[i+8]=HEAPF32[value+(4*i+32)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*36>>2)}GLctx.uniformMatrix3fv(webglGetUniformLocation(location),!!transpose,view)}function _glUniformMatrix4fv(location,count,transpose,value){if(GL.currentContext.version>=2){count&&GLctx.uniformMatrix4fv(webglGetUniformLocation(location),!!transpose,HEAPF32,value>>2,count*16);return}if(count<=18){var view=miniTempWebGLFloatBuffers[16*count-1];var heap=HEAPF32;value>>=2;for(var i=0;i<16*count;i+=16){var dst=value+i;view[i]=heap[dst];view[i+1]=heap[dst+1];view[i+2]=heap[dst+2];view[i+3]=heap[dst+3];view[i+4]=heap[dst+4];view[i+5]=heap[dst+5];view[i+6]=heap[dst+6];view[i+7]=heap[dst+7];view[i+8]=heap[dst+8];view[i+9]=heap[dst+9];view[i+10]=heap[dst+10];view[i+11]=heap[dst+11];view[i+12]=heap[dst+12];view[i+13]=heap[dst+13];view[i+14]=heap[dst+14];view[i+15]=heap[dst+15]}}else{var view=HEAPF32.subarray(value>>2,value+count*64>>2)}GLctx.uniformMatrix4fv(webglGetUniformLocation(location),!!transpose,view)}function _glUseProgram(program){program=GL.programs[program];GLctx.useProgram(program);GLctx.currentProgram=program}function _glVertexAttrib4f(x0,x1,x2,x3,x4){GLctx["vertexAttrib4f"](x0,x1,x2,x3,x4)}function _glVertexAttribDivisor(index,divisor){GLctx["vertexAttribDivisor"](index,divisor)}function _glVertexAttribI4ui(x0,x1,x2,x3,x4){GLctx["vertexAttribI4ui"](x0,x1,x2,x3,x4)}function _glVertexAttribIPointer(index,size,type,stride,ptr){GLctx["vertexAttribIPointer"](index,size,type,stride,ptr)}function _glVertexAttribPointer(index,size,type,normalized,stride,ptr){GLctx.vertexAttribPointer(index,size,type,!!normalized,stride,ptr)}function _glViewport(x0,x1,x2,x3){GLctx["viewport"](x0,x1,x2,x3)}var GodotRuntime={get_func:function(ptr){return wasmTable.get(ptr)},error:function(){err.apply(null,Array.from(arguments))},print:function(){out.apply(null,Array.from(arguments))},malloc:function(p_size){return _malloc(p_size)},free:function(p_ptr){_free(p_ptr)},getHeapValue:function(p_ptr,p_type){return getValue(p_ptr,p_type)},setHeapValue:function(p_ptr,p_value,p_type){setValue(p_ptr,p_value,p_type)},heapSub:function(p_heap,p_ptr,p_len){const bytes=p_heap.BYTES_PER_ELEMENT;return p_heap.subarray(p_ptr/bytes,p_ptr/bytes+p_len)},heapSlice:function(p_heap,p_ptr,p_len){const bytes=p_heap.BYTES_PER_ELEMENT;return p_heap.slice(p_ptr/bytes,p_ptr/bytes+p_len)},heapCopy:function(p_dst,p_src,p_ptr){const bytes=p_src.BYTES_PER_ELEMENT;return p_dst.set(p_src,p_ptr/bytes)},parseString:function(p_ptr){return UTF8ToString(p_ptr)},parseStringArray:function(p_ptr,p_size){const strings=[];const ptrs=GodotRuntime.heapSub(HEAP32,p_ptr,p_size);ptrs.forEach(function(ptr){strings.push(GodotRuntime.parseString(ptr))});return strings},strlen:function(p_str){return lengthBytesUTF8(p_str)},allocString:function(p_str){const length=GodotRuntime.strlen(p_str)+1;const c_str=GodotRuntime.malloc(length);stringToUTF8(p_str,c_str,length);return c_str},allocStringArray:function(p_strings){const size=p_strings.length;const c_ptr=GodotRuntime.malloc(size*4);for(let i=0;i>2)+i]=GodotRuntime.allocString(p_strings[i])}return c_ptr},freeStringArray:function(p_ptr,p_len){for(let i=0;i>2)+i])}GodotRuntime.free(p_ptr)},stringToHeap:function(p_str,p_ptr,p_len){return stringToUTF8Array(p_str,HEAP8,p_ptr,p_len)}};var GodotConfig={canvas:null,locale:"en",canvas_resize_policy:2,virtual_keyboard:false,persistent_drops:false,on_execute:null,on_exit:null,init_config:function(p_opts){GodotConfig.canvas_resize_policy=p_opts["canvasResizePolicy"];GodotConfig.canvas=p_opts["canvas"];GodotConfig.locale=p_opts["locale"]||GodotConfig.locale;GodotConfig.virtual_keyboard=p_opts["virtualKeyboard"];GodotConfig.persistent_drops=!!p_opts["persistentDrops"];GodotConfig.on_execute=p_opts["onExecute"];GodotConfig.on_exit=p_opts["onExit"];if(p_opts["focusCanvas"]){GodotConfig.canvas.focus()}},locate_file:function(file){return Module["locateFile"](file)},clear:function(){GodotConfig.canvas=null;GodotConfig.locale="en";GodotConfig.canvas_resize_policy=2;GodotConfig.virtual_keyboard=false;GodotConfig.persistent_drops=false;GodotConfig.on_execute=null;GodotConfig.on_exit=null}};var GodotFS={ENOENT:44,_idbfs:false,_syncing:false,_mount_points:[],is_persistent:function(){return GodotFS._idbfs?1:0},init:function(persistentPaths){GodotFS._idbfs=false;if(!Array.isArray(persistentPaths)){return Promise.reject(new Error("Persistent paths must be an array"))}if(!persistentPaths.length){return Promise.resolve()}GodotFS._mount_points=persistentPaths.slice();function createRecursive(dir){try{FS.stat(dir)}catch(e){if(e.errno!==GodotFS.ENOENT){GodotRuntime.error(e)}FS.mkdirTree(dir)}}GodotFS._mount_points.forEach(function(path){createRecursive(path);FS.mount(IDBFS,{},path)});return new Promise(function(resolve,reject){FS.syncfs(true,function(err){if(err){GodotFS._mount_points=[];GodotFS._idbfs=false;GodotRuntime.print(`IndexedDB not available: ${err.message}`)}else{GodotFS._idbfs=true}resolve(err)})})},deinit:function(){GodotFS._mount_points.forEach(function(path){try{FS.unmount(path)}catch(e){GodotRuntime.print("Already unmounted",e)}if(GodotFS._idbfs&&IDBFS.dbs[path]){IDBFS.dbs[path].close();delete IDBFS.dbs[path]}});GodotFS._mount_points=[];GodotFS._idbfs=false;GodotFS._syncing=false},sync:function(){if(GodotFS._syncing){GodotRuntime.error("Already syncing!");return Promise.resolve()}GodotFS._syncing=true;return new Promise(function(resolve,reject){FS.syncfs(false,function(error){if(error){GodotRuntime.error(`Failed to save IDB file system: ${error.message}`)}GodotFS._syncing=false;resolve(error)})})},copy_to_fs:function(path,buffer){const idx=path.lastIndexOf("/");let dir="/";if(idx>0){dir=path.slice(0,idx)}try{FS.stat(dir)}catch(e){if(e.errno!==GodotFS.ENOENT){GodotRuntime.error(e)}FS.mkdirTree(dir)}FS.writeFile(path,new Uint8Array(buffer))}};var GodotOS={request_quit:function(){},_async_cbs:[],_fs_sync_promise:null,atexit:function(p_promise_cb){GodotOS._async_cbs.push(p_promise_cb)},cleanup:function(exit_code){const cb=GodotConfig.on_exit;GodotFS.deinit();GodotConfig.clear();if(cb){cb(exit_code)}},finish_async:function(callback){GodotOS._fs_sync_promise.then(function(err){const promises=[];GodotOS._async_cbs.forEach(function(cb){promises.push(new Promise(cb))});return Promise.all(promises)}).then(function(){return GodotFS.sync()}).then(function(err){setTimeout(function(){callback()},0)})}};var GodotAudio={MAX_VOLUME_CHANNELS:8,GodotChannel:{CHANNEL_L:0,CHANNEL_R:1,CHANNEL_C:3,CHANNEL_LFE:4,CHANNEL_RL:5,CHANNEL_RR:6,CHANNEL_SL:7,CHANNEL_SR:8},WebChannel:{CHANNEL_L:0,CHANNEL_R:1,CHANNEL_SL:2,CHANNEL_SR:3,CHANNEL_C:4,CHANNEL_LFE:5},samples:null,Sample:class Sample{static getSample(id){if(!GodotAudio.samples.has(id)){throw new ReferenceError(`Could not find sample "${id}"`)}return GodotAudio.samples.get(id)}static getSampleOrNull(id){return GodotAudio.samples.get(id)??null}static create(params,options={}){const sample=new GodotAudio.Sample(params,options);GodotAudio.samples.set(params.id,sample);return sample}static delete(id){GodotAudio.samples.delete(id)}constructor(params,options={}){this.id=params.id;this._audioBuffer=null;this.numberOfChannels=(options.numberOfChannels??2);this.sampleRate=(options.sampleRate??44100);this.loopMode=(options.loopMode??"disabled");this.loopBegin=(options.loopBegin??0);this.loopEnd=(options.loopEnd??0);this.setAudioBuffer(params.audioBuffer)}getAudioBuffer(){return this._duplicateAudioBuffer()}setAudioBuffer(val){this._audioBuffer=val}clear(){this.setAudioBuffer(null);GodotAudio.Sample.delete(this.id)}_duplicateAudioBuffer(){if(this._audioBuffer==null){throw new Error("couldn't duplicate a null audioBuffer")}const channels=new Array(this._audioBuffer.numberOfChannels);for(let i=0;i{if(self.isPaused){return}switch(self.getSample().loopMode){case"disabled":{const id=this.id;self.stop();if(GodotAudio.sampleFinishedCallback!=null){const idCharPtr=GodotRuntime.allocString(id);GodotAudio.sampleFinishedCallback(idCharPtr);GodotRuntime.free(idCharPtr)}}break;case"forward":case"backward":self.restart();break;default:}};this._source.addEventListener("ended",this._onended)}},buses:null,busSolo:null,Bus:class Bus{static getCount(){return GodotAudio.buses.length}static setCount(val){const buses=GodotAudio.buses;if(val===buses.length){return}if(val=GodotAudio.buses.length){throw new ReferenceError(`invalid bus index "${index}"`)}return GodotAudio.buses[index]}static getBusOrNull(index){if(index<0||index>=GodotAudio.buses.length){return null}return GodotAudio.buses[index]}static move(fromIndex,toIndex){const movedBus=GodotAudio.Bus.getBus(fromIndex);const buses=GodotAudio.buses.filter((_,i)=>i!==fromIndex);buses.splice(toIndex-1,0,movedBus);GodotAudio.buses=buses}static addAt(index){const newBus=GodotAudio.Bus.create();if(index!==newBus.getId()){GodotAudio.Bus.move(newBus.getId(),index)}}static create(){const newBus=new GodotAudio.Bus;const isFirstBus=GodotAudio.buses.length===0;GodotAudio.buses.push(newBus);if(isFirstBus){newBus.setSend(null)}else{newBus.setSend(GodotAudio.Bus.getBus(0))}return newBus}constructor(){this._sampleNodes=new Set;this.isSolo=false;this._send=null;this._gainNode=GodotAudio.ctx.createGain();this._soloNode=GodotAudio.ctx.createGain();this._muteNode=GodotAudio.ctx.createGain();this._gainNode.connect(this._soloNode).connect(this._muteNode)}getId(){return GodotAudio.buses.indexOf(this)}getVolumeDb(){return GodotAudio.linear_to_db(this._gainNode.gain.value)}setVolumeDb(val){const linear=GodotAudio.db_to_linear(val);if(isFinite(linear)){this._gainNode.gain.value=linear}}getSend(){return this._send}setSend(val){this._send=val;if(val==null){if(this.getId()==0){this.getOutputNode().connect(GodotAudio.ctx.destination);return}throw new Error(`Cannot send to "${val}" without the bus being at index 0 (current index: ${this.getId()})`)}this.connect(val)}getInputNode(){return this._gainNode}getOutputNode(){return this._muteNode}mute(enable){this._muteNode.gain.value=enable?0:1}solo(enable){if(this.isSolo===enable){return}if(enable){if(GodotAudio.busSolo!=null&&GodotAudio.busSolo!==this){GodotAudio.busSolo._disableSolo()}this._enableSolo();return}this._disableSolo()}addSampleNode(sampleNode){this._sampleNodes.add(sampleNode);sampleNode.getOutputNode().connect(this.getInputNode())}removeSampleNode(sampleNode){this._sampleNodes.delete(sampleNode);sampleNode.getOutputNode().disconnect()}connect(bus){if(bus==null){throw new Error("cannot connect to null bus")}this.getOutputNode().disconnect();this.getOutputNode().connect(bus.getInputNode());return bus}clear(){GodotAudio.buses=GodotAudio.buses.filter(v=>v!==this)}_syncSampleNodes(){const sampleNodes=Array.from(this._sampleNodes);for(let i=0;iotherBus!==this);for(let i=0;iotherBus!==this);for(let i=0;iGodotAudio.Bus.getBus(busIndex));sampleNode.setVolumes(buses,volumes)},set_sample_bus_count:function(count){GodotAudio.Bus.setCount(count)},remove_sample_bus:function(index){const bus=GodotAudio.Bus.getBus(index);bus.clear()},add_sample_bus:function(atPos){GodotAudio.Bus.addAt(atPos)},move_sample_bus:function(busIndex,toPos){GodotAudio.Bus.move(busIndex,toPos)},set_sample_bus_send:function(busIndex,sendIndex){const bus=GodotAudio.Bus.getBus(busIndex);bus.setSend(GodotAudio.Bus.getBus(sendIndex))},set_sample_bus_volume_db:function(busIndex,volumeDb){const bus=GodotAudio.Bus.getBus(busIndex);bus.setVolumeDb(volumeDb)},set_sample_bus_solo:function(busIndex,enable){const bus=GodotAudio.Bus.getBus(busIndex);bus.solo(enable)},set_sample_bus_mute:function(busIndex,enable){const bus=GodotAudio.Bus.getBus(busIndex);bus.mute(enable)}};function _godot_audio_has_worklet(){return GodotAudio.ctx&&GodotAudio.ctx.audioWorklet?1:0}function _godot_audio_init(p_mix_rate,p_latency,p_state_change,p_latency_update){const statechange=GodotRuntime.get_func(p_state_change);const latencyupdate=GodotRuntime.get_func(p_latency_update);const mix_rate=GodotRuntime.getHeapValue(p_mix_rate,"i32");const channels=GodotAudio.init(mix_rate,p_latency,statechange,latencyupdate);GodotRuntime.setHeapValue(p_mix_rate,GodotAudio.ctx.sampleRate,"i32");return channels}function _godot_audio_input_start(){return GodotAudio.create_input(function(input){input.connect(GodotAudio.driver.get_node())})}function _godot_audio_input_stop(){if(GodotAudio.input){const tracks=GodotAudio.input["mediaStream"]["getTracks"]();for(let i=0;i=size){const high=size-wpos;wbuf.set(buffer.subarray(wpos,size));pending_samples-=high;wpos=0}if(pending_samples>0){wbuf.set(buffer.subarray(wpos,wpos+pending_samples),tot_sent-pending_samples)}port.postMessage({"cmd":"chunk","data":wbuf.subarray(0,tot_sent)});wpos+=pending_samples;pending_samples=0}this.receive=function(recv_buf){const buffer=GodotRuntime.heapSub(HEAPF32,p_in_buf,p_in_size);const from=rpos;let to_write=recv_buf.length;let high=0;if(rpos+to_write>=p_in_size){high=p_in_size-rpos;buffer.set(recv_buf.subarray(0,high),rpos);to_write-=high;rpos=0}if(to_write){buffer.set(recv_buf.subarray(high,to_write),rpos)}in_callback(from,recv_buf.length);rpos+=to_write};this.consumed=function(size,port){pending_samples+=size;send(port)}}GodotAudioWorklet.ring_buffer=new RingBuffer;GodotAudioWorklet.promise.then(function(){const node=GodotAudioWorklet.worklet;const buffer=GodotRuntime.heapSlice(HEAPF32,p_out_buf,p_out_size);node.connect(GodotAudio.ctx.destination);node.port.postMessage({"cmd":"start_nothreads","data":[buffer,p_in_size]});node.port.onmessage=function(event){if(!GodotAudioWorklet.worklet){return}if(event.data["cmd"]==="read"){const read=event.data["data"];GodotAudioWorklet.ring_buffer.consumed(read,GodotAudioWorklet.worklet.port)}else if(event.data["cmd"]==="input"){const buf=event.data["data"];if(buf.length>p_in_size){GodotRuntime.error("Input chunk is too big");return}GodotAudioWorklet.ring_buffer.receive(buf)}else{GodotRuntime.error(event.data)}}})},get_node:function(){return GodotAudioWorklet.worklet},close:function(){return new Promise(function(resolve,reject){if(GodotAudioWorklet.promise===null){return}const p=GodotAudioWorklet.promise;p.then(function(){GodotAudioWorklet.worklet.port.postMessage({"cmd":"stop","data":null});GodotAudioWorklet.worklet.disconnect();GodotAudioWorklet.worklet.port.onmessage=null;GodotAudioWorklet.worklet=null;GodotAudioWorklet.promise=null;resolve()}).catch(function(err){GodotRuntime.error(err)})})}};function _godot_audio_worklet_create(channels){try{GodotAudioWorklet.create(channels)}catch(e){GodotRuntime.error("Error starting AudioDriverWorklet",e);return 1}return 0}function _godot_audio_worklet_start_no_threads(p_out_buf,p_out_size,p_out_callback,p_in_buf,p_in_size,p_in_callback){const out_callback=GodotRuntime.get_func(p_out_callback);const in_callback=GodotRuntime.get_func(p_in_callback);GodotAudioWorklet.start_no_threads(p_out_buf,p_out_size,out_callback,p_in_buf,p_in_size,in_callback)}function _godot_js_config_canvas_id_get(p_ptr,p_ptr_max){GodotRuntime.stringToHeap(`#${GodotConfig.canvas.id}`,p_ptr,p_ptr_max)}function _godot_js_config_locale_get(p_ptr,p_ptr_max){GodotRuntime.stringToHeap(GodotConfig.locale,p_ptr,p_ptr_max)}var GodotDisplayCursor={shape:"default",visible:true,cursors:{},set_style:function(style){GodotConfig.canvas.style.cursor=style},set_shape:function(shape){GodotDisplayCursor.shape=shape;let css=shape;if(shape in GodotDisplayCursor.cursors){const c=GodotDisplayCursor.cursors[shape];css=`url("${c.url}") ${c.x} ${c.y}, default`}if(GodotDisplayCursor.visible){GodotDisplayCursor.set_style(css)}},clear:function(){GodotDisplayCursor.set_style("");GodotDisplayCursor.shape="default";GodotDisplayCursor.visible=true;Object.keys(GodotDisplayCursor.cursors).forEach(function(key){URL.revokeObjectURL(GodotDisplayCursor.cursors[key]);delete GodotDisplayCursor.cursors[key]})},lockPointer:function(){const canvas=GodotConfig.canvas;if(canvas.requestPointerLock){canvas.requestPointerLock()}},releasePointer:function(){if(document.exitPointerLock){document.exitPointerLock()}},isPointerLocked:function(){return document.pointerLockElement===GodotConfig.canvas}};var GodotEventListeners={handlers:[],has:function(target,event,method,capture){return GodotEventListeners.handlers.findIndex(function(e){return e.target===target&&e.event===event&&e.method===method&&e.capture===capture})!==-1},add:function(target,event,method,capture){if(GodotEventListeners.has(target,event,method,capture)){return}function Handler(p_target,p_event,p_method,p_capture){this.target=p_target;this.event=p_event;this.method=p_method;this.capture=p_capture}GodotEventListeners.handlers.push(new Handler(target,event,method,capture));target.addEventListener(event,method,capture)},clear:function(){GodotEventListeners.handlers.forEach(function(h){h.target.removeEventListener(h.event,h.method,h.capture)});GodotEventListeners.handlers.length=0}};function _emscripten_webgl_do_get_current_context(){return GL.currentContext?GL.currentContext.handle:0}var _emscripten_webgl_get_current_context=_emscripten_webgl_do_get_current_context;var GodotDisplayScreen={desired_size:[0,0],hidpi:true,getPixelRatio:function(){return GodotDisplayScreen.hidpi?window.devicePixelRatio||1:1},isFullscreen:function(){const elem=document.fullscreenElement||document.mozFullscreenElement||document.webkitFullscreenElement||document.msFullscreenElement;if(elem){return elem===GodotConfig.canvas}return document.fullscreen||document.mozFullScreen||document.webkitIsFullscreen},hasFullscreen:function(){return document.fullscreenEnabled||document.mozFullScreenEnabled||document.webkitFullscreenEnabled},requestFullscreen:function(){if(!GodotDisplayScreen.hasFullscreen()){return 1}const canvas=GodotConfig.canvas;try{const promise=(canvas.requestFullscreen||canvas.msRequestFullscreen||canvas.mozRequestFullScreen||canvas.mozRequestFullscreen||canvas.webkitRequestFullscreen).call(canvas);if(promise){promise.catch(function(){})}}catch(e){return 1}return 0},exitFullscreen:function(){if(!GodotDisplayScreen.isFullscreen()){return 0}try{const promise=document.exitFullscreen();if(promise){promise.catch(function(){})}}catch(e){return 1}return 0},_updateGL:function(){const gl_context_handle=_emscripten_webgl_get_current_context();const gl=GL.getContext(gl_context_handle);if(gl){GL.resizeOffscreenFramebuffer(gl)}},updateSize:function(){const isFullscreen=GodotDisplayScreen.isFullscreen();const wantsFullWindow=GodotConfig.canvas_resize_policy===2;const noResize=GodotConfig.canvas_resize_policy===0;const dWidth=GodotDisplayScreen.desired_size[0];const dHeight=GodotDisplayScreen.desired_size[1];const canvas=GodotConfig.canvas;let width=dWidth;let height=dHeight;if(noResize){if(canvas.width!==width||canvas.height!==height){GodotDisplayScreen.desired_size=[canvas.width,canvas.height];GodotDisplayScreen._updateGL();return 1}return 0}const scale=GodotDisplayScreen.getPixelRatio();if(isFullscreen||wantsFullWindow){width=window.innerWidth*scale;height=window.innerHeight*scale}const csw=`${width/scale}px`;const csh=`${height/scale}px`;if(canvas.style.width!==csw||canvas.style.height!==csh||canvas.width!==width||canvas.height!==height){canvas.width=width;canvas.height=height;canvas.style.width=csw;canvas.style.height=csh;GodotDisplayScreen._updateGL();return 1}return 0}};var GodotDisplayVK={textinput:null,textarea:null,available:function(){return GodotConfig.virtual_keyboard&&"ontouchstart"in window},init:function(input_cb){function create(what){const elem=document.createElement(what);elem.style.display="none";elem.style.position="absolute";elem.style.zIndex="-1";elem.style.background="transparent";elem.style.padding="0px";elem.style.margin="0px";elem.style.overflow="hidden";elem.style.width="0px";elem.style.height="0px";elem.style.border="0px";elem.style.outline="none";elem.readonly=true;elem.disabled=true;GodotEventListeners.add(elem,"input",function(evt){const c_str=GodotRuntime.allocString(elem.value);input_cb(c_str,elem.selectionEnd);GodotRuntime.free(c_str)},false);GodotEventListeners.add(elem,"blur",function(evt){elem.style.display="none";elem.readonly=true;elem.disabled=true},false);GodotConfig.canvas.insertAdjacentElement("beforebegin",elem);return elem}GodotDisplayVK.textinput=create("input");GodotDisplayVK.textarea=create("textarea");GodotDisplayVK.updateSize()},show:function(text,type,start,end){if(!GodotDisplayVK.textinput||!GodotDisplayVK.textarea){return}if(GodotDisplayVK.textinput.style.display!==""||GodotDisplayVK.textarea.style.display!==""){GodotDisplayVK.hide()}GodotDisplayVK.updateSize();let elem=GodotDisplayVK.textinput;switch(type){case 0:elem.type="text";elem.inputmode="";break;case 1:elem=GodotDisplayVK.textarea;break;case 2:elem.type="text";elem.inputmode="numeric";break;case 3:elem.type="text";elem.inputmode="decimal";break;case 4:elem.type="tel";elem.inputmode="";break;case 5:elem.type="email";elem.inputmode="";break;case 6:elem.type="password";elem.inputmode="";break;case 7:elem.type="url";elem.inputmode="";break;default:elem.type="text";elem.inputmode="";break}elem.readonly=false;elem.disabled=false;elem.value=text;elem.style.display="block";elem.focus();elem.setSelectionRange(start,end)},hide:function(){if(!GodotDisplayVK.textinput||!GodotDisplayVK.textarea){return}[GodotDisplayVK.textinput,GodotDisplayVK.textarea].forEach(function(elem){elem.blur();elem.style.display="none";elem.value=""})},updateSize:function(){if(!GodotDisplayVK.textinput||!GodotDisplayVK.textarea){return}const rect=GodotConfig.canvas.getBoundingClientRect();function update(elem){elem.style.left=`${rect.left}px`;elem.style.top=`${rect.top}px`;elem.style.width=`${rect.width}px`;elem.style.height=`${rect.height}px`}update(GodotDisplayVK.textinput);update(GodotDisplayVK.textarea)},clear:function(){if(GodotDisplayVK.textinput){GodotDisplayVK.textinput.remove();GodotDisplayVK.textinput=null}if(GodotDisplayVK.textarea){GodotDisplayVK.textarea.remove();GodotDisplayVK.textarea=null}}};var GodotDisplay={window_icon:"",getDPI:function(){const dpi=Math.round(window.devicePixelRatio*96);return dpi>=96?dpi:96}};function _godot_js_display_alert(p_text){window.alert(GodotRuntime.parseString(p_text))}function _godot_js_display_canvas_focus(){GodotConfig.canvas.focus()}function _godot_js_display_canvas_is_focused(){return document.activeElement===GodotConfig.canvas}function _godot_js_display_clipboard_get(callback){const func=GodotRuntime.get_func(callback);try{navigator.clipboard.readText().then(function(result){const ptr=GodotRuntime.allocString(result);func(ptr);GodotRuntime.free(ptr)}).catch(function(e){})}catch(e){}}function _godot_js_display_clipboard_set(p_text){const text=GodotRuntime.parseString(p_text);if(!navigator.clipboard||!navigator.clipboard.writeText){return 1}navigator.clipboard.writeText(text).catch(function(e){GodotRuntime.error("Setting OS clipboard is only possible from an input callback for the Web platform. Exception:",e)});return 0}function _godot_js_display_cursor_is_hidden(){return!GodotDisplayCursor.visible}function _godot_js_display_cursor_is_locked(){return GodotDisplayCursor.isPointerLocked()?1:0}function _godot_js_display_cursor_lock_set(p_lock){if(p_lock){GodotDisplayCursor.lockPointer()}else{GodotDisplayCursor.releasePointer()}}function _godot_js_display_cursor_set_custom_shape(p_shape,p_ptr,p_len,p_hotspot_x,p_hotspot_y){const shape=GodotRuntime.parseString(p_shape);const old_shape=GodotDisplayCursor.cursors[shape];if(p_len>0){const png=new Blob([GodotRuntime.heapSlice(HEAPU8,p_ptr,p_len)],{type:"image/png"});const url=URL.createObjectURL(png);GodotDisplayCursor.cursors[shape]={url:url,x:p_hotspot_x,y:p_hotspot_y}}else{delete GodotDisplayCursor.cursors[shape]}if(shape===GodotDisplayCursor.shape){GodotDisplayCursor.set_shape(GodotDisplayCursor.shape)}if(old_shape){URL.revokeObjectURL(old_shape.url)}}function _godot_js_display_cursor_set_shape(p_string){GodotDisplayCursor.set_shape(GodotRuntime.parseString(p_string))}function _godot_js_display_cursor_set_visible(p_visible){const visible=p_visible!==0;if(visible===GodotDisplayCursor.visible){return}GodotDisplayCursor.visible=visible;if(visible){GodotDisplayCursor.set_shape(GodotDisplayCursor.shape)}else{GodotDisplayCursor.set_style("none")}}function _godot_js_display_desired_size_set(width,height){GodotDisplayScreen.desired_size=[width,height];GodotDisplayScreen.updateSize()}function _godot_js_display_fullscreen_cb(callback){const canvas=GodotConfig.canvas;const func=GodotRuntime.get_func(callback);function change_cb(evt){if(evt.target===canvas){func(GodotDisplayScreen.isFullscreen())}}GodotEventListeners.add(document,"fullscreenchange",change_cb,false);GodotEventListeners.add(document,"mozfullscreenchange",change_cb,false);GodotEventListeners.add(document,"webkitfullscreenchange",change_cb,false)}function _godot_js_display_fullscreen_exit(){return GodotDisplayScreen.exitFullscreen()}function _godot_js_display_fullscreen_request(){return GodotDisplayScreen.requestFullscreen()}function _godot_js_display_has_webgl(p_version){if(p_version!==1&&p_version!==2){return false}try{return!!document.createElement("canvas").getContext(p_version===2?"webgl2":"webgl")}catch(e){}return false}function _godot_js_display_is_swap_ok_cancel(){const win=["Windows","Win64","Win32","WinCE"];const plat=navigator.platform||"";if(win.indexOf(plat)!==-1){return 1}return 0}function _godot_js_display_notification_cb(callback,p_enter,p_exit,p_in,p_out){const canvas=GodotConfig.canvas;const func=GodotRuntime.get_func(callback);const notif=[p_enter,p_exit,p_in,p_out];["mouseover","mouseleave","focus","blur"].forEach(function(evt_name,idx){GodotEventListeners.add(canvas,evt_name,function(){func(notif[idx])},true)})}function _godot_js_display_pixel_ratio_get(){return GodotDisplayScreen.getPixelRatio()}function _godot_js_display_screen_dpi_get(){return GodotDisplay.getDPI()}function _godot_js_display_screen_size_get(width,height){const scale=GodotDisplayScreen.getPixelRatio();GodotRuntime.setHeapValue(width,window.screen.width*scale,"i32");GodotRuntime.setHeapValue(height,window.screen.height*scale,"i32")}function _godot_js_display_setup_canvas(p_width,p_height,p_fullscreen,p_hidpi){const canvas=GodotConfig.canvas;GodotEventListeners.add(canvas,"contextmenu",function(ev){ev.preventDefault()},false);GodotEventListeners.add(canvas,"webglcontextlost",function(ev){alert("WebGL context lost, please reload the page");ev.preventDefault()},false);GodotDisplayScreen.hidpi=!!p_hidpi;switch(GodotConfig.canvas_resize_policy){case 0:GodotDisplayScreen.desired_size=[canvas.width,canvas.height];break;case 1:GodotDisplayScreen.desired_size=[p_width,p_height];break;default:canvas.style.position="absolute";canvas.style.top=0;canvas.style.left=0;break}GodotDisplayScreen.updateSize();if(p_fullscreen){GodotDisplayScreen.requestFullscreen()}}function _godot_js_display_size_update(){const updated=GodotDisplayScreen.updateSize();if(updated){GodotDisplayVK.updateSize()}return updated}function _godot_js_display_touchscreen_is_available(){return"ontouchstart"in window}function _godot_js_display_tts_available(){return"speechSynthesis"in window}function _godot_js_display_vk_available(){return GodotDisplayVK.available()}function _godot_js_display_vk_cb(p_input_cb){const input_cb=GodotRuntime.get_func(p_input_cb);if(GodotDisplayVK.available()){GodotDisplayVK.init(input_cb)}}function _godot_js_display_vk_hide(){GodotDisplayVK.hide()}function _godot_js_display_vk_show(p_text,p_type,p_start,p_end){const text=GodotRuntime.parseString(p_text);const start=p_start>0?p_start:0;const end=p_end>0?p_end:start;GodotDisplayVK.show(text,p_type,start,end)}function _godot_js_display_window_blur_cb(callback){const func=GodotRuntime.get_func(callback);GodotEventListeners.add(window,"blur",function(){func()},false)}function _godot_js_display_window_icon_set(p_ptr,p_len){let link=document.getElementById("-gd-engine-icon");const old_icon=GodotDisplay.window_icon;if(p_ptr){if(link===null){link=document.createElement("link");link.rel="icon";link.id="-gd-engine-icon";document.head.appendChild(link)}const png=new Blob([GodotRuntime.heapSlice(HEAPU8,p_ptr,p_len)],{type:"image/png"});GodotDisplay.window_icon=URL.createObjectURL(png);link.href=GodotDisplay.window_icon}else{if(link){link.remove()}GodotDisplay.window_icon=null}if(old_icon){URL.revokeObjectURL(old_icon)}}function _godot_js_display_window_size_get(p_width,p_height){GodotRuntime.setHeapValue(p_width,GodotConfig.canvas.width,"i32");GodotRuntime.setHeapValue(p_height,GodotConfig.canvas.height,"i32")}function _godot_js_display_window_title_set(p_data){document.title=GodotRuntime.parseString(p_data)}function _godot_js_eval(p_js,p_use_global_ctx,p_union_ptr,p_byte_arr,p_byte_arr_write,p_callback){const js_code=GodotRuntime.parseString(p_js);let eval_ret=null;try{if(p_use_global_ctx){const global_eval=eval;eval_ret=global_eval(js_code)}else{eval_ret=eval(js_code)}}catch(e){GodotRuntime.error(e)}switch(typeof eval_ret){case"boolean":GodotRuntime.setHeapValue(p_union_ptr,eval_ret,"i32");return 1;case"number":GodotRuntime.setHeapValue(p_union_ptr,eval_ret,"double");return 3;case"string":GodotRuntime.setHeapValue(p_union_ptr,GodotRuntime.allocString(eval_ret),"*");return 4;case"object":if(eval_ret===null){break}if(ArrayBuffer.isView(eval_ret)&&!(eval_ret instanceof Uint8Array)){eval_ret=new Uint8Array(eval_ret.buffer)}else if(eval_ret instanceof ArrayBuffer){eval_ret=new Uint8Array(eval_ret)}if(eval_ret instanceof Uint8Array){const func=GodotRuntime.get_func(p_callback);const bytes_ptr=func(p_byte_arr,p_byte_arr_write,eval_ret.length);HEAPU8.set(eval_ret,bytes_ptr);return 29}break}return 0}var IDHandler={_last_id:0,_references:{},get:function(p_id){return IDHandler._references[p_id]},add:function(p_data){const id=++IDHandler._last_id;IDHandler._references[id]=p_data;return id},remove:function(p_id){delete IDHandler._references[p_id]}};var GodotFetch={onread:function(id,result){const obj=IDHandler.get(id);if(!obj){return}if(result.value){obj.chunks.push(result.value)}obj.reading=false;obj.done=result.done},onresponse:function(id,response){const obj=IDHandler.get(id);if(!obj){return}let chunked=false;response.headers.forEach(function(value,header){const v=value.toLowerCase().trim();const h=header.toLowerCase().trim();if(h==="transfer-encoding"&&v==="chunked"){chunked=true}});obj.status=response.status;obj.response=response;obj.reader=response.body.getReader();obj.chunked=chunked},onerror:function(id,err){GodotRuntime.error(err);const obj=IDHandler.get(id);if(!obj){return}obj.error=err},create:function(method,url,headers,body){const obj={request:null,response:null,reader:null,error:null,done:false,reading:false,status:0,chunks:[]};const id=IDHandler.add(obj);const init={method:method,headers:headers,body:body};obj.request=fetch(url,init);obj.request.then(GodotFetch.onresponse.bind(null,id)).catch(GodotFetch.onerror.bind(null,id));return id},free:function(id){const obj=IDHandler.get(id);if(!obj){return}IDHandler.remove(id);if(!obj.request){return}obj.request.then(function(response){response.abort()}).catch(function(e){})},read:function(id){const obj=IDHandler.get(id);if(!obj){return}if(obj.reader&&!obj.reading){if(obj.done){obj.reader=null;return}obj.reading=true;obj.reader.read().then(GodotFetch.onread.bind(null,id)).catch(GodotFetch.onerror.bind(null,id))}}};function _godot_js_fetch_create(p_method,p_url,p_headers,p_headers_size,p_body,p_body_size){const method=GodotRuntime.parseString(p_method);const url=GodotRuntime.parseString(p_url);const headers=GodotRuntime.parseStringArray(p_headers,p_headers_size);const body=p_body_size?GodotRuntime.heapSlice(HEAP8,p_body,p_body_size):null;return GodotFetch.create(method,url,headers.map(function(hv){const idx=hv.indexOf(":");if(idx<=0){return[]}return[hv.slice(0,idx).trim(),hv.slice(idx+1).trim()]}).filter(function(v){return v.length===2}),body)}function _godot_js_fetch_free(id){GodotFetch.free(id)}function _godot_js_fetch_http_status_get(p_id){const obj=IDHandler.get(p_id);if(!obj||!obj.response){return 0}return obj.status}function _godot_js_fetch_is_chunked(p_id){const obj=IDHandler.get(p_id);if(!obj||!obj.response){return-1}return obj.chunked?1:0}function _godot_js_fetch_read_chunk(p_id,p_buf,p_buf_size){const obj=IDHandler.get(p_id);if(!obj||!obj.response){return 0}let to_read=p_buf_size;const chunks=obj.chunks;while(to_read&&chunks.length){const chunk=obj.chunks[0];if(chunk.length>to_read){GodotRuntime.heapCopy(HEAP8,chunk.slice(0,to_read),p_buf);chunks[0]=chunk.slice(to_read);to_read=0}else{GodotRuntime.heapCopy(HEAP8,chunk,p_buf);to_read-=chunk.length;chunks.pop()}}if(!chunks.length){GodotFetch.read(p_id)}return p_buf_size-to_read}function _godot_js_fetch_read_headers(p_id,p_parse_cb,p_ref){const obj=IDHandler.get(p_id);if(!obj||!obj.response){return 1}const cb=GodotRuntime.get_func(p_parse_cb);const arr=[];obj.response.headers.forEach(function(v,h){arr.push(`${h}:${v}`)});const c_ptr=GodotRuntime.allocStringArray(arr);cb(arr.length,c_ptr,p_ref);GodotRuntime.freeStringArray(c_ptr,arr.length);return 0}function _godot_js_fetch_state_get(p_id){const obj=IDHandler.get(p_id);if(!obj){return-1}if(obj.error){return-1}if(!obj.response){return 0}if(obj.reader){return 1}if(obj.done){return 2}return-1}var GodotInputGamepads={samples:[],get_pads:function(){try{const pads=navigator.getGamepads();if(pads){return pads}return[]}catch(e){return[]}},get_samples:function(){return GodotInputGamepads.samples},get_sample:function(index){const samples=GodotInputGamepads.samples;return index=0){os="Android"}else if(ua.indexOf("Linux")>=0){os="Linux"}else if(ua.indexOf("iPhone")>=0){os="iOS"}else if(ua.indexOf("Macintosh")>=0){os="MacOSX"}else if(ua.indexOf("Windows")>=0){os="Windows"}const id=pad.id;const exp1=/vendor: ([0-9a-f]{4}) product: ([0-9a-f]{4})/i;const exp2=/^([0-9a-f]+)-([0-9a-f]+)-/i;let vendor="";let product="";if(exp1.test(id)){const match=exp1.exec(id);vendor=match[1].padStart(4,"0");product=match[2].padStart(4,"0")}else if(exp2.test(id)){const match=exp2.exec(id);vendor=match[1].padStart(4,"0");product=match[2].padStart(4,"0")}if(!vendor||!product){return`${os}Unknown`}return os+vendor+product}};var GodotInputDragDrop={promises:[],pending_files:[],add_entry:function(entry){if(entry.isDirectory){GodotInputDragDrop.add_dir(entry)}else if(entry.isFile){GodotInputDragDrop.add_file(entry)}else{GodotRuntime.error("Unrecognized entry...",entry)}},add_dir:function(entry){GodotInputDragDrop.promises.push(new Promise(function(resolve,reject){const reader=entry.createReader();reader.readEntries(function(entries){for(let i=0;i{const path=elem["path"];GodotFS.copy_to_fs(DROP+path,elem["data"]);let idx=path.indexOf("/");if(idx===-1){drops.push(DROP+path)}else{const sub=path.substr(0,idx);idx=sub.indexOf("/");if(idx<0&&drops.indexOf(DROP+sub)===-1){drops.push(DROP+sub)}}files.push(DROP+path)});GodotInputDragDrop.promises=[];GodotInputDragDrop.pending_files=[];callback(drops);if(GodotConfig.persistent_drops){GodotOS.atexit(function(resolve,reject){GodotInputDragDrop.remove_drop(files,DROP);resolve()})}else{GodotInputDragDrop.remove_drop(files,DROP)}})},remove_drop:function(files,drop_path){const dirs=[drop_path.substr(0,drop_path.length-1)];files.forEach(function(file){FS.unlink(file);let dir=file.replace(drop_path,"");let idx=dir.lastIndexOf("/");while(idx>0){dir=dir.substr(0,idx);if(dirs.indexOf(drop_path+dir)===-1){dirs.push(drop_path+dir)}idx=dir.lastIndexOf("/")}});dirs.sort(function(a,b){const al=(a.match(/\//g)||[]).length;const bl=(b.match(/\//g)||[]).length;if(al>bl){return-1}else if(al=Number.MIN_SAFE_INTEGER&&heap_value<=Number.MAX_SAFE_INTEGER?Number(heap_value):heap_value}case 3:return Number(GodotRuntime.getHeapValue(val,"double"));case 4:return GodotRuntime.parseString(GodotRuntime.getHeapValue(val,"*"));case 24:return GodotJSWrapper.get_proxied_value(GodotRuntime.getHeapValue(val,"i64"));default:return undefined}},js2variant:function(p_val,p_exchange){if(p_val===undefined||p_val===null){return 0}const type=typeof p_val;if(type==="boolean"){GodotRuntime.setHeapValue(p_exchange,p_val,"i64");return 1}else if(type==="number"){if(Number.isInteger(p_val)){GodotRuntime.setHeapValue(p_exchange,p_val,"i64");return 2}GodotRuntime.setHeapValue(p_exchange,p_val,"double");return 3}else if(type==="bigint"){GodotRuntime.setHeapValue(p_exchange,p_val,"i64");return 2}else if(type==="string"){const c_str=GodotRuntime.allocString(p_val);GodotRuntime.setHeapValue(p_exchange,c_str,"*");return 4}const id=GodotJSWrapper.get_proxied(p_val);GodotRuntime.setHeapValue(p_exchange,id,"i64");return 24}};function _godot_js_wrapper_create_cb(p_ref,p_func){const func=GodotRuntime.get_func(p_func);let id=0;const cb=function(){if(!GodotJSWrapper.get_proxied_value(id)){return undefined}GodotJSWrapper.cb_ret=null;const args=Array.from(arguments);const argsProxy=new GodotJSWrapper.MyProxy(args);func(p_ref,argsProxy.get_id(),args.length);argsProxy.unref();const ret=GodotJSWrapper.cb_ret;GodotJSWrapper.cb_ret=null;return ret};id=GodotJSWrapper.get_proxied(cb);return id}function _godot_js_wrapper_create_object(p_object,p_args,p_argc,p_convert_callback,p_exchange,p_lock,p_free_lock_callback){const name=GodotRuntime.parseString(p_object);if(typeof window[name]==="undefined"){return-1}const convert=GodotRuntime.get_func(p_convert_callback);const freeLock=GodotRuntime.get_func(p_free_lock_callback);const args=new Array(p_argc);for(let i=0;i{if(GodotWebXR.session&&GodotWebXR.space){const onFrame=function(time,frame){GodotWebXR.frame=frame;GodotWebXR.pose=frame.getViewerPose(GodotWebXR.space);callback(time);GodotWebXR.frame=null;GodotWebXR.pose=null};GodotWebXR.session.requestAnimationFrame(onFrame)}else{GodotWebXR.orig_requestAnimationFrame(callback)}},monkeyPatchRequestAnimationFrame:enable=>{if(GodotWebXR.orig_requestAnimationFrame===null){GodotWebXR.orig_requestAnimationFrame=Browser.requestAnimationFrame}Browser.requestAnimationFrame=enable?GodotWebXR.requestAnimationFrame:GodotWebXR.orig_requestAnimationFrame},pauseResumeMainLoop:()=>{Browser.mainLoop.pause();runtimeKeepalivePush();window.setTimeout(function(){runtimeKeepalivePop();Browser.mainLoop.resume()},0)},getLayer:()=>{const new_view_count=GodotWebXR.pose?GodotWebXR.pose.views.length:1;let layer=GodotWebXR.layer;if(layer&&GodotWebXR.view_count===new_view_count){return layer}if(!GodotWebXR.session||!GodotWebXR.gl_binding){return null}const gl=GodotWebXR.gl;layer=GodotWebXR.gl_binding.createProjectionLayer({textureType:new_view_count>1?"texture-array":"texture",colorFormat:gl.RGBA8,depthFormat:gl.DEPTH_COMPONENT24});GodotWebXR.session.updateRenderState({layers:[layer]});GodotWebXR.layer=layer;GodotWebXR.view_count=new_view_count;return layer},getSubImage:()=>{if(!GodotWebXR.pose){return null}const layer=GodotWebXR.getLayer();if(layer===null){return null}return GodotWebXR.gl_binding.getViewSubImage(layer,GodotWebXR.pose.views[0])},getTextureId:texture=>{if(texture.name!==undefined){return texture.name}const id=GL.getNewId(GL.textures);texture.name=id;GL.textures[id]=texture;return id},addInputSource:input_source=>{let name=-1;if(input_source.targetRayMode==="tracked-pointer"&&input_source.handedness==="left"){name=0}else if(input_source.targetRayMode==="tracked-pointer"&&input_source.handedness==="right"){name=1}else{for(let i=2;i<16;i++){if(!GodotWebXR.input_sources[i]){name=i;break}}}if(name>=0){GodotWebXR.input_sources[name]=input_source;input_source.name=name;if(input_source.targetRayMode==="screen"){let touch_index=-1;for(let i=0;i<5;i++){if(!GodotWebXR.touches[i]){touch_index=i;break}}if(touch_index>=0){GodotWebXR.touches[touch_index]=input_source;input_source.touch_index=touch_index}}}return name},removeInputSource:input_source=>{if(input_source.name!==undefined){const name=input_source.name;if(name>=0&&name<16){GodotWebXR.input_sources[name]=null}if(input_source.touch_index!==undefined){const touch_index=input_source.touch_index;if(touch_index>=0&&touch_index<5){GodotWebXR.touches[touch_index]=null}}return name}return-1},getInputSourceId:input_source=>{if(input_source!==undefined){return input_source.name}return-1},getTouchIndex:input_source=>{if(input_source.touch_index!==undefined){return input_source.touch_index}return-1}};function _godot_webxr_get_bounds_geometry(r_points){if(!GodotWebXR.space||!GodotWebXR.space.boundsGeometry){return 0}const point_count=GodotWebXR.space.boundsGeometry.length;if(point_count===0){return 0}const buf=GodotRuntime.malloc(point_count*3*4);for(let i=0;i=0){matrix=views[p_view].transform.matrix}else{matrix=GodotWebXR.pose.transform.matrix}for(let i=0;i<16;i++){GodotRuntime.setHeapValue(r_transform+i*4,matrix[i],"float")}return true}function _godot_webxr_get_velocity_texture(){const subimage=GodotWebXR.getSubImage();if(subimage===null){return 0}if(!subimage.motionVectorTexture){return 0}return GodotWebXR.getTextureId(subimage.motionVectorTexture)}function _godot_webxr_get_view_count(){if(!GodotWebXR.session||!GodotWebXR.pose){return 1}const view_count=GodotWebXR.pose.views.length;return view_count>0?view_count:1}function _godot_webxr_get_visibility_state(){if(!GodotWebXR.session||!GodotWebXR.session.visibilityState){return 0}return GodotRuntime.allocString(GodotWebXR.session.visibilityState)}function _godot_webxr_initialize(p_session_mode,p_required_features,p_optional_features,p_requested_reference_spaces,p_on_session_started,p_on_session_ended,p_on_session_failed,p_on_input_event,p_on_simple_event){GodotWebXR.monkeyPatchRequestAnimationFrame(true);const session_mode=GodotRuntime.parseString(p_session_mode);const required_features=GodotRuntime.parseString(p_required_features).split(",").map(s=>s.trim()).filter(s=>s!=="");const optional_features=GodotRuntime.parseString(p_optional_features).split(",").map(s=>s.trim()).filter(s=>s!=="");const requested_reference_space_types=GodotRuntime.parseString(p_requested_reference_spaces).split(",").map(s=>s.trim());const onstarted=GodotRuntime.get_func(p_on_session_started);const onended=GodotRuntime.get_func(p_on_session_ended);const onfailed=GodotRuntime.get_func(p_on_session_failed);const oninputevent=GodotRuntime.get_func(p_on_input_event);const onsimpleevent=GodotRuntime.get_func(p_on_simple_event);const session_init={};if(required_features.length>0){session_init["requiredFeatures"]=required_features}if(optional_features.length>0){session_init["optionalFeatures"]=optional_features}navigator.xr.requestSession(session_mode,session_init).then(function(session){GodotWebXR.session=session;session.addEventListener("end",function(evt){onended()});session.addEventListener("inputsourceschange",function(evt){evt.added.forEach(GodotWebXR.addInputSource);evt.removed.forEach(GodotWebXR.removeInputSource)});["selectstart","selectend","squeezestart","squeezeend"].forEach((input_event,index)=>{session.addEventListener(input_event,function(evt){GodotWebXR.frame=evt.frame;oninputevent(index,GodotWebXR.getInputSourceId(evt.inputSource));GodotWebXR.frame=null})});session.addEventListener("visibilitychange",function(evt){const c_str=GodotRuntime.allocString("visibility_state_changed");onsimpleevent(c_str);GodotRuntime.free(c_str)});GodotWebXR.onsimpleevent=onsimpleevent;const gl_context_handle=_emscripten_webgl_get_current_context();const gl=GL.getContext(gl_context_handle).GLctx;GodotWebXR.gl=gl;gl.makeXRCompatible().then(function(){GodotWebXR.gl_binding=new XRWebGLBinding(session,gl);GodotWebXR.getLayer();function onReferenceSpaceSuccess(reference_space,reference_space_type){GodotWebXR.space=reference_space;reference_space.onreset=function(evt){const c_str=GodotRuntime.allocString("reference_space_reset");onsimpleevent(c_str);GodotRuntime.free(c_str)};GodotWebXR.pauseResumeMainLoop();window.setTimeout(function(){const reference_space_c_str=GodotRuntime.allocString(reference_space_type);const enabled_features="enabledFeatures"in session?Array.from(session.enabledFeatures):[];const enabled_features_c_str=GodotRuntime.allocString(enabled_features.join(","));const environment_blend_mode="environmentBlendMode"in session?session.environmentBlendMode:"";const environment_blend_mode_c_str=GodotRuntime.allocString(environment_blend_mode);onstarted(reference_space_c_str,enabled_features_c_str,environment_blend_mode_c_str);GodotRuntime.free(reference_space_c_str);GodotRuntime.free(enabled_features_c_str);GodotRuntime.free(environment_blend_mode_c_str)},0)}function requestReferenceSpace(){const reference_space_type=requested_reference_space_types.shift();session.requestReferenceSpace(reference_space_type).then(refSpace=>{onReferenceSpaceSuccess(refSpace,reference_space_type)}).catch(()=>{if(requested_reference_space_types.length===0){const c_str=GodotRuntime.allocString("Unable to get any of the requested reference space types");onfailed(c_str);GodotRuntime.free(c_str)}else{requestReferenceSpace()}})}requestReferenceSpace()}).catch(function(error){const c_str=GodotRuntime.allocString(`Unable to make WebGL context compatible with WebXR: ${error}`);onfailed(c_str);GodotRuntime.free(c_str)})}).catch(function(error){const c_str=GodotRuntime.allocString(`Unable to start session: ${error}`);onfailed(c_str);GodotRuntime.free(c_str)})}function _godot_webxr_is_session_supported(p_session_mode,p_callback){const session_mode=GodotRuntime.parseString(p_session_mode);const cb=GodotRuntime.get_func(p_callback);if(navigator.xr){navigator.xr.isSessionSupported(session_mode).then(function(supported){const c_str=GodotRuntime.allocString(session_mode);cb(c_str,supported?1:0);GodotRuntime.free(c_str)})}else{const c_str=GodotRuntime.allocString(session_mode);cb(c_str,0);GodotRuntime.free(c_str)}}function _godot_webxr_is_supported(){return!!navigator.xr}function _godot_webxr_uninitialize(){if(GodotWebXR.session){GodotWebXR.session.end().catch(e=>{})}GodotWebXR.session=null;GodotWebXR.gl_binding=null;GodotWebXR.layer=null;GodotWebXR.space=null;GodotWebXR.frame=null;GodotWebXR.pose=null;GodotWebXR.view_count=1;GodotWebXR.input_sources=new Array(16);GodotWebXR.touches=new Array(5);GodotWebXR.onsimpleevent=null;GodotWebXR.monkeyPatchRequestAnimationFrame(false);GodotWebXR.pauseResumeMainLoop()}function _godot_webxr_update_input_source(p_input_source_id,r_target_pose,r_target_ray_mode,r_touch_index,r_has_grip_pose,r_grip_pose,r_has_standard_mapping,r_button_count,r_buttons,r_axes_count,r_axes,r_has_hand_data,r_hand_joints,r_hand_radii){if(!GodotWebXR.session||!GodotWebXR.frame){return 0}if(p_input_source_id<0||p_input_source_id>=GodotWebXR.input_sources.length||!GodotWebXR.input_sources[p_input_source_id]){return false}const input_source=GodotWebXR.input_sources[p_input_source_id];const frame=GodotWebXR.frame;const space=GodotWebXR.space;const target_pose=frame.getPose(input_source.targetRaySpace,space);if(!target_pose){return false}const target_pose_matrix=target_pose.transform.matrix;for(let i=0;i<16;i++){GodotRuntime.setHeapValue(r_target_pose+i*4,target_pose_matrix[i],"float")}let target_ray_mode=0;switch(input_source.targetRayMode){case"gaze":target_ray_mode=1;break;case"tracked-pointer":target_ray_mode=2;break;case"screen":target_ray_mode=3;break;default:}GodotRuntime.setHeapValue(r_target_ray_mode,target_ray_mode,"i32");GodotRuntime.setHeapValue(r_touch_index,GodotWebXR.getTouchIndex(input_source),"i32");let has_grip_pose=false;if(input_source.gripSpace){const grip_pose=frame.getPose(input_source.gripSpace,space);if(grip_pose){const grip_pose_matrix=grip_pose.transform.matrix;for(let i=0;i<16;i++){GodotRuntime.setHeapValue(r_grip_pose+i*4,grip_pose_matrix[i],"float")}has_grip_pose=true}}GodotRuntime.setHeapValue(r_has_grip_pose,has_grip_pose?1:0,"i32");let has_standard_mapping=false;let button_count=0;let axes_count=0;if(input_source.gamepad){if(input_source.gamepad.mapping==="xr-standard"){has_standard_mapping=true}button_count=Math.min(input_source.gamepad.buttons.length,10);for(let i=0;i{const c_str=GodotRuntime.allocString("display_refresh_rate_changed");GodotWebXR.onsimpleevent(c_str);GodotRuntime.free(c_str)})}function __arraySum(array,index){var sum=0;for(var i=0;i<=index;sum+=array[i++]){}return sum}var __MONTH_DAYS_LEAP=[31,29,31,30,31,30,31,31,30,31,30,31];var __MONTH_DAYS_REGULAR=[31,28,31,30,31,30,31,31,30,31,30,31];function __addDays(date,days){var newDate=new Date(date.getTime());while(days>0){var leap=__isLeapYear(newDate.getFullYear());var currentMonth=newDate.getMonth();var daysInCurrentMonth=(leap?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR)[currentMonth];if(days>daysInCurrentMonth-newDate.getDate()){days-=daysInCurrentMonth-newDate.getDate()+1;newDate.setDate(1);if(currentMonth<11){newDate.setMonth(currentMonth+1)}else{newDate.setMonth(0);newDate.setFullYear(newDate.getFullYear()+1)}}else{newDate.setDate(newDate.getDate()+days);return newDate}}return newDate}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function _strftime(s,maxsize,format,tm){var tm_zone=HEAP32[tm+40>>2];var date={tm_sec:HEAP32[tm>>2],tm_min:HEAP32[tm+4>>2],tm_hour:HEAP32[tm+8>>2],tm_mday:HEAP32[tm+12>>2],tm_mon:HEAP32[tm+16>>2],tm_year:HEAP32[tm+20>>2],tm_wday:HEAP32[tm+24>>2],tm_yday:HEAP32[tm+28>>2],tm_isdst:HEAP32[tm+32>>2],tm_gmtoff:HEAP32[tm+36>>2],tm_zone:tm_zone?UTF8ToString(tm_zone):""};var pattern=UTF8ToString(format);var EXPANSION_RULES_1={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};for(var rule in EXPANSION_RULES_1){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_1[rule])}var WEEKDAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];var MONTHS=["January","February","March","April","May","June","July","August","September","October","November","December"];function leadingSomething(value,digits,character){var str=typeof value=="number"?value.toString():value||"";while(str.length0?1:0}var compare;if((compare=sgn(date1.getFullYear()-date2.getFullYear()))===0){if((compare=sgn(date1.getMonth()-date2.getMonth()))===0){compare=sgn(date1.getDate()-date2.getDate())}}return compare}function getFirstWeekStartDate(janFourth){switch(janFourth.getDay()){case 0:return new Date(janFourth.getFullYear()-1,11,29);case 1:return janFourth;case 2:return new Date(janFourth.getFullYear(),0,3);case 3:return new Date(janFourth.getFullYear(),0,2);case 4:return new Date(janFourth.getFullYear(),0,1);case 5:return new Date(janFourth.getFullYear()-1,11,31);case 6:return new Date(janFourth.getFullYear()-1,11,30)}}function getWeekBasedYear(date){var thisDate=__addDays(new Date(date.tm_year+1900,0,1),date.tm_yday);var janFourthThisYear=new Date(thisDate.getFullYear(),0,4);var janFourthNextYear=new Date(thisDate.getFullYear()+1,0,4);var firstWeekStartThisYear=getFirstWeekStartDate(janFourthThisYear);var firstWeekStartNextYear=getFirstWeekStartDate(janFourthNextYear);if(compareByDay(firstWeekStartThisYear,thisDate)<=0){if(compareByDay(firstWeekStartNextYear,thisDate)<=0){return thisDate.getFullYear()+1}return thisDate.getFullYear()}return thisDate.getFullYear()-1}var EXPANSION_RULES_2={"%a":function(date){return WEEKDAYS[date.tm_wday].substring(0,3)},"%A":function(date){return WEEKDAYS[date.tm_wday]},"%b":function(date){return MONTHS[date.tm_mon].substring(0,3)},"%B":function(date){return MONTHS[date.tm_mon]},"%C":function(date){var year=date.tm_year+1900;return leadingNulls(year/100|0,2)},"%d":function(date){return leadingNulls(date.tm_mday,2)},"%e":function(date){return leadingSomething(date.tm_mday,2," ")},"%g":function(date){return getWeekBasedYear(date).toString().substring(2)},"%G":function(date){return getWeekBasedYear(date)},"%H":function(date){return leadingNulls(date.tm_hour,2)},"%I":function(date){var twelveHour=date.tm_hour;if(twelveHour==0)twelveHour=12;else if(twelveHour>12)twelveHour-=12;return leadingNulls(twelveHour,2)},"%j":function(date){return leadingNulls(date.tm_mday+__arraySum(__isLeapYear(date.tm_year+1900)?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR,date.tm_mon-1),3)},"%m":function(date){return leadingNulls(date.tm_mon+1,2)},"%M":function(date){return leadingNulls(date.tm_min,2)},"%n":function(){return"\n"},"%p":function(date){if(date.tm_hour>=0&&date.tm_hour<12){return"AM"}return"PM"},"%S":function(date){return leadingNulls(date.tm_sec,2)},"%t":function(){return"\t"},"%u":function(date){return date.tm_wday||7},"%U":function(date){var days=date.tm_yday+7-date.tm_wday;return leadingNulls(Math.floor(days/7),2)},"%V":function(date){var val=Math.floor((date.tm_yday+7-(date.tm_wday+6)%7)/7);if((date.tm_wday+371-date.tm_yday-2)%7<=2){val++}if(!val){val=52;var dec31=(date.tm_wday+7-date.tm_yday-1)%7;if(dec31==4||dec31==5&&__isLeapYear(date.tm_year%400-1)){val++}}else if(val==53){var jan1=(date.tm_wday+371-date.tm_yday)%7;if(jan1!=4&&(jan1!=3||!__isLeapYear(date.tm_year)))val=1}return leadingNulls(val,2)},"%w":function(date){return date.tm_wday},"%W":function(date){var days=date.tm_yday+7-(date.tm_wday+6)%7;return leadingNulls(Math.floor(days/7),2)},"%y":function(date){return(date.tm_year+1900).toString().substring(2)},"%Y":function(date){return date.tm_year+1900},"%z":function(date){var off=date.tm_gmtoff;var ahead=off>=0;off=Math.abs(off)/60;off=off/60*100+off%60;return(ahead?"+":"-")+String("0000"+off).slice(-4)},"%Z":function(date){return date.tm_zone},"%%":function(){return"%"}};pattern=pattern.replace(/%%/g,"\0\0");for(var rule in EXPANSION_RULES_2){if(pattern.includes(rule)){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_2[rule](date))}}pattern=pattern.replace(/\0\0/g,"%");var bytes=intArrayFromString(pattern,false);if(bytes.length>maxsize){return 0}writeArrayToMemory(bytes,s);return bytes.length-1}function _strftime_l(s,maxsize,format,tm,loc){return _strftime(s,maxsize,format,tm)}function allocateUTF8OnStack(str){var size=lengthBytesUTF8(str)+1;var ret=stackAlloc(size);stringToUTF8Array(str,HEAP8,ret,size);return ret}function getCFunc(ident){var func=Module["_"+ident];return func}function ccall(ident,returnType,argTypes,args,opts){var toC={"string":str=>{var ret=0;if(str!==null&&str!==undefined&&str!==0){var len=(str.length<<2)+1;ret=stackAlloc(len);stringToUTF8(str,ret,len)}return ret},"array":arr=>{var ret=stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}};function convertReturnValue(ret){if(returnType==="string"){return UTF8ToString(ret)}if(returnType==="boolean")return Boolean(ret);return ret}var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;itype==="number"||type==="boolean");var numericRet=returnType!=="string";if(numericRet&&numericArgs&&!opts){return getCFunc(ident)}return function(){return ccall(ident,returnType,argTypes,arguments,opts)}}var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.staticInit();Module["requestFullscreen"]=function Module_requestFullscreen(lockPointer,resizeCanvas){Browser.requestFullscreen(lockPointer,resizeCanvas)};Module["requestAnimationFrame"]=function Module_requestAnimationFrame(func){Browser.requestAnimationFrame(func)};Module["setCanvasSize"]=function Module_setCanvasSize(width,height,noUpdates){Browser.setCanvasSize(width,height,noUpdates)};Module["pauseMainLoop"]=function Module_pauseMainLoop(){Browser.mainLoop.pause()};Module["resumeMainLoop"]=function Module_resumeMainLoop(){Browser.mainLoop.resume()};Module["getUserMedia"]=function Module_getUserMedia(){Browser.getUserMedia()};Module["createContext"]=function Module_createContext(canvas,useWebGL,setInModule,webGLContextAttributes){return Browser.createContext(canvas,useWebGL,setInModule,webGLContextAttributes)};var preloadedImages={};var preloadedAudios={};var GLctx;for(var i=0;i<32;++i)tempFixedLengthArray.push(new Array(i));var __miniTempWebGLIntBuffersStorage=new Int32Array(288);for(var i=0;i<288;++i){__miniTempWebGLIntBuffers[i]=__miniTempWebGLIntBuffersStorage.subarray(0,i+1)}var miniTempWebGLFloatBuffersStorage=new Float32Array(288);for(var i=0;i<288;++i){miniTempWebGLFloatBuffers[i]=miniTempWebGLFloatBuffersStorage.subarray(0,i+1)}Module["request_quit"]=function(){GodotOS.request_quit()};Module["onExit"]=GodotOS.cleanup;GodotOS._fs_sync_promise=Promise.resolve();Module["initConfig"]=GodotConfig.init_config;Module["initFS"]=GodotFS.init;Module["copyToFS"]=GodotFS.copy_to_fs;GodotOS.atexit(function(resolve,reject){GodotDisplayCursor.clear();resolve()});GodotOS.atexit(function(resolve,reject){GodotEventListeners.clear();resolve()});GodotOS.atexit(function(resolve,reject){GodotDisplayVK.clear();resolve()});GodotOS.atexit(function(resolve,reject){GodotIME.clear();resolve()});GodotJSWrapper.proxies=new Map;var asmLibraryArg={"qf":___call_sighandler,"pf":___syscall__newselect,"of":___syscall_accept4,"nf":___syscall_bind,"mf":___syscall_chdir,"tb":___syscall_chmod,"lf":___syscall_connect,"kf":___syscall_faccessat,"jf":___syscall_fchmod,"U":___syscall_fcntl64,"wb":___syscall_ftruncate64,"hf":___syscall_getcwd,"gf":___syscall_getdents64,"ff":___syscall_getsockname,"ef":___syscall_getsockopt,"sb":___syscall_ioctl,"df":___syscall_listen,"cf":___syscall_lstat64,"bf":___syscall_mkdirat,"af":___syscall_mknodat,"$e":___syscall_newfstatat,"rb":___syscall_openat,"_e":___syscall_poll,"Ze":___syscall_readlinkat,"Ye":___syscall_recvfrom,"Xe":___syscall_renameat,"We":___syscall_rmdir,"Ve":___syscall_sendto,"qb":___syscall_socket,"Ue":___syscall_stat64,"Te":___syscall_statfs64,"Se":___syscall_symlink,"Re":___syscall_unlinkat,"Oe":__dlinit,"Ne":__dlopen_js,"Me":__dlsym_js,"Le":__emscripten_get_now_is_monotonic,"Ke":__gmtime_js,"Je":__localtime_js,"Ie":__tzset_js,"e":_abort,"He":_emscripten_cancel_main_loop,"Ja":_emscripten_date_now,"Ge":_emscripten_force_exit,"Fe":_emscripten_get_heap_max,"Ia":_emscripten_get_now,"Ee":_emscripten_memcpy_big,"De":_emscripten_resize_heap,"Ce":_emscripten_set_canvas_element_size,"Ha":_emscripten_set_main_loop,"nb":_emscripten_webgl_commit_frame,"Be":_emscripten_webgl_create_context,"Ae":_emscripten_webgl_destroy_context,"ze":_emscripten_webgl_enable_extension,"ye":_emscripten_webgl_get_supported_extensions,"xe":_emscripten_webgl_init_context_attributes,"we":_emscripten_webgl_make_context_current,"Qe":_environ_get,"Pe":_environ_sizes_get,"ua":_exit,"da":_fd_close,"pb":_fd_fdstat_get,"ob":_fd_read,"vb":_fd_seek,"Ka":_fd_write,"Ga":_getaddrinfo,"ve":_getnameinfo,"j":_glActiveTexture,"mb":_glAttachShader,"ha":_glBeginTransformFeedback,"b":_glBindBuffer,"w":_glBindBufferBase,"Fa":_glBindBufferRange,"d":_glBindFramebuffer,"ta":_glBindRenderbuffer,"c":_glBindTexture,"f":_glBindVertexArray,"ue":_glBlendColor,"J":_glBlendEquation,"la":_glBlendFunc,"C":_glBlendFuncSeparate,"ga":_glBlitFramebuffer,"h":_glBufferData,"P":_glBufferSubData,"O":_glCheckFramebufferStatus,"H":_glClear,"lb":_glClearBufferfv,"N":_glClearColor,"te":_glClearDepthf,"_":_glColorMask,"kb":_glCompileShader,"se":_glCompressedTexImage2D,"re":_glCompressedTexImage3D,"qe":_glCompressedTexSubImage3D,"pe":_glCopyBufferSubData,"oe":_glCreateProgram,"jb":_glCreateShader,"ka":_glCullFace,"o":_glDeleteBuffers,"v":_glDeleteFramebuffers,"fa":_glDeleteProgram,"ne":_glDeleteQueries,"na":_glDeleteRenderbuffers,"Z":_glDeleteShader,"ib":_glDeleteSync,"l":_glDeleteTextures,"M":_glDeleteVertexArrays,"W":_glDepthFunc,"z":_glDepthMask,"i":_glDisable,"q":_glDisableVertexAttribArray,"G":_glDrawArrays,"Y":_glDrawArraysInstanced,"ja":_glDrawBuffers,"T":_glDrawElements,"S":_glDrawElementsInstanced,"y":_glEnable,"g":_glEnableVertexAttribArray,"ea":_glEndTransformFeedback,"hb":_glFenceSync,"me":_glFinish,"Ea":_glFramebufferRenderbuffer,"u":_glFramebufferTexture2D,"aa":_glFramebufferTextureLayer,"gb":_glFrontFace,"m":_glGenBuffers,"A":_glGenFramebuffers,"le":_glGenQueries,"Da":_glGenRenderbuffers,"t":_glGenTextures,"L":_glGenVertexArrays,"ke":_glGenerateMipmap,"je":_glGetFloatv,"ie":_glGetInteger64v,"ma":_glGetIntegerv,"he":_glGetProgramInfoLog,"fb":_glGetProgramiv,"eb":_glGetShaderInfoLog,"sa":_glGetShaderiv,"$":_glGetString,"ge":_glGetSynciv,"fe":_glGetUniformBlockIndex,"Ca":_glGetUniformLocation,"ee":_glLinkProgram,"Ba":_glPixelStorei,"db":_glReadBuffer,"Aa":_glReadPixels,"de":_glRenderbufferStorage,"cb":_glRenderbufferStorageMultisample,"ra":_glScissor,"bb":_glShaderSource,"s":_glTexImage2D,"V":_glTexImage3D,"za":_glTexParameterf,"a":_glTexParameteri,"ya":_glTexStorage2D,"ab":_glTexSubImage3D,"ce":_glTransformFeedbackVaryings,"n":_glUniform1f,"E":_glUniform1i,"be":_glUniform1iv,"F":_glUniform1ui,"qa":_glUniform1uiv,"ca":_glUniform2f,"R":_glUniform2fv,"ia":_glUniform2iv,"r":_glUniform3fv,"K":_glUniform4f,"D":_glUniform4fv,"ae":_glUniformBlockBinding,"$a":_glUniformMatrix3fv,"I":_glUniformMatrix4fv,"p":_glUseProgram,"pa":_glVertexAttrib4f,"x":_glVertexAttribDivisor,"ba":_glVertexAttribI4ui,"Q":_glVertexAttribIPointer,"k":_glVertexAttribPointer,"B":_glViewport,"$d":_godot_audio_has_worklet,"_d":_godot_audio_init,"Zd":_godot_audio_input_start,"Yd":_godot_audio_input_stop,"Xd":_godot_audio_is_available,"Wd":_godot_audio_resume,"Vd":_godot_audio_sample_bus_add,"Ud":_godot_audio_sample_bus_move,"Td":_godot_audio_sample_bus_remove,"Sd":_godot_audio_sample_bus_set_count,"Rd":_godot_audio_sample_bus_set_mute,"Qd":_godot_audio_sample_bus_set_send,"Pd":_godot_audio_sample_bus_set_solo,"Od":_godot_audio_sample_bus_set_volume_db,"Nd":_godot_audio_sample_is_active,"Md":_godot_audio_sample_register_stream,"Ld":_godot_audio_sample_set_finished_callback,"Kd":_godot_audio_sample_set_pause,"Jd":_godot_audio_sample_set_volumes_linear,"Id":_godot_audio_sample_start,"Hd":_godot_audio_sample_stop,"Gd":_godot_audio_sample_stream_is_registered,"Fd":_godot_audio_sample_unregister_stream,"Ed":_godot_audio_sample_update_pitch_scale,"Dd":_godot_audio_worklet_create,"Cd":_godot_audio_worklet_start_no_threads,"Bd":_godot_js_config_canvas_id_get,"Ad":_godot_js_config_locale_get,"zd":_godot_js_display_alert,"yd":_godot_js_display_canvas_focus,"xd":_godot_js_display_canvas_is_focused,"wd":_godot_js_display_clipboard_get,"vd":_godot_js_display_clipboard_set,"ud":_godot_js_display_cursor_is_hidden,"td":_godot_js_display_cursor_is_locked,"xa":_godot_js_display_cursor_lock_set,"_a":_godot_js_display_cursor_set_custom_shape,"sd":_godot_js_display_cursor_set_shape,"wa":_godot_js_display_cursor_set_visible,"rd":_godot_js_display_desired_size_set,"qd":_godot_js_display_fullscreen_cb,"pd":_godot_js_display_fullscreen_exit,"od":_godot_js_display_fullscreen_request,"nd":_godot_js_display_has_webgl,"md":_godot_js_display_is_swap_ok_cancel,"ld":_godot_js_display_notification_cb,"kd":_godot_js_display_pixel_ratio_get,"jd":_godot_js_display_screen_dpi_get,"id":_godot_js_display_screen_size_get,"hd":_godot_js_display_setup_canvas,"gd":_godot_js_display_size_update,"fd":_godot_js_display_touchscreen_is_available,"ed":_godot_js_display_tts_available,"Za":_godot_js_display_vk_available,"dd":_godot_js_display_vk_cb,"cd":_godot_js_display_vk_hide,"bd":_godot_js_display_vk_show,"ad":_godot_js_display_window_blur_cb,"Ya":_godot_js_display_window_icon_set,"Xa":_godot_js_display_window_size_get,"$c":_godot_js_display_window_title_set,"_c":_godot_js_eval,"Zc":_godot_js_fetch_create,"Wa":_godot_js_fetch_free,"Yc":_godot_js_fetch_http_status_get,"Xc":_godot_js_fetch_is_chunked,"Wc":_godot_js_fetch_read_chunk,"Vc":_godot_js_fetch_read_headers,"va":_godot_js_fetch_state_get,"Uc":_godot_js_input_drop_files_cb,"Tc":_godot_js_input_gamepad_cb,"Sc":_godot_js_input_gamepad_sample,"Rc":_godot_js_input_gamepad_sample_count,"Qc":_godot_js_input_gamepad_sample_get,"Pc":_godot_js_input_key_cb,"Oc":_godot_js_input_mouse_button_cb,"Nc":_godot_js_input_mouse_move_cb,"Mc":_godot_js_input_mouse_wheel_cb,"Lc":_godot_js_input_paste_cb,"Kc":_godot_js_input_touch_cb,"Jc":_godot_js_input_vibrate_handheld,"Va":_godot_js_is_ime_focused,"Ic":_godot_js_os_download_buffer,"Hc":_godot_js_os_execute,"Ua":_godot_js_os_finish_async,"Gc":_godot_js_os_fs_is_persistent,"Fc":_godot_js_os_fs_sync,"Ec":_godot_js_os_has_feature,"Dc":_godot_js_os_hw_concurrency_get,"Cc":_godot_js_os_request_quit_cb,"Bc":_godot_js_os_shell_open,"Ac":_godot_js_pwa_cb,"zc":_godot_js_pwa_update,"Ta":_godot_js_rtc_datachannel_close,"yc":_godot_js_rtc_datachannel_connect,"xc":_godot_js_rtc_datachannel_destroy,"wc":_godot_js_rtc_datachannel_get_buffered_amount,"vc":_godot_js_rtc_datachannel_id_get,"uc":_godot_js_rtc_datachannel_is_negotiated,"tc":_godot_js_rtc_datachannel_is_ordered,"sc":_godot_js_rtc_datachannel_label_get,"rc":_godot_js_rtc_datachannel_max_packet_lifetime_get,"qc":_godot_js_rtc_datachannel_max_retransmits_get,"pc":_godot_js_rtc_datachannel_protocol_get,"oc":_godot_js_rtc_datachannel_ready_state_get,"nc":_godot_js_rtc_datachannel_send,"Sa":_godot_js_rtc_pc_close,"mc":_godot_js_rtc_pc_create,"lc":_godot_js_rtc_pc_datachannel_create,"Ra":_godot_js_rtc_pc_destroy,"kc":_godot_js_rtc_pc_ice_candidate_add,"jc":_godot_js_rtc_pc_local_description_set,"ic":_godot_js_rtc_pc_offer_create,"hc":_godot_js_rtc_pc_remote_description_set,"gc":_godot_js_set_ime_active,"fc":_godot_js_set_ime_cb,"ec":_godot_js_set_ime_position,"dc":_godot_js_tts_get_voices,"cc":_godot_js_tts_is_paused,"bc":_godot_js_tts_is_speaking,"ac":_godot_js_tts_pause,"$b":_godot_js_tts_resume,"_b":_godot_js_tts_speak,"Zb":_godot_js_tts_stop,"Yb":_godot_js_websocket_buffered_amount,"Xb":_godot_js_websocket_close,"Wb":_godot_js_websocket_create,"Qa":_godot_js_websocket_destroy,"Vb":_godot_js_websocket_send,"Ub":_godot_js_wrapper_create_cb,"Tb":_godot_js_wrapper_create_object,"Sb":_godot_js_wrapper_interface_get,"Rb":_godot_js_wrapper_object_call,"Qb":_godot_js_wrapper_object_get,"Pa":_godot_js_wrapper_object_getvar,"Pb":_godot_js_wrapper_object_set,"Ob":_godot_js_wrapper_object_set_cb_ret,"Nb":_godot_js_wrapper_object_setvar,"Mb":_godot_js_wrapper_object_unref,"Oa":_godot_webgl2_glFramebufferTextureMultisampleMultiviewOVR,"X":_godot_webgl2_glFramebufferTextureMultiviewOVR,"oa":_godot_webgl2_glGetBufferSubData,"Lb":_godot_webxr_get_bounds_geometry,"Kb":_godot_webxr_get_color_texture,"Jb":_godot_webxr_get_depth_texture,"Ib":_godot_webxr_get_frame_rate,"Hb":_godot_webxr_get_projection_for_view,"Gb":_godot_webxr_get_render_target_size,"Fb":_godot_webxr_get_supported_frame_rates,"Na":_godot_webxr_get_transform_for_view,"Eb":_godot_webxr_get_velocity_texture,"Ma":_godot_webxr_get_view_count,"Db":_godot_webxr_get_visibility_state,"Cb":_godot_webxr_initialize,"Bb":_godot_webxr_is_session_supported,"Ab":_godot_webxr_is_supported,"zb":_godot_webxr_uninitialize,"yb":_godot_webxr_update_input_source,"xb":_godot_webxr_update_target_frame_rate,"La":_strftime,"ub":_strftime_l};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["sf"]).apply(null,arguments)};var __Z14godot_web_mainiPPc=Module["__Z14godot_web_mainiPPc"]=function(){return(__Z14godot_web_mainiPPc=Module["__Z14godot_web_mainiPPc"]=Module["asm"]["uf"]).apply(null,arguments)};var _main=Module["_main"]=function(){return(_main=Module["_main"]=Module["asm"]["vf"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["wf"]).apply(null,arguments)};var ___errno_location=Module["___errno_location"]=function(){return(___errno_location=Module["___errno_location"]=Module["asm"]["xf"]).apply(null,arguments)};var _fflush=Module["_fflush"]=function(){return(_fflush=Module["_fflush"]=Module["asm"]["yf"]).apply(null,arguments)};var _htonl=Module["_htonl"]=function(){return(_htonl=Module["_htonl"]=Module["asm"]["zf"]).apply(null,arguments)};var _htons=Module["_htons"]=function(){return(_htons=Module["_htons"]=Module["asm"]["Af"]).apply(null,arguments)};var _ntohs=Module["_ntohs"]=function(){return(_ntohs=Module["_ntohs"]=Module["asm"]["Bf"]).apply(null,arguments)};var __emwebxr_on_input_event=Module["__emwebxr_on_input_event"]=function(){return(__emwebxr_on_input_event=Module["__emwebxr_on_input_event"]=Module["asm"]["Cf"]).apply(null,arguments)};var __emwebxr_on_simple_event=Module["__emwebxr_on_simple_event"]=function(){return(__emwebxr_on_simple_event=Module["__emwebxr_on_simple_event"]=Module["asm"]["Df"]).apply(null,arguments)};var ___funcs_on_exit=Module["___funcs_on_exit"]=function(){return(___funcs_on_exit=Module["___funcs_on_exit"]=Module["asm"]["Ef"]).apply(null,arguments)};var stackSave=Module["stackSave"]=function(){return(stackSave=Module["stackSave"]=Module["asm"]["Ff"]).apply(null,arguments)};var stackRestore=Module["stackRestore"]=function(){return(stackRestore=Module["stackRestore"]=Module["asm"]["Gf"]).apply(null,arguments)};var stackAlloc=Module["stackAlloc"]=function(){return(stackAlloc=Module["stackAlloc"]=Module["asm"]["Hf"]).apply(null,arguments)};Module["callMain"]=callMain;Module["cwrap"]=cwrap;var calledRun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function callMain(args){var entryFunction=Module["_main"];args=args||[];args.unshift(thisProgram);var argc=args.length;var argv=stackAlloc((argc+1)*4);var argv_ptr=argv>>2;args.forEach(arg=>{HEAP32[argv_ptr++]=allocateUTF8OnStack(arg)});HEAP32[argv_ptr]=0;try{var ret=entryFunction(argc,argv);exitJS(ret,true);return ret}catch(e){return handleException(e)}}function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();preMain();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();if(shouldRunNow)callMain(args);postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}var shouldRunNow=false;if(Module["noInitialRun"])shouldRunNow=false;run(); + + + return Godot.ready +} +); +})(); +if (typeof exports === 'object' && typeof module === 'object') + module.exports = Godot; +else if (typeof define === 'function' && define['amd']) + define([], function() { return Godot; }); +else if (typeof exports === 'object') + exports["Godot"] = Godot; + +const Features = { + /** + * Check whether WebGL is available. Optionally, specify a particular version of WebGL to check for. + * + * @param {number=} [majorVersion=1] The major WebGL version to check for. + * @returns {boolean} If the given major version of WebGL is available. + * @function Engine.isWebGLAvailable + */ + isWebGLAvailable: function (majorVersion = 1) { + try { + return !!document.createElement('canvas').getContext(['webgl', 'webgl2'][majorVersion - 1]); + } catch (e) { /* Not available */ } + return false; + }, + + /** + * Check whether the Fetch API available and supports streaming responses. + * + * @returns {boolean} If the Fetch API is available and supports streaming responses. + * @function Engine.isFetchAvailable + */ + isFetchAvailable: function () { + return 'fetch' in window && 'Response' in window && 'body' in window.Response.prototype; + }, + + /** + * Check whether the engine is running in a Secure Context. + * + * @returns {boolean} If the engine is running in a Secure Context. + * @function Engine.isSecureContext + */ + isSecureContext: function () { + return window['isSecureContext'] === true; + }, + + /** + * Check whether the engine is cross origin isolated. + * This value is dependent on Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers sent by the server. + * + * @returns {boolean} If the engine is running in a Secure Context. + * @function Engine.isSecureContext + */ + isCrossOriginIsolated: function () { + return window['crossOriginIsolated'] === true; + }, + + /** + * Check whether SharedBufferArray is available. + * + * Most browsers require the page to be running in a secure context, and the + * the server to provide specific CORS headers for SharedArrayBuffer to be available. + * + * @returns {boolean} If SharedArrayBuffer is available. + * @function Engine.isSharedArrayBufferAvailable + */ + isSharedArrayBufferAvailable: function () { + return 'SharedArrayBuffer' in window; + }, + + /** + * Check whether the AudioContext supports AudioWorkletNodes. + * + * @returns {boolean} If AudioWorkletNode is available. + * @function Engine.isAudioWorkletAvailable + */ + isAudioWorkletAvailable: function () { + return 'AudioContext' in window && 'audioWorklet' in AudioContext.prototype; + }, + + /** + * Return an array of missing required features (as string). + * + * @returns {Array} A list of human-readable missing features. + * @function Engine.getMissingFeatures + * @param {{threads: (boolean|undefined)}} supportedFeatures + */ + getMissingFeatures: function (supportedFeatures = {}) { + const { + // Quotes are needed for the Closure compiler. + 'threads': supportsThreads = true, + } = supportedFeatures; + + const missing = []; + if (!Features.isWebGLAvailable(2)) { + missing.push('WebGL2 - Check web browser configuration and hardware support'); + } + if (!Features.isFetchAvailable()) { + missing.push('Fetch - Check web browser version'); + } + if (!Features.isSecureContext()) { + missing.push('Secure Context - Check web server configuration (use HTTPS)'); + } + + if (supportsThreads) { + if (!Features.isCrossOriginIsolated()) { + missing.push('Cross-Origin Isolation - Check that the web server configuration sends the correct headers.'); + } + if (!Features.isSharedArrayBufferAvailable()) { + missing.push('SharedArrayBuffer - Check that the web server configuration sends the correct headers.'); + } + } + + // Audio is normally optional since we have a dummy fallback. + return missing; + }, +}; + +const Preloader = /** @constructor */ function () { // eslint-disable-line no-unused-vars + function getTrackedResponse(response, load_status) { + function onloadprogress(reader, controller) { + return reader.read().then(function (result) { + if (load_status.done) { + return Promise.resolve(); + } + if (result.value) { + controller.enqueue(result.value); + load_status.loaded += result.value.length; + } + if (!result.done) { + return onloadprogress(reader, controller); + } + load_status.done = true; + return Promise.resolve(); + }); + } + const reader = response.body.getReader(); + return new Response(new ReadableStream({ + start: function (controller) { + onloadprogress(reader, controller).then(function () { + controller.close(); + }); + }, + }), { headers: response.headers }); + } + + function loadFetch(file, tracker, fileSize, raw) { + tracker[file] = { + total: fileSize || 0, + loaded: 0, + done: false, + }; + return fetch(file).then(function (response) { + if (!response.ok) { + return Promise.reject(new Error(`Failed loading file '${file}'`)); + } + const tr = getTrackedResponse(response, tracker[file]); + if (raw) { + return Promise.resolve(tr); + } + return tr.arrayBuffer(); + }); + } + + function retry(func, attempts = 1) { + function onerror(err) { + if (attempts <= 1) { + return Promise.reject(err); + } + return new Promise(function (resolve, reject) { + setTimeout(function () { + retry(func, attempts - 1).then(resolve).catch(reject); + }, 1000); + }); + } + return func().catch(onerror); + } + + const DOWNLOAD_ATTEMPTS_MAX = 4; + const loadingFiles = {}; + const lastProgress = { loaded: 0, total: 0 }; + let progressFunc = null; + + const animateProgress = function () { + let loaded = 0; + let total = 0; + let totalIsValid = true; + let progressIsFinal = true; + + Object.keys(loadingFiles).forEach(function (file) { + const stat = loadingFiles[file]; + if (!stat.done) { + progressIsFinal = false; + } + if (!totalIsValid || stat.total === 0) { + totalIsValid = false; + total = 0; + } else { + total += stat.total; + } + loaded += stat.loaded; + }); + if (loaded !== lastProgress.loaded || total !== lastProgress.total) { + lastProgress.loaded = loaded; + lastProgress.total = total; + if (typeof progressFunc === 'function') { + progressFunc(loaded, total); + } + } + if (!progressIsFinal) { + requestAnimationFrame(animateProgress); + } + }; + + this.animateProgress = animateProgress; + + this.setProgressFunc = function (callback) { + progressFunc = callback; + }; + + this.loadPromise = function (file, fileSize, raw = false) { + return retry(loadFetch.bind(null, file, loadingFiles, fileSize, raw), DOWNLOAD_ATTEMPTS_MAX); + }; + + this.preloadedFiles = []; + this.preload = function (pathOrBuffer, destPath, fileSize) { + let buffer = null; + if (typeof pathOrBuffer === 'string') { + const me = this; + return this.loadPromise(pathOrBuffer, fileSize).then(function (buf) { + me.preloadedFiles.push({ + path: destPath || pathOrBuffer, + buffer: buf, + }); + return Promise.resolve(); + }); + } else if (pathOrBuffer instanceof ArrayBuffer) { + buffer = new Uint8Array(pathOrBuffer); + } else if (ArrayBuffer.isView(pathOrBuffer)) { + buffer = new Uint8Array(pathOrBuffer.buffer); + } + if (buffer) { + this.preloadedFiles.push({ + path: destPath, + buffer: pathOrBuffer, + }); + return Promise.resolve(); + } + return Promise.reject(new Error('Invalid object for preloading')); + }; +}; + +/** + * An object used to configure the Engine instance based on godot export options, and to override those in custom HTML + * templates if needed. + * + * @header Engine configuration + * @summary The Engine configuration object. This is just a typedef, create it like a regular object, e.g.: + * + * ``const MyConfig = { executable: 'godot', unloadAfterInit: false }`` + * + * @typedef {Object} EngineConfig + */ +const EngineConfig = {}; // eslint-disable-line no-unused-vars + +/** + * @struct + * @constructor + * @ignore + */ +const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-vars + const cfg = /** @lends {InternalConfig.prototype} */ { + /** + * Whether to unload the engine automatically after the instance is initialized. + * + * @memberof EngineConfig + * @default + * @type {boolean} + */ + unloadAfterInit: true, + /** + * The HTML DOM Canvas object to use. + * + * By default, the first canvas element in the document will be used is none is specified. + * + * @memberof EngineConfig + * @default + * @type {?HTMLCanvasElement} + */ + canvas: null, + /** + * The name of the WASM file without the extension. (Set by Godot Editor export process). + * + * @memberof EngineConfig + * @default + * @type {string} + */ + executable: '', + /** + * An alternative name for the game pck to load. The executable name is used otherwise. + * + * @memberof EngineConfig + * @default + * @type {?string} + */ + mainPack: null, + /** + * Specify a language code to select the proper localization for the game. + * + * The browser locale will be used if none is specified. See complete list of + * :ref:`supported locales `. + * + * @memberof EngineConfig + * @type {?string} + * @default + */ + locale: null, + /** + * The canvas resize policy determines how the canvas should be resized by Godot. + * + * ``0`` means Godot won't do any resizing. This is useful if you want to control the canvas size from + * javascript code in your template. + * + * ``1`` means Godot will resize the canvas on start, and when changing window size via engine functions. + * + * ``2`` means Godot will adapt the canvas size to match the whole browser window. + * + * @memberof EngineConfig + * @type {number} + * @default + */ + canvasResizePolicy: 2, + /** + * The arguments to be passed as command line arguments on startup. + * + * See :ref:`command line tutorial `. + * + * **Note**: :js:meth:`startGame ` will always add the ``--main-pack`` argument. + * + * @memberof EngineConfig + * @type {Array} + * @default + */ + args: [], + /** + * When enabled, the game canvas will automatically grab the focus when the engine starts. + * + * @memberof EngineConfig + * @type {boolean} + * @default + */ + focusCanvas: true, + /** + * When enabled, this will turn on experimental virtual keyboard support on mobile. + * + * @memberof EngineConfig + * @type {boolean} + * @default + */ + experimentalVK: false, + /** + * The progressive web app service worker to install. + * @memberof EngineConfig + * @default + * @type {string} + */ + serviceWorker: '', + /** + * @ignore + * @type {Array.} + */ + persistentPaths: ['/userfs'], + /** + * @ignore + * @type {boolean} + */ + persistentDrops: false, + /** + * @ignore + * @type {Array.} + */ + gdextensionLibs: [], + /** + * @ignore + * @type {Array.} + */ + fileSizes: [], + /** + * A callback function for handling Godot's ``OS.execute`` calls. + * + * This is for example used in the Web Editor template to switch between project manager and editor, and for running the game. + * + * @callback EngineConfig.onExecute + * @param {string} path The path that Godot's wants executed. + * @param {Array.} args The arguments of the "command" to execute. + */ + /** + * @ignore + * @type {?function(string, Array.)} + */ + onExecute: null, + /** + * A callback function for being notified when the Godot instance quits. + * + * **Note**: This function will not be called if the engine crashes or become unresponsive. + * + * @callback EngineConfig.onExit + * @param {number} status_code The status code returned by Godot on exit. + */ + /** + * @ignore + * @type {?function(number)} + */ + onExit: null, + /** + * A callback function for displaying download progress. + * + * The function is called once per frame while downloading files, so the usage of ``requestAnimationFrame()`` + * is not necessary. + * + * If the callback function receives a total amount of bytes as 0, this means that it is impossible to calculate. + * Possible reasons include: + * + * - Files are delivered with server-side chunked compression + * - Files are delivered with server-side compression on Chromium + * - Not all file downloads have started yet (usually on servers without multi-threading) + * + * @callback EngineConfig.onProgress + * @param {number} current The current amount of downloaded bytes so far. + * @param {number} total The total amount of bytes to be downloaded. + */ + /** + * @ignore + * @type {?function(number, number)} + */ + onProgress: null, + /** + * A callback function for handling the standard output stream. This method should usually only be used in debug pages. + * + * By default, ``console.log()`` is used. + * + * @callback EngineConfig.onPrint + * @param {...*} [var_args] A variadic number of arguments to be printed. + */ + /** + * @ignore + * @type {?function(...*)} + */ + onPrint: function () { + console.log.apply(console, Array.from(arguments)); // eslint-disable-line no-console + }, + /** + * A callback function for handling the standard error stream. This method should usually only be used in debug pages. + * + * By default, ``console.error()`` is used. + * + * @callback EngineConfig.onPrintError + * @param {...*} [var_args] A variadic number of arguments to be printed as errors. + */ + /** + * @ignore + * @type {?function(...*)} + */ + onPrintError: function (var_args) { + console.error.apply(console, Array.from(arguments)); // eslint-disable-line no-console + }, + }; + + /** + * @ignore + * @struct + * @constructor + * @param {EngineConfig} opts + */ + function Config(opts) { + this.update(opts); + } + + Config.prototype = cfg; + + /** + * @ignore + * @param {EngineConfig} opts + */ + Config.prototype.update = function (opts) { + const config = opts || {}; + // NOTE: We must explicitly pass the default, accessing it via + // the key will fail due to closure compiler renames. + function parse(key, def) { + if (typeof (config[key]) === 'undefined') { + return def; + } + return config[key]; + } + // Module config + this.unloadAfterInit = parse('unloadAfterInit', this.unloadAfterInit); + this.onPrintError = parse('onPrintError', this.onPrintError); + this.onPrint = parse('onPrint', this.onPrint); + this.onProgress = parse('onProgress', this.onProgress); + + // Godot config + this.canvas = parse('canvas', this.canvas); + this.executable = parse('executable', this.executable); + this.mainPack = parse('mainPack', this.mainPack); + this.locale = parse('locale', this.locale); + this.canvasResizePolicy = parse('canvasResizePolicy', this.canvasResizePolicy); + this.persistentPaths = parse('persistentPaths', this.persistentPaths); + this.persistentDrops = parse('persistentDrops', this.persistentDrops); + this.experimentalVK = parse('experimentalVK', this.experimentalVK); + this.focusCanvas = parse('focusCanvas', this.focusCanvas); + this.serviceWorker = parse('serviceWorker', this.serviceWorker); + this.gdextensionLibs = parse('gdextensionLibs', this.gdextensionLibs); + this.fileSizes = parse('fileSizes', this.fileSizes); + this.args = parse('args', this.args); + this.onExecute = parse('onExecute', this.onExecute); + this.onExit = parse('onExit', this.onExit); + }; + + /** + * @ignore + * @param {string} loadPath + * @param {Response} response + */ + Config.prototype.getModuleConfig = function (loadPath, response) { + let r = response; + const gdext = this.gdextensionLibs; + return { + 'print': this.onPrint, + 'printErr': this.onPrintError, + 'thisProgram': this.executable, + 'noExitRuntime': false, + 'dynamicLibraries': [`${loadPath}.side.wasm`].concat(this.gdextensionLibs), + 'instantiateWasm': function (imports, onSuccess) { + function done(result) { + onSuccess(result['instance'], result['module']); + } + if (typeof (WebAssembly.instantiateStreaming) !== 'undefined') { + WebAssembly.instantiateStreaming(Promise.resolve(r), imports).then(done); + } else { + r.arrayBuffer().then(function (buffer) { + WebAssembly.instantiate(buffer, imports).then(done); + }); + } + r = null; + return {}; + }, + 'locateFile': function (path) { + if (!path.startsWith('godot.')) { + return path; + } else if (path.endsWith('.worker.js')) { + return `${loadPath}.worker.js`; + } else if (path.endsWith('.audio.worklet.js')) { + return `${loadPath}.audio.worklet.js`; + } else if (path.endsWith('.js')) { + return `${loadPath}.js`; + } else if (path in gdext) { + return path; + } else if (path.endsWith('.side.wasm')) { + return `${loadPath}.side.wasm`; + } else if (path.endsWith('.wasm')) { + return `${loadPath}.wasm`; + } + return path; + }, + }; + }; + + /** + * @ignore + * @param {function()} cleanup + */ + Config.prototype.getGodotConfig = function (cleanup) { + // Try to find a canvas + if (!(this.canvas instanceof HTMLCanvasElement)) { + const nodes = document.getElementsByTagName('canvas'); + if (nodes.length && nodes[0] instanceof HTMLCanvasElement) { + const first = nodes[0]; + this.canvas = /** @type {!HTMLCanvasElement} */ (first); + } + if (!this.canvas) { + throw new Error('No canvas found in page'); + } + } + // Canvas can grab focus on click, or key events won't work. + if (this.canvas.tabIndex < 0) { + this.canvas.tabIndex = 0; + } + + // Browser locale, or custom one if defined. + let locale = this.locale; + if (!locale) { + locale = navigator.languages ? navigator.languages[0] : navigator.language; + locale = locale.split('.')[0]; + } + locale = locale.replace('-', '_'); + const onExit = this.onExit; + + // Godot configuration. + return { + 'canvas': this.canvas, + 'canvasResizePolicy': this.canvasResizePolicy, + 'locale': locale, + 'persistentDrops': this.persistentDrops, + 'virtualKeyboard': this.experimentalVK, + 'focusCanvas': this.focusCanvas, + 'onExecute': this.onExecute, + 'onExit': function (p_code) { + cleanup(); // We always need to call the cleanup callback to free memory. + if (typeof (onExit) === 'function') { + onExit(p_code); + } + }, + }; + }; + return new Config(initConfig); +}; + +/** + * Projects exported for the Web expose the :js:class:`Engine` class to the JavaScript environment, that allows + * fine control over the engine's start-up process. + * + * This API is built in an asynchronous manner and requires basic understanding + * of `Promises `__. + * + * @module Engine + * @header Web export JavaScript reference + */ +const Engine = (function () { + const preloader = new Preloader(); + + let loadPromise = null; + let loadPath = ''; + let initPromise = null; + + /** + * @classdesc The ``Engine`` class provides methods for loading and starting exported projects on the Web. For default export + * settings, this is already part of the exported HTML page. To understand practical use of the ``Engine`` class, + * see :ref:`Custom HTML page for Web export `. + * + * @description Create a new Engine instance with the given configuration. + * + * @global + * @constructor + * @param {EngineConfig} initConfig The initial config for this instance. + */ + function Engine(initConfig) { // eslint-disable-line no-shadow + this.config = new InternalConfig(initConfig); + this.rtenv = null; + } + + /** + * Load the engine from the specified base path. + * + * @param {string} basePath Base path of the engine to load. + * @param {number=} [size=0] The file size if known. + * @returns {Promise} A Promise that resolves once the engine is loaded. + * + * @function Engine.load + */ + Engine.load = function (basePath, size) { + if (loadPromise == null) { + loadPath = basePath; + loadPromise = preloader.loadPromise(`${loadPath}.wasm`, size, true); + requestAnimationFrame(preloader.animateProgress); + } + return loadPromise; + }; + + /** + * Unload the engine to free memory. + * + * This method will be called automatically depending on the configuration. See :js:attr:`unloadAfterInit`. + * + * @function Engine.unload + */ + Engine.unload = function () { + loadPromise = null; + }; + + /** + * Safe Engine constructor, creates a new prototype for every new instance to avoid prototype pollution. + * @ignore + * @constructor + */ + function SafeEngine(initConfig) { + const proto = /** @lends Engine.prototype */ { + /** + * Initialize the engine instance. Optionally, pass the base path to the engine to load it, + * if it hasn't been loaded yet. See :js:meth:`Engine.load`. + * + * @param {string=} basePath Base path of the engine to load. + * @return {Promise} A ``Promise`` that resolves once the engine is loaded and initialized. + */ + init: function (basePath) { + if (initPromise) { + return initPromise; + } + if (loadPromise == null) { + if (!basePath) { + initPromise = Promise.reject(new Error('A base path must be provided when calling `init` and the engine is not loaded.')); + return initPromise; + } + Engine.load(basePath, this.config.fileSizes[`${basePath}.wasm`]); + } + const me = this; + function doInit(promise) { + // Care! Promise chaining is bogus with old emscripten versions. + // This caused a regression with the Mono build (which uses an older emscripten version). + // Make sure to test that when refactoring. + return new Promise(function (resolve, reject) { + promise.then(function (response) { + const cloned = new Response(response.clone().body, { 'headers': [['content-type', 'application/wasm']] }); + Godot(me.config.getModuleConfig(loadPath, cloned)).then(function (module) { + const paths = me.config.persistentPaths; + module['initFS'](paths).then(function (err) { + me.rtenv = module; + if (me.config.unloadAfterInit) { + Engine.unload(); + } + resolve(); + }); + }); + }); + }); + } + preloader.setProgressFunc(this.config.onProgress); + initPromise = doInit(loadPromise); + return initPromise; + }, + + /** + * Load a file so it is available in the instance's file system once it runs. Must be called **before** starting the + * instance. + * + * If not provided, the ``path`` is derived from the URL of the loaded file. + * + * @param {string|ArrayBuffer} file The file to preload. + * + * If a ``string`` the file will be loaded from that path. + * + * If an ``ArrayBuffer`` or a view on one, the buffer will used as the content of the file. + * + * @param {string=} path Path by which the file will be accessible. Required, if ``file`` is not a string. + * + * @returns {Promise} A Promise that resolves once the file is loaded. + */ + preloadFile: function (file, path) { + return preloader.preload(file, path, this.config.fileSizes[file]); + }, + + /** + * Start the engine instance using the given override configuration (if any). + * :js:meth:`startGame ` can be used in typical cases instead. + * + * This will initialize the instance if it is not initialized. For manual initialization, see :js:meth:`init `. + * The engine must be loaded beforehand. + * + * Fails if a canvas cannot be found on the page, or not specified in the configuration. + * + * @param {EngineConfig} override An optional configuration override. + * @return {Promise} Promise that resolves once the engine started. + */ + start: function (override) { + this.config.update(override); + const me = this; + return me.init().then(function () { + if (!me.rtenv) { + return Promise.reject(new Error('The engine must be initialized before it can be started')); + } + + let config = {}; + try { + config = me.config.getGodotConfig(function () { + me.rtenv = null; + }); + } catch (e) { + return Promise.reject(e); + } + // Godot configuration. + me.rtenv['initConfig'](config); + + // Preload GDExtension libraries. + if (me.config.gdextensionLibs.length > 0 && !me.rtenv['loadDynamicLibrary']) { + return Promise.reject(new Error('GDExtension libraries are not supported by this engine version. ' + + 'Enable "Extensions Support" for your export preset and/or build your custom template with "dlink_enabled=yes".')); + } + return new Promise(function (resolve, reject) { + for (const file of preloader.preloadedFiles) { + me.rtenv['copyToFS'](file.path, file.buffer); + } + preloader.preloadedFiles.length = 0; // Clear memory + me.rtenv['callMain'](me.config.args); + initPromise = null; + me.installServiceWorker(); + resolve(); + }); + }); + }, + + /** + * Start the game instance using the given configuration override (if any). + * + * This will initialize the instance if it is not initialized. For manual initialization, see :js:meth:`init `. + * + * This will load the engine if it is not loaded, and preload the main pck. + * + * This method expects the initial config (or the override) to have both the :js:attr:`executable` and :js:attr:`mainPack` + * properties set (normally done by the editor during export). + * + * @param {EngineConfig} override An optional configuration override. + * @return {Promise} Promise that resolves once the game started. + */ + startGame: function (override) { + this.config.update(override); + // Add main-pack argument. + const exe = this.config.executable; + const pack = this.config.mainPack || `${exe}.pck`; + this.config.args = ['--main-pack', pack].concat(this.config.args); + // Start and init with execName as loadPath if not inited. + const me = this; + return Promise.all([ + this.init(exe), + this.preloadFile(pack, pack), + ]).then(function () { + return me.start.apply(me); + }); + }, + + /** + * Create a file at the specified ``path`` with the passed as ``buffer`` in the instance's file system. + * + * @param {string} path The location where the file will be created. + * @param {ArrayBuffer} buffer The content of the file. + */ + copyToFS: function (path, buffer) { + if (this.rtenv == null) { + throw new Error('Engine must be inited before copying files'); + } + this.rtenv['copyToFS'](path, buffer); + }, + + /** + * Request that the current instance quit. + * + * This is akin the user pressing the close button in the window manager, and will + * have no effect if the engine has crashed, or is stuck in a loop. + * + */ + requestQuit: function () { + if (this.rtenv) { + this.rtenv['request_quit'](); + } + }, + + /** + * Install the progressive-web app service worker. + * @returns {Promise} The service worker registration promise. + */ + installServiceWorker: function () { + if (this.config.serviceWorker && 'serviceWorker' in navigator) { + return navigator.serviceWorker.register(this.config.serviceWorker); + } + return Promise.resolve(); + }, + }; + + Engine.prototype = proto; + // Closure compiler exported instance methods. + Engine.prototype['init'] = Engine.prototype.init; + Engine.prototype['preloadFile'] = Engine.prototype.preloadFile; + Engine.prototype['start'] = Engine.prototype.start; + Engine.prototype['startGame'] = Engine.prototype.startGame; + Engine.prototype['copyToFS'] = Engine.prototype.copyToFS; + Engine.prototype['requestQuit'] = Engine.prototype.requestQuit; + Engine.prototype['installServiceWorker'] = Engine.prototype.installServiceWorker; + // Also expose static methods as instance methods + Engine.prototype['load'] = Engine.load; + Engine.prototype['unload'] = Engine.unload; + return new Engine(initConfig); + } + + // Closure compiler exported static methods. + SafeEngine['load'] = Engine.load; + SafeEngine['unload'] = Engine.unload; + + // Feature-detection utilities. + SafeEngine['isWebGLAvailable'] = Features.isWebGLAvailable; + SafeEngine['isFetchAvailable'] = Features.isFetchAvailable; + SafeEngine['isSecureContext'] = Features.isSecureContext; + SafeEngine['isCrossOriginIsolated'] = Features.isCrossOriginIsolated; + SafeEngine['isSharedArrayBufferAvailable'] = Features.isSharedArrayBufferAvailable; + SafeEngine['isAudioWorkletAvailable'] = Features.isAudioWorkletAvailable; + SafeEngine['getMissingFeatures'] = Features.getMissingFeatures; + + return SafeEngine; +}()); +if (typeof window !== 'undefined') { + window['Engine'] = Engine; +} diff --git a/build/web/Test Project.pck b/build/web/Test Project.pck new file mode 100644 index 0000000..7965c68 Binary files /dev/null and b/build/web/Test Project.pck differ diff --git a/build/web/Test Project.png b/build/web/Test Project.png new file mode 100644 index 0000000..766b0b6 Binary files /dev/null and b/build/web/Test Project.png differ diff --git a/build/web/Test Project.png.import b/build/web/Test Project.png.import new file mode 100644 index 0000000..a4e39f8 --- /dev/null +++ b/build/web/Test Project.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d3eyhvfobywi1" +path="res://.godot/imported/Test Project.png-7caed56d1488c410323711de0c53c191.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://build/web/Test Project.png" +dest_files=["res://.godot/imported/Test Project.png-7caed56d1488c410323711de0c53c191.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/build/web/Test Project.wasm b/build/web/Test Project.wasm new file mode 100644 index 0000000..121549f Binary files /dev/null and b/build/web/Test Project.wasm differ diff --git a/export_presets.cfg b/export_presets.cfg new file mode 100644 index 0000000..5ea8789 --- /dev/null +++ b/export_presets.cfg @@ -0,0 +1,146 @@ +[preset.0] + +name="Web" +platform="Web" +runnable=true +advanced_options=true +dedicated_server=false +custom_features="" +export_filter="all_resources" +include_filter="" +exclude_filter="" +export_path="build/web/Test Project.html" +encryption_include_filters="" +encryption_exclude_filters="" +encrypt_pck=false +encrypt_directory=false +script_export_mode=2 + +[preset.0.options] + +custom_template/debug="" +custom_template/release="" +variant/extensions_support=false +variant/thread_support=false +vram_texture_compression/for_desktop=true +vram_texture_compression/for_mobile=false +html/export_icon=true +html/custom_html_shell="" +html/head_include="" +html/canvas_resize_policy=2 +html/focus_canvas_on_start=true +html/experimental_virtual_keyboard=false +progressive_web_app/enabled=false +progressive_web_app/ensure_cross_origin_isolation_headers=true +progressive_web_app/offline_page="" +progressive_web_app/display=1 +progressive_web_app/orientation=0 +progressive_web_app/icon_144x144="" +progressive_web_app/icon_180x180="" +progressive_web_app/icon_512x512="" +progressive_web_app/background_color=Color(0, 0, 0, 1) + +[preset.1] + +name="Linux" +platform="Linux" +runnable=true +advanced_options=true +dedicated_server=false +custom_features="" +export_filter="all_resources" +include_filter="" +exclude_filter="" +export_path="build/game.x86_64" +encryption_include_filters="" +encryption_exclude_filters="" +encrypt_pck=false +encrypt_directory=false +script_export_mode=2 + +[preset.1.options] + +custom_template/debug="" +custom_template/release="" +debug/export_console_wrapper=1 +binary_format/embed_pck=false +texture_format/s3tc_bptc=true +texture_format/etc2_astc=false +binary_format/architecture="x86_64" +ssh_remote_deploy/enabled=false +ssh_remote_deploy/host="user@host_ip" +ssh_remote_deploy/port="22" +ssh_remote_deploy/extra_args_ssh="" +ssh_remote_deploy/extra_args_scp="" +ssh_remote_deploy/run_script="#!/usr/bin/env bash +export DISPLAY=:0 +unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\" +\"{temp_dir}/{exe_name}\" {cmd_args}" +ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash +kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\") +rm -rf \"{temp_dir}\"" + +[preset.2] + +name="Windows Desktop" +platform="Windows Desktop" +runnable=true +advanced_options=false +dedicated_server=false +custom_features="" +export_filter="all_resources" +include_filter="" +exclude_filter="" +export_path="" +encryption_include_filters="" +encryption_exclude_filters="" +encrypt_pck=false +encrypt_directory=false +script_export_mode=2 + +[preset.2.options] + +custom_template/debug="" +custom_template/release="" +debug/export_console_wrapper=1 +binary_format/embed_pck=false +texture_format/s3tc_bptc=true +texture_format/etc2_astc=false +binary_format/architecture="x86_64" +codesign/enable=false +codesign/timestamp=true +codesign/timestamp_server_url="" +codesign/digest_algorithm=1 +codesign/description="" +codesign/custom_options=PackedStringArray() +application/modify_resources=true +application/icon="" +application/console_wrapper_icon="" +application/icon_interpolation=4 +application/file_version="" +application/product_version="" +application/company_name="" +application/product_name="" +application/file_description="" +application/copyright="" +application/trademarks="" +application/export_angle=0 +application/export_d3d12=0 +application/d3d12_agility_sdk_multiarch=true +ssh_remote_deploy/enabled=false +ssh_remote_deploy/host="user@host_ip" +ssh_remote_deploy/port="22" +ssh_remote_deploy/extra_args_ssh="" +ssh_remote_deploy/extra_args_scp="" +ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}' +$action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}' +$trigger = New-ScheduledTaskTrigger -Once -At 00:00 +$settings = New-ScheduledTaskSettingsSet +$task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings +Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true +Start-ScheduledTask -TaskName godot_remote_debug +while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 } +Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue" +ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue +Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue +Remove-Item -Recurse -Force '{temp_dir}'" diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..c099cb8 --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icon.svg.import b/icon.svg.import new file mode 100644 index 0000000..de09409 --- /dev/null +++ b/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://xddnpif04q1m" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..f626b9d --- /dev/null +++ b/project.godot @@ -0,0 +1,92 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="Test Project" +run/main_scene="res://assets/main.tscn" +config/features=PackedStringArray("4.3", "GL Compatibility") +boot_splash/show_image=false +boot_splash/fullsize=false +config/icon="res://icon.svg" + +[autoload] + +GDRx="*res://addons/reactivex/__gdrxsingleton__.gd" +Console="*res://addons/console/console.gd" + +[display] + +window/size/viewport_width=1920 +window/size/viewport_height=1080 +window/size/resizable=false +window/stretch/mode="viewport" +window/vsync/vsync_mode=0 + +[dotnet] + +project/assembly_name="Test Project" + +[editor_plugins] + +enabled=PackedStringArray("res://addons/Todo_Manager/plugin.cfg", "res://addons/console/plugin.cfg", "res://addons/reactivex/plugin.cfg") + +[input] + +move_forward={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) +] +} +move_left={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null) +] +} +move_right={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null) +] +} +move_back={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) +] +} +quentincaffeino_console_toggle={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +quentincaffeino_console_autocomplete={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +quentincaffeino_console_history_up={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} +quentincaffeino_console_history_down={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null) +] +} + +[input_devices] + +pointing/android/enable_long_press_as_right_click=true +pointing/android/enable_pan_and_scale_gestures=true + +[rendering] + +renderer/rendering_method="gl_compatibility" +renderer/rendering_method.mobile="gl_compatibility" diff --git a/src/camera_3d.gd b/src/camera_3d.gd new file mode 100644 index 0000000..975afa9 --- /dev/null +++ b/src/camera_3d.gd @@ -0,0 +1,15 @@ +extends Camera3D + +func _ready() -> void: + pass # Replace with function body. + +func _process(delta: float) -> void: + pass + +func _unhandled_input(event: InputEvent) -> void: + if event is InputEventMouseButton: + if event.is_pressed(): + if event.button_index == MOUSE_BUTTON_WHEEL_UP: + self.fov -= 1 * (5 if Input.is_key_pressed(KEY_SHIFT) else 1) + if event.button_index == MOUSE_BUTTON_WHEEL_DOWN: + self.fov += 1 * (5 if Input.is_key_pressed(KEY_SHIFT) else 1) diff --git a/src/camera_manager.gd b/src/camera_manager.gd new file mode 100644 index 0000000..ff635ab --- /dev/null +++ b/src/camera_manager.gd @@ -0,0 +1,11 @@ +extends Node + + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta: float) -> void: + pass diff --git a/src/collision_shape_3d.gd b/src/collision_shape_3d.gd new file mode 100644 index 0000000..25ee7c5 --- /dev/null +++ b/src/collision_shape_3d.gd @@ -0,0 +1,12 @@ +extends CollisionShape3D + +@export var Level:= 3.0 + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta: float) -> void: + pass diff --git a/src/console_manager.gd b/src/console_manager.gd new file mode 100644 index 0000000..d790d38 --- /dev/null +++ b/src/console_manager.gd @@ -0,0 +1,9 @@ +extends Node + +func _ready() -> void: + Console.add_command("kill", suicide) + Console.add_command("kill", suicide, 1) + +func suicide(target = null) -> void: + var s = "Boom %s" % (target if target else "") + Console.print_line(s) diff --git a/src/dialogue_manager.gd b/src/dialogue_manager.gd new file mode 100644 index 0000000..d7818a0 --- /dev/null +++ b/src/dialogue_manager.gd @@ -0,0 +1,6 @@ +extends Node + + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + pass # Replace with function body. diff --git a/src/input_manager.gd b/src/input_manager.gd new file mode 100644 index 0000000..9c50075 --- /dev/null +++ b/src/input_manager.gd @@ -0,0 +1,9 @@ +extends Node + +func _ready() -> void: + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta: float) -> void: + pass diff --git a/src/level_manager.gd b/src/level_manager.gd new file mode 100644 index 0000000..5481d7f --- /dev/null +++ b/src/level_manager.gd @@ -0,0 +1,22 @@ +extends Node + +signal scene_transition + +var CURRENT_LEVEL: Node3D = null +var CURRENT_PLAYER: CharacterBody3D = null + +func _ready() -> void: + var lvl = load("res://assets/levels/level1.tscn") + var player = load("res://assets/characters/player.tscn") + CURRENT_LEVEL = lvl.instantiate() + CURRENT_PLAYER = player.instantiate() + CURRENT_PLAYER.transform.origin = Vector3(0, 1, 0) + add_child(CURRENT_LEVEL) + CURRENT_LEVEL.add_child(CURRENT_PLAYER) + #CURRENT_LEVEL.remove_child() + +func _process(delta: float) -> void: + if (CURRENT_PLAYER.global_position.y) < -5: + CURRENT_PLAYER.translate(Vector3.UP*10) + # TODO: blah blah + diff --git a/src/logo.gd b/src/logo.gd new file mode 100644 index 0000000..7e1df9c --- /dev/null +++ b/src/logo.gd @@ -0,0 +1,11 @@ +extends Sprite2D + + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + pass # Replace with function body. + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta: float) -> void: + self.position.x += randi() diff --git a/src/main.gd b/src/main.gd new file mode 100644 index 0000000..a58e757 --- /dev/null +++ b/src/main.gd @@ -0,0 +1,11 @@ +extends Node + +@export var test := 100 +signal showMenu + +func _ready() -> void: + GDRx.start_periodic_timer(1.0) \ + .subscribe(func(i): print("Tick: ", i)) \ + .dispose_with(self) +func move() -> void: + pass diff --git a/src/spine_sprite.gd b/src/spine_sprite.gd new file mode 100644 index 0000000..642c8f3 --- /dev/null +++ b/src/spine_sprite.gd @@ -0,0 +1,7 @@ +extends SpineSprite + +func _ready() -> void: + get_animation_state().set_animation("animation", true, 0) + +func _process(delta: float) -> void: + pass diff --git a/src/state_manager.gd b/src/state_manager.gd new file mode 100644 index 0000000..e3bd34d --- /dev/null +++ b/src/state_manager.gd @@ -0,0 +1,19 @@ +extends Node +enum GameState { + INIT, + START, + MENUMAIN, + MENULAPTOP, + CUTSCENE, + COMBAT, + VICTORY, + LOSS, + DIALOGUE +} + +@export var GAMESTATE = GameState.START +@export var CURRENT_LEVEL = null + +func _ready() -> void: + + pass # Replace with function body. diff --git a/src/viewport.gd b/src/viewport.gd new file mode 100644 index 0000000..3227130 --- /dev/null +++ b/src/viewport.gd @@ -0,0 +1,11 @@ +@tool +extends MeshInstance3D + +@onready var camera = $"../Camera3D" + +func _ready(): + if material_override != null: + material_override.albedo_texture = $SubViewport.get_texture() + +#func _process(delta): + #self.look_at(camera.global_position, Vector3.UP, true)