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)