This commit is contained in:
user 2025-01-11 00:25:52 +01:00
commit 74502c52dd
475 changed files with 25680 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/

View File

@ -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

View File

@ -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

297
addons/Todo_Manager/Dock.gd Normal file
View File

@ -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)

View File

@ -0,0 +1,19 @@
@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

View File

@ -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

View File

@ -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

View File

@ -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"]

View File

@ -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"

View File

@ -0,0 +1,7 @@
extends Node
# TODO: this is a TODO
# HACK: this is a HACK
# FIXME: this is a FIXME
# TODO this works too
#Hack any format will do

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dx5m1i244ub28"
path="res://.godot/imported/Instruct1.png-698c6faa3ef3ac4960807faa3e028bd6.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/Todo_Manager/doc/images/Instruct1.png"
dest_files=["res://.godot/imported/Instruct1.png-698c6faa3ef3ac4960807faa3e028bd6.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://kaiwqtyohu0e"
path="res://.godot/imported/Instruct2.png-4e8664e49d00a397bbd539f7dee976ff.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/Todo_Manager/doc/images/Instruct2.png"
dest_files=["res://.godot/imported/Instruct2.png-4e8664e49d00a397bbd539f7dee976ff.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dy2822d83t0aa"
path="res://.godot/imported/Instruct3.png-3cc62ed99bf29d90b803cb8eb40881e9.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/Todo_Manager/doc/images/Instruct3.png"
dest_files=["res://.godot/imported/Instruct3.png-3cc62ed99bf29d90b803cb8eb40881e9.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bvf32pbtep5eq"
path="res://.godot/imported/Instruct4.png-bf5aa1cffc066175cecf9281b0774809.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/Todo_Manager/doc/images/Instruct4.png"
dest_files=["res://.godot/imported/Instruct4.png-bf5aa1cffc066175cecf9281b0774809.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ddoxpi6ium7hq"
path="res://.godot/imported/Instruct5.png-001538ed8b5682dcf232de08035aab38.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/Todo_Manager/doc/images/Instruct5.png"
dest_files=["res://.godot/imported/Instruct5.png-001538ed8b5682dcf232de08035aab38.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://vi5mj4m4i70i"
path="res://.godot/imported/TODO_Manager_Logo.png-e07d7ec75201c66b732ef87ec1bece15.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/Todo_Manager/doc/images/TODO_Manager_Logo.png"
dest_files=["res://.godot/imported/TODO_Manager_Logo.png-e07d7ec75201c66b732ef87ec1bece15.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dm4238rk6sken"
path="res://.godot/imported/example1.png-6386c332ca46e1e62ea061b956a901cd.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/Todo_Manager/doc/images/example1.png"
dest_files=["res://.godot/imported/example1.png-6386c332ca46e1e62ea061b956a901cd.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://csmkq6165b5yj"
path="res://.godot/imported/example2.png-2e3a8f9cd1e178daf22b83dc0513f37a.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/Todo_Manager/doc/images/example2.png"
dest_files=["res://.godot/imported/example2.png-2e3a8f9cd1e178daf22b83dc0513f37a.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

View File

@ -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"

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,9 @@
@tool
extends RefCounted
var pattern : String
var title : String
var content : String
var script_path : String
var line_number : int

21
addons/reactivex/LICENSE Normal file
View File

@ -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.

422
addons/reactivex/README.md Normal file
View File

@ -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).

View File

@ -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")

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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__

View File

@ -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

View File

@ -0,0 +1,2 @@
## Represents the end of an iterable sequence.
class_name ItEnd

View File

@ -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()

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,7 @@
class_name StartableBase
func start():
NotImplementedError.raise()
func wait_to_finish():
NotImplementedError.raise()

View File

@ -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()

View File

@ -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 []

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -0,0 +1,5 @@
extends ReadOnlyReactivePropertyBase
class_name ReactivePropertyBase
func _set_value(_value):
NotImplementedError.raise()

View File

@ -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()

View File

@ -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__

View File

@ -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__

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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 "<<Disposed ReactiveCollection>>"
return str(self._data)

View File

@ -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 "<<Disposed ReactiveDictionary>>"
return str(self._data)
func iter() -> Iterator:
return GDRx.iter(self._data)

View File

@ -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 "<<Disposed ReactiveProperty>>"
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)
)

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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 "<<Disposed ReadOnlyReactiveProperty>>"
return str(self.Value)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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_)

View File

@ -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

View File

@ -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"])

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

Some files were not shown because too many files have changed in this diff Show More