-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclass_signals.gml
230 lines (193 loc) · 7.8 KB
/
class_signals.gml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
/// @about Creates a new signaler system that can execute multiple methods
/// at a time while passing optional static and dynamic arguments.
///
/// Signalers can be slow to create / destroy but they are fast to
/// execute. Excessive addition / removal of signals can cause slow-down
/// and should be minimized as much as possible.
///
/// Signals can accept either regular method() structures or Callable()
/// structures which allow pre-defining arguments. When removing a signal
/// the same structure must be provided along with the argument values used
/// when attached.
///
/// Callable() structures are compared based on their contained values and
/// not their references. Any Callable() structures attached will be auto-deleted
/// upon detaching. If this is undesired, a duplicate of your Callable() should
/// be attached instead.
function Signaler() constructor{
#region MEMBERS
signal_map = {}; // signal -> ref pairs
#endregion
#region METHODS
/// @desc Cleans the signaler, removing all attached signals.
function clear(){
var array = struct_get_names(signal_map);
for (var i = array_length(array) - 1; i >= 0; --i)
clear_label(array[i]);
}
/// @desc Cleans the signaler, removing all attached signals for a given label.
/// @param {string} label
/// @return {bool} whether or not the signal map was changed
function clear_label(name=""){
var array = signal_map[$ name];
if (is_undefined(array))
return false;
for (var i = 0; i < array_length(array); ++i)
delete array[i];
struct_remove(signal_map, name);
return true;
}
/// @desc Returns whether the specified signal is defined in the system.
/// @param {string} name name of the signal that was added
/// @param {method} method method/callable of the signal that was added
/// @return {bool} true if success
function signal_exists(_name, _method){
var array = signal_map[$ _name];
if (is_undefined(array))
return false;
var callable = (is_instanceof(_method, Callable) ? _method : new Callable(method_get_self(_method), method_get_index(_method)));
for (var i = array_length(array) - 1; i >= 0; --i){
if (array[i].is_equal(callable))
return true;
}
return false;
}
/// @desc Takes a method or Callable and ties it to a string label so that it
/// it is executed whenever the specified label is triggered.
/// @param {string} name name to give the signal
/// @param {method} method method/callale to execute upon call
function add_signal(_name, _method){
if (not is_method(_method) and not is_instanceof(_method, Callable)){
throw "[argument1] invalid type, expected [method] or [Callable]!";
return;
}
// Grab our signal array:
var array = (signal_map[$ _name] ?? []);
array_push(array, is_instanceof(_method, Callable) ? _method : new Callable(method_get_self(_method), method_get_index(_method)));
signal_map[$ _name] = array;
}
/// @desc Performs the same as add_signal() except the signal is added to the front
/// of the execution order.
function add_signal_front(_name, _method){
if (not is_method(_method) and not is_instanceof(_method, Callable)){
throw "[argument1] invalid type, expected [method] or [Callable]!";
return;
}
// Grab our signal array:
var array = (signal_map[$ _name] ?? []);
array = array_concat([is_instanceof(_method, Callable) ? _method : new Callable(method_get_self(_method), method_get_index(_method))], array);
signal_map[$ _name] = array;
}
/// @desc Removes a signal from the signaling system. The method/callable must
/// specify the exact same data as when it was added in order to successfully
/// remove it.
/// @param {string} name name of the signal that was added
/// @param {method} method method of the signal that was added
/// @return {bool} true if success
function remove_signal(_name, _method){
var array = signal_map[$ _name];
if (is_undefined(array))
return false;
var callable = (is_instanceof(_method, Callable) ? _method : new Callable(method_get_self(_method), method_get_index(_method)));
var found_value = false;
for (var i = array_length(array) - 1; i >= 0; --i){
if (array[i].is_equal(callable)){
delete array[i];
array_delete(array, i, 1);
found_value = true;
}
}
signal_map[$ _name] = array;
return found_value;
}
/// @desc Triggers a signal, if it exists, and overrides any pre-specified
/// arguments with the specified argument array.
/// @param {string} name name of the signal to trigger
/// @param {array} argv=[] argument array to pass
function signal(name, argv=[]){
// Look up our signal:
var array = array_duplicate_shallow(signal_map[$ name]);
if (is_undefined(array))
return; // No signal w/ this name
if (not is_array(argv))
argv = [argv];
// Loop through each attached signal:
for (var i = 0; i < array_length(array); ++i){
var callable = array[i];
callable.call(argv);
}
}
/// @desc Convert-to-string override to print out some useful signal data
/// if required.
function toString(){
return string_ext("[signaler:{0}]", [struct_names_count(signal_map)]);
}
#endregion
}
/// @desc A callable is effectively a method() that can also take arguments which will
/// be auto-passed when called.
/// @warning Due to the identifier hash, passing in a large struct as an instance or argv
/// value may cause a slow construction! Override the struct's toString() to mitigate this.
/// @param {any} instance the instance / struct to call in the contect of
/// @param {function} function the function to call when executed
/// @param {array} argv=[] the arguments to pass when executed
function Callable(_instance, _function, argv=[]) constructor {
#region PROPERTIES
method_ref = method(_instance, _function);
safe_ref = weak_ref_create(_instance);
identifier = "";
self.argv = argv;
#endregion
#region METHODS
function get_method(){
return method_ref;
}
/// @desc Executes the Callable. Any arguments passed will OVERRIDE the currently
/// stored argv values! E.g., if you created the callable with argv=[1, 2]
/// and executed call([2]) then the system would execute with argv=[2, 2]
/// If a value is undefined then the default pre-defined value will be
/// substituted in.
function call(argv=[]){
if (not weak_ref_alive(safe_ref)) // The data is gone, don't execute
return;
var other_loop = array_length(argv);
var loop = max(array_length(self.argv), other_loop);
var nargv = array_create(loop);
for (var i = 0; i < loop; ++i){
if (i < other_loop)
nargv[i] = argv[i];
else
nargv[i] = self.argv[i];
}
method_call(method_ref, nargv);
}
/// @desc Returns if the specified callable contains the same data.
/// @param {Callable} callable callable instance to check against
/// @return {bool}
function is_equal(callable){
if (not is_instanceof(callable, Callable))
return false;
return identifier == callable.identifier;
}
/// @desc Creates an identical copy of this callable instance.
/// @return {Callable}
function duplicate(){
return new Callable(method_get_self(method_ref), method_get_index(method_ref), array_duplicate_shallow(argv));
}
#endregion
#region INIT
identifier = md5_string_utf8(string(_instance) + string(_function) + string(argv));
#endregion
}
/// @desc Creates a shallow copy of the specified array and returns the result.
function array_duplicate_shallow(array, offset=0, count=-1){
if (count < 0)
count = array_length(array) - offset;
if (count == 0)
return [];
var narray = array_create(count);
var nindex = count - 1;
for (var i = offset + count - 1; i >= offset; --i)
narray[nindex--] = array[i];
return narray;
}