aotrautils 0.0.1067 → 0.0.1069

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
 
2
2
 
3
- /*utils COMMONS library associated with aotra version : «1_29072022-2359 (13/10/2024-22:45:41)»*/
3
+ /*utils COMMONS library associated with aotra version : «1_29072022-2359 (19/02/2025-23:41:19)»*/
4
4
  /*-----------------------------------------------------------------------------*/
5
5
 
6
6
 
@@ -1551,6 +1551,13 @@ aotest.profile=function(rootObject,methodName,visited=[]){
1551
1551
 
1552
1552
 
1553
1553
 
1554
+ //================================================================
1555
+ //================= Tjread control utility methods =================
1556
+ //================================================================
1557
+
1558
+
1559
+ window.sleep = (millis) => new Promise( resolve => setTimeout(resolve, millis) );
1560
+
1554
1561
 
1555
1562
 
1556
1563
  //================================================================
@@ -1988,6 +1995,15 @@ window.compare=function(value1,value2){
1988
1995
  if(value1==value2) return 0;
1989
1996
  }
1990
1997
 
1998
+ window.pushInArrayAsQueue=function(array, maxSize, item){
1999
+ if(maxSize<=getArraySize(array)){
2000
+ array.shift();
2001
+ }
2002
+ array.push(item);
2003
+ return item;
2004
+ }
2005
+
2006
+
1991
2007
 
1992
2008
  /*KNOWN LIMITATIONS : You will have an incomplete browsing loop if there are the string values "break" or "continue" in your array or associative array !*/
1993
2009
  // CAUTION : Only use «return foreach» syntax in client code loops with SINGLE-LEVEL nested loops ONLY !
@@ -1998,7 +2014,7 @@ window.foreach=function(arrayOfValues,doOnEachFunction,
1998
2014
  // -1: if(a<b)
1999
2015
  // 1: if(a>b)
2000
2016
  // for instance this is a valid javascript compare function : function(a, b){return a-b} so (100,20) returns positive result and (20,100) returns negative result !
2001
- /*OPTIONAL*/compareFunction=null){
2017
+ /*OPTIONAL*/compareFunction=null, sortKeys=false){
2002
2018
 
2003
2019
  // TODO : FIXME : Add the possibility to iterate over a string characters ?
2004
2020
 
@@ -2054,7 +2070,8 @@ window.foreach=function(arrayOfValues,doOnEachFunction,
2054
2070
 
2055
2071
  // Else : associative array
2056
2072
 
2057
- var associativeArray=arrayOfValues;
2073
+ let keysToIterateOn=[];
2074
+ const associativeArray=arrayOfValues;
2058
2075
  if(compareFunction){
2059
2076
 
2060
2077
  // if(!associativeArray.hasOwnProperty){
@@ -2075,24 +2092,26 @@ window.foreach=function(arrayOfValues,doOnEachFunction,
2075
2092
  }
2076
2093
  sortedKeysAndValues.sort(compareFunction);
2077
2094
 
2078
- associativeArray={};
2095
+ // OLD : associativeArray={};
2079
2096
  for(var i=0;i<sortedKeysAndValues.length;i++){
2080
2097
  var item=sortedKeysAndValues[i];
2081
- associativeArray[item.key]=item.value;
2098
+ // OLD : associativeArray[item.key]=item.value;
2099
+ keysToIterateOn.push(item.key);
2082
2100
  }
2083
2101
 
2102
+ }else{
2103
+ keysToIterateOn=Object.keys(associativeArray);
2084
2104
  }
2085
2105
 
2086
- //if(!associativeArray.hasOwnProperty){
2087
- // // TRACE
2088
- // console.log("WARN : Object of type «"+(typeof(associativeArray))+"» has no method hasOwnProperty() method, cannot perform foreach ! ",associativeArray);
2089
- // return null;
2090
- //}
2091
-
2106
+ if(!compareFunction && sortKeys){
2107
+ keysToIterateOn=keysToIterateOn.sort();
2108
+ }
2109
+
2092
2110
  var cnt=0;
2093
- for(var key in associativeArray){
2111
+ for(var key of keysToIterateOn){
2094
2112
  if(associativeArray.hasOwnProperty && !associativeArray.hasOwnProperty(key)) continue;
2095
2113
  var item=associativeArray[key];
2114
+
2096
2115
  if(!compareFunction){ // If we have a compare function, then array is already filtered !
2097
2116
  if(filterFunction && !filterFunction(item,key)) continue;
2098
2117
  }
@@ -2108,6 +2127,8 @@ window.foreach=function(arrayOfValues,doOnEachFunction,
2108
2127
  }// else continue looping...
2109
2128
  cnt++;
2110
2129
  }
2130
+
2131
+
2111
2132
 
2112
2133
  //
2113
2134
  return null;
@@ -2124,7 +2145,7 @@ window.getAt=function(associativeOrNormalArray,index,returnKey=false){
2124
2145
  return foreach(associativeOrNormalArray,(item, key)=>{
2125
2146
  if(index<=i) return returnKey?key:item;
2126
2147
  i++;
2127
- });
2148
+ },null,null,returnKey);
2128
2149
  }
2129
2150
 
2130
2151
  window.getFirst=function(associativeOrNormalArray){
@@ -2856,24 +2877,46 @@ Math.averageInArray=function(arrayOfValues){
2856
2877
  return Math.sumInArray(arrayOfValues) / arrayOfValues.length;
2857
2878
  };
2858
2879
 
2859
- Math.maxInArray=function(arrayOfValues){
2880
+ Math.maxInArray=function(arrayOfValues, returnKey=false){
2860
2881
  if(empty(arrayOfValues))
2861
2882
  return null;
2862
- var max=arrayOfValues[0];
2863
- for (var i=0; i<arrayOfValues.length; i++)
2864
- if(max<arrayOfValues[i])
2865
- max=arrayOfValues[i];
2866
- return max;
2883
+
2884
+ // var max=arrayOfValues[0];
2885
+ // for (var i=0; i<arrayOfValues.length; i++)
2886
+ // if(max<arrayOfValues[i])
2887
+ // max=arrayOfValues[i];
2888
+
2889
+ let returnedKey=getKeyAt(arrayOfValues,0);
2890
+ let max=getAt(arrayOfValues,0);
2891
+ foreach(arrayOfValues,(item, key)=>{
2892
+ if(max<item){
2893
+ returnedKey=key;
2894
+ max=item;
2895
+ }
2896
+ },null,null,true);
2897
+
2898
+ return (returnKey?returnedKey:max);
2867
2899
  };
2868
2900
 
2869
- Math.minInArray=function(arrayOfValues){
2901
+ Math.minInArray=function(arrayOfValues, returnKey=false){
2870
2902
  if(empty(arrayOfValues))
2871
2903
  return null;
2872
- var min=arrayOfValues[0];
2873
- for (var i=0; i<arrayOfValues.length; i++)
2874
- if(arrayOfValues[i]<min)
2875
- min=arrayOfValues[i];
2876
- return min;
2904
+
2905
+ // var min=arrayOfValues[0];
2906
+ // for (var i=0; i<arrayOfValues.length; i++)
2907
+ // if(arrayOfValues[i]<min)
2908
+ // min=arrayOfValues[i];
2909
+
2910
+ let returnedKey=getKeyAt(arrayOfValues,0);
2911
+ let min=getAt(arrayOfValues,0);
2912
+ foreach(arrayOfValues,(item, key)=>{
2913
+ if(item<min){
2914
+ returnedKey=key;
2915
+ min=item;
2916
+ }
2917
+ },null,null,true);
2918
+
2919
+ return (returnKey?returnedKey:min);
2877
2920
  };
2878
2921
 
2879
2922
 
@@ -5248,6 +5291,25 @@ window.stringifyObject=function(objectToStringify){
5248
5291
  }
5249
5292
 
5250
5293
 
5294
+ window.splitURL=(urlOrigin)=>{
5295
+ let protocol=null;
5296
+ let hostAndPort=urlOrigin;
5297
+ if(contains(urlOrigin,"://")){
5298
+ const hostAndPortSplits=urlOrigin.split("://");
5299
+ protocol=hostAndPortSplits[0];
5300
+ hostAndPort=hostAndPortSplits[1];
5301
+ }
5302
+ let host=null, port=null;
5303
+ if(contains(hostAndPort,":")){
5304
+ const splits=hostAndPort.split(":");
5305
+ host=splits[0];
5306
+ port=splits[1];
5307
+ }else{
5308
+ host=hostAndPort;
5309
+ }
5310
+ const isSecure=(protocol && contains(protocol,"s"));
5311
+ return {protocol:protocol, host:host, port:port, isSecure:isSecure};
5312
+ };
5251
5313
 
5252
5314
 
5253
5315
 
@@ -5259,7 +5321,7 @@ AOTRAUTILS_LIB_IS_LOADED=true;
5259
5321
 
5260
5322
 
5261
5323
 
5262
- /*utils CLIENT library associated with aotra version : «1_29072022-2359 (13/10/2024-22:45:41)»*/
5324
+ /*utils CLIENT library associated with aotra version : «1_29072022-2359 (19/02/2025-23:41:19)»*/
5263
5325
  /*-----------------------------------------------------------------------------*/
5264
5326
  /* ## Utility global methods in a browser (htmljs) client environment.
5265
5327
  *
@@ -8868,7 +8930,7 @@ getColorFingersPointer=(mediaHandler,colorsPattern="rgb")=>{
8868
8930
 
8869
8931
 
8870
8932
 
8871
- //Sound handling :
8933
+ // ====================================================== Sound handling ======================================================
8872
8934
 
8873
8935
 
8874
8936
  function playAudioData(audioData,audioCtxParam=null,audioBufferSizeParam=null,numberOfAudioChannelsParam=null,sampleRateParam=null,
@@ -8947,335 +9009,340 @@ function playAudioData(audioData,audioCtxParam=null,audioBufferSizeParam=null,nu
8947
9009
  }
8948
9010
 
8949
9011
 
9012
+ class SoundsLoop{
9013
+ constructor(doOnEnded=null){
8950
9014
 
8951
-
8952
- function loadSoundsLoop(filesPaths,doOnLoaded=null,doOnEnded=null){
9015
+ this.doOnEnded=doOnEnded;
9016
+
9017
+ this.audios=null;
9018
+ this.currentAudio=null;
9019
+ this.hasAbortedLooping=false;
9020
+ this.hasLoopStarted=false;
9021
+ this.volume=1;
9022
+ this.mainLoopSound=null;
9023
+ this.started=false;
8953
9024
 
8954
- let self={
8955
-
8956
- audios:null,
8957
- currentAudio:null,
8958
- hasAbortedLooping:false,
8959
- hasLoopStarted:false,
8960
- volume:1,
8961
- mainLoopSound:null,
8962
- started:false,
8963
- setVolume:function(newVolume=null){
8964
- if(newVolume) self.volume=newVolume;
8965
- return self;
8966
- },
8967
- loop:function(pauseMillis=null,fadeInMillis=null,fadeOutMillis=null){
8968
-
8969
- self.currentAudio=Math.getRandomInArray(self.audios);
8970
- self.currentAudio.setVolume(self.volume);
8971
-
8972
-
8973
- let startChainedLoop=function(){
8974
-
8975
- if(!self.started) return;
8976
-
8977
- if(fadeInMillis && fadeOutMillis){
8978
- if(fadeInMillis){
8979
- self.currentAudio.fadeIn(fadeInMillis);
8980
- }
8981
- if(fadeOutMillis){
8982
- self.currentAudio.fadeOut(fadeOutMillis,function(){
8983
-
8984
- // The next one to loop :
8985
- if(self.started) self.loop(pauseMillis,fadeInMillis,fadeOutMillis);
8986
- if(doOnEnded) doOnEnded(self);
8987
-
9025
+ }
8988
9026
 
8989
- });
8990
- }
8991
-
8992
-
8993
- }else{
8994
- if(!fadeInMillis){
8995
- self.currentAudio.jumpIn();
8996
- }
8997
- if(!fadeOutMillis){
8998
- self.currentAudio.jumpOut(function(){
8999
-
9000
- // The next one to loop : (the same, loop on itself)
9001
- if(self.started) self.loop(pauseMillis,fadeInMillis,fadeOutMillis);
9002
- if(doOnEnded) doOnEnded(self);
9027
+ setVolume(newVolume=null){
9028
+ if(newVolume) this.volume=newVolume;
9029
+ return this;
9030
+ }
9031
+ loop(pauseMillis=null,fadeInMillis=null,fadeOutMillis=null){
9032
+
9033
+ this.currentAudio=Math.getRandomInArray(this.audios);
9034
+ this.currentAudio.setVolume(this.volume);
9003
9035
 
9004
- }
9005
- );
9006
- }
9007
-
9036
+ this.pauseMillis=pauseMillis;
9037
+ this.fadeInMillis=fadeInMillis;
9038
+ this.fadeOutMillis=fadeOutMillis;
9039
+
9040
+ // If it is the first time of the loop then there is no delay at the start:
9041
+ // or if we have no pause between loopings :
9042
+ this.started=true;
9043
+ if(!this.hasLoopStarted || !this.pauseMillis){
9044
+ this.startChainedLoop();
9045
+ }else{
9046
+ this.mainLoopSound=getTimer(this.startChainedLoop,this.pauseMillis);
9047
+ }
9048
+
9049
+ return this;
9050
+ }
9051
+
9052
+ /*private*/startChainedLoop(){
9053
+ if(!this.started) return;
9054
+
9055
+ const self=this;
9056
+ if(this.fadeInMillis && this.fadeOutMillis){
9057
+ if(this.fadeInMillis){
9058
+ this.currentAudio.fadeIn(this.fadeInMillis);
9059
+ }
9060
+ if(this.fadeOutMillis){
9061
+ this.currentAudio.fadeOut(this.fadeOutMillis,()=>{
9062
+ // The next one to loop :
9063
+ if(self.started) self.loop(self.pauseMillis,self.fadeInMillis,self.fadeOutMillis);
9064
+ if(self.doOnEnded) self.doOnEnded(self);
9065
+ });
9066
+ }
9067
+
9068
+ }else{
9069
+ if(!this.fadeInMillis){
9070
+ this.currentAudio.jumpIn();
9071
+ }
9072
+ if(!this.fadeOutMillis){
9073
+ this.currentAudio.jumpOut(()=>{
9074
+ // The next one to loop : (the same, loop on itself)
9075
+ if(self.started) self.loop(self.pauseMillis,self.fadeInMillis,self.fadeOutMillis);
9076
+ if(self.doOnEnded) self.doOnEnded(self);
9008
9077
  }
9078
+ );
9079
+ }
9080
+ }
9009
9081
 
9010
- self.hasLoopStarted=true;
9011
- };
9012
-
9013
- // If it is the first time of the loop then there is no delay at the start:
9014
- // or if we have no pause between loopings :
9015
- self.started=true;
9016
- if(!self.hasLoopStarted || !pauseMillis){
9017
- startChainedLoop();
9018
- }else{
9019
- self.mainLoopSound=getTimer(startChainedLoop,pauseMillis);
9020
- }
9021
-
9022
- return self;
9023
- },
9024
- stopPlaying:function(){
9025
- // To prevent starting treatments after the loading has occurred after the loop has been stopped !
9026
- // (rare case when the loading is so long that the user has aborted the loop before all loop files has been loaded !)
9027
- if(!self.audios) self.hasAbortedLooping=true;
9028
- // (It is necessarily a loop !)
9029
- self.started=false;
9030
- if(self.mainLoopSound) self.mainLoopSound.clear();
9031
- if(self.currentAudio) self.currentAudio.stopPlaying();
9032
- return self;
9033
- },
9034
-
9035
-
9036
- };
9082
+ this.hasLoopStarted=true;
9083
+ }
9084
+
9085
+
9086
+ stopPlaying(){
9087
+ // To prevent starting treatments after the loading has occurred after the loop has been stopped !
9088
+ // (rare case when the loading is so long that the user has aborted the loop before all loop files has been loaded !)
9089
+ if(!this.audios) this.hasAbortedLooping=true;
9090
+ // (It is necessarily a loop !)
9091
+ this.started=false;
9092
+ if(this.mainLoopSound) this.mainLoopSound.clear();
9093
+ if(this.currentAudio) this.currentAudio.stopPlaying();
9094
+ return this;
9095
+ }
9096
+
9097
+ }
9098
+
9099
+ function loadSoundsLoop(filesPaths,doOnLoaded=null,doOnEnded=null){
9037
9100
 
9101
+ const selfSoundsLoop=new SoundsLoop(doOnEnded);
9038
9102
 
9039
9103
  let audios=[];
9040
9104
  let numberLoaded=0;
9041
9105
  foreach(filesPaths,(filePath)=>{
9042
- audios.push( loadSound(filePath,function(){
9106
+ audios.push( loadSound(filePath,function(selfParam/*UNUSED*/){
9043
9107
 
9044
9108
  numberLoaded++
9045
9109
  if(doOnLoaded && numberLoaded==filesPaths.length){
9046
9110
 
9047
9111
  // Once all have been loaded :
9048
- self.audios=audios;
9112
+ selfSoundsLoop.audios=audios;
9049
9113
 
9050
9114
  // To prevent starting treatments after the loading has occurred after the loop has been stopped !
9051
9115
  // (rare case when the loading is so long that the user has aborted the loop before all loop files has been loaded !)
9052
- if(!self.hasAbortedLooping) doOnLoaded(self);
9116
+ if(!selfSoundsLoop.hasAbortedLooping) doOnLoaded(selfSoundsLoop);
9053
9117
 
9054
9118
  }
9055
9119
 
9056
9120
  }) );
9057
9121
  });
9058
9122
 
9059
-
9060
- return self;
9123
+ return selfSoundsLoop;
9061
9124
  }
9062
9125
 
9063
9126
 
9064
9127
 
9128
+ const FADE_REFRESH_MILLIS=500;
9065
9129
 
9066
- function loadSound(filePath,doOnLoaded=null,doOnEnded=null){
9130
+ class Sound{
9131
+ constructor(filePath,doOnEnded=null){
9132
+
9133
+ this.doOnEnded=doOnEnded;
9067
9134
 
9068
- const FADE_REFRESH_MILLIS=500;
9135
+ this.nativeAudioElement=new Audio(filePath);
9136
+ this.isLoop=false;
9137
+ this.hasLoopStarted=false;
9138
+ this.isReady=false;
9139
+ this.speed=1;
9140
+ this.volume=1;
9141
+ this.isPlaying=false;
9142
+ this.mainLoopSound=null;
9143
+
9144
+ }
9069
9145
 
9070
9146
 
9071
- let self={
9072
- nativeAudioElement:new Audio(filePath),
9073
- isLoop:false,
9074
- hasLoopStarted:false,
9075
- isReady:false,
9076
- speed:1,
9077
- volume:1,
9078
- isPlaying:false,
9079
- mainLoopSound:null,
9080
- setVolume:function(newVolume=null){
9081
- if(newVolume) self.volume=newVolume;
9082
- self.nativeAudioElement.volume=self.volume;
9083
- return self;
9084
- },
9085
- setSpeed:function(newSpeed=null){
9086
- if(newSpeed) self.speed=newSpeed;
9087
- self.nativeAudioElement.playbackRate=self.speed;
9088
- return self;
9089
- },
9090
- play:function(){
9091
- if(!self.nativeAudioElement.paused) return self;
9092
- self.nativeAudioElement.play().then(()=>{
9093
- self.isPlaying=true;
9094
- });
9095
- return self;
9096
- },
9097
- pause:function(){
9098
- if(self.nativeAudioElement.paused || !self.isPlaying) return self;
9099
- self.nativeAudioElement.pause();
9100
- self.isPlaying=false;
9101
- return self;
9102
- },
9103
- resumeLoop:function(){
9104
- if(!self.isLoop || !self.mainLoopSound) return self;
9105
- self.mainLoopSound.resume();
9106
- self.play();
9107
- return self;
9108
- },
9109
- pauseLoop:function(){
9110
- if(!self.isLoop || !self.mainLoopSound || !self.isPlaying) return self;
9111
- self.mainLoopSound.pause();
9112
- self.pause();
9113
- return self;
9114
- },
9147
+ setVolume(newVolume=null){
9148
+ if(newVolume) this.volume=newVolume;
9149
+ this.nativeAudioElement.volume=this.volume;
9150
+ return this;
9151
+ }
9152
+ setSpeed(newSpeed=null){
9153
+ if(newSpeed) this.speed=newSpeed;
9154
+ this.nativeAudioElement.playbackRate=this.speed;
9155
+ return this;
9156
+ }
9157
+ play(){
9158
+ if(!this.nativeAudioElement.paused) return this;
9159
+ this.nativeAudioElement.play().then(()=>{
9160
+ this.isPlaying=true;
9161
+ });
9162
+ return this;
9163
+ }
9164
+ pause(){
9165
+ if(this.nativeAudioElement.paused || !this.isPlaying) return this;
9166
+ this.nativeAudioElement.pause();
9167
+ this.isPlaying=false;
9168
+ return this;
9169
+ }
9170
+ resumeLoop(){
9171
+ if(!this.isLoop || !this.mainLoopSound) return this;
9172
+ this.mainLoopSound.resume();
9173
+ this.play();
9174
+ return this;
9175
+ }
9176
+ pauseLoop(){
9177
+ if(!this.isLoop || !this.mainLoopSound || !this.isPlaying) return this;
9178
+ this.mainLoopSound.pause();
9179
+ this.pause();
9180
+ return this;
9181
+ }
9182
+
9183
+ startPlaying(){
9184
+ if(this.isLoop){
9185
+ this.loop();
9186
+ }else{
9187
+ this.play();
9188
+ }
9189
+ }
9190
+
9191
+ setIsLoop(isLoop){
9192
+ this.isLoop=isLoop;
9193
+ return this;
9194
+ }
9195
+
9196
+ stopPlaying(){
9197
+ // NO : We have to stop the audio file even if it's looping :
9198
+ // TODO : FIXME : FIND A BETTER WAY !!
9199
+ this.pause();
9200
+ this.nativeAudioElement.currentTime=0;
9201
+ if(this.isLoop && this.mainLoopSound){
9202
+ this.hasLoopStarted=false;
9203
+ this.mainLoopSound.clear();
9204
+ }
9205
+ return this;
9206
+ }
9207
+
9208
+ loop(pauseMillis=null,fadeInMillis=null,fadeOutMillis=null){
9209
+
9210
+ if(!this.isLoop) return this;
9115
9211
 
9116
- startPlaying:function(){
9117
- if(self.isLoop){
9118
- self.loop();
9119
- }else{
9120
- self.play();
9121
- }
9122
- },
9212
+ // At this point, this.isLoop is necessarily true
9213
+
9214
+ // Some of the more commonly used properties of the audio element include :
9215
+ // src, currentTime, duration, paused, muted, and volume
9216
+ this.pauseMillis=pauseMillis;
9217
+ this.fadeInMillis=fadeInMillis;
9218
+ this.fadeOutMillis=fadeOutMillis;
9123
9219
 
9124
- setIsLoop:function(isLoop){
9125
- self.isLoop=isLoop;
9126
- return self;
9127
- },
9220
+ // If it is the first time of the loop then there is no delay at the start:
9221
+ if(!this.hasLoopStarted || !this.pauseMillis){
9222
+ this.startChainedLoop();
9223
+ }else{
9224
+ this.mainLoopSound=getTimer(this.startChainedLoop, this.pauseMillis);
9225
+ }
9128
9226
 
9129
- stopPlaying:function(){
9130
- // NO : We have to stop the audio file even if it's looping :
9131
- // TODO : FIXME : FIND A BETTER WAY !!
9132
- self.pause();
9133
- self.nativeAudioElement.currentTime=0;
9134
- if(self.isLoop && self.mainLoopSound){
9135
- self.hasLoopStarted=false;
9136
- self.mainLoopSound.clear();
9137
- }
9138
- return self;
9139
- },
9227
+ return this;
9228
+ }
9229
+
9230
+ /*private*/startChainedLoop(){
9140
9231
 
9141
- loop:function(pauseMillis=null,fadeInMillis=null,fadeOutMillis=null){
9142
-
9143
- if(!self.isLoop) return self;
9144
-
9145
- // At this point, self.isLoop is necessarily true
9232
+ const self=this;
9233
+ if(this.fadeInMillis && this.fadeOutMillis){
9234
+ if(this.fadeInMillis){
9235
+ this.fadeIn(this.fadeInMillis);
9236
+ }
9237
+ if(this.fadeOutMillis){
9238
+ this.fadeOut(this.fadeOutMillis,()=>{
9239
+ // The next one to loop : (the same, loop on itself)
9240
+ self.loop(self.pauseMillis,self.fadeInMillis,self.fadeOutMillis);
9241
+ if(self.doOnEnded) self.doOnEnded(self);
9242
+ });
9243
+ }
9244
+ }else{
9245
+ if(!this.fadeInMillis){
9246
+ this.jumpIn();
9247
+ }
9248
+ if(!this.fadeOutMillis){
9249
+ this.jumpOut(()=>{
9250
+ // The next one to loop : (the same, loop on itself)
9251
+ self.loop(self.pauseMillis,self.fadeInMillis,self.fadeOutMillis);
9252
+ if(self.doOnEnded) self.doOnEnded(self);
9253
+ }
9254
+ );
9255
+ }
9256
+ }
9146
9257
 
9147
- // Some of the more commonly used properties of the audio element include :
9148
- // src, currentTime, duration, paused, muted, and volume
9258
+ this.hasLoopStarted=true;
9259
+ }
9260
+
9261
+ jumpIn(){
9262
+ this.nativeAudioElement.currentTime=0;
9263
+ this.play();
9264
+ }
9265
+ fadeIn(durationMillis){
9149
9266
 
9150
- let startChainedLoop=function(){
9151
-
9152
- if(fadeInMillis && fadeOutMillis){
9153
- if(fadeInMillis){
9154
- self.fadeIn(fadeInMillis);
9155
- }
9156
- if(fadeOutMillis){
9157
- self.fadeOut(fadeOutMillis,function(){
9158
-
9159
- // The next one to loop : (the same, loop on itself)
9160
- self.loop(pauseMillis,fadeInMillis,fadeOutMillis);
9161
- if(doOnEnded) doOnEnded(self);
9267
+ this.nativeAudioElement.currentTime=0;
9268
+ // We have to bypass the setVolume method here !
9269
+ this.nativeAudioElement.volume=0;
9270
+
9271
+ let timeRatio=FADE_REFRESH_MILLIS/durationMillis;
9272
+ let volumeStep=timeRatio;
9273
+
9274
+ const self=this;
9275
+ this.fadeRoutine=setInterval(()=>{
9276
+ // We have to bypass the setVolume method here !
9277
+ self.nativeAudioElement.volume=Math.min(self.volume,
9278
+ // and here too :
9279
+ self.nativeAudioElement.volume+volumeStep);
9280
+ },FADE_REFRESH_MILLIS);
9281
+
9282
+ setTimeout(()=>{ clearInterval(self.fadeRoutine); },durationMillis);
9283
+
9284
+ this.play();
9285
+
9286
+ return this;
9287
+ }
9288
+
9289
+ jumpOut(doOnEndJump=null){
9162
9290
 
9163
- });
9164
- }
9165
- }else{
9166
- if(!fadeInMillis){
9167
- self.jumpIn();
9168
- }
9169
- if(!fadeOutMillis){
9170
- self.jumpOut(function(){
9171
- // The next one to loop : (the same, loop on itself)
9172
- self.loop(pauseMillis,fadeInMillis,fadeOutMillis);
9173
- if(doOnEnded) doOnEnded(self);
9174
- }
9175
- );
9176
- }
9177
-
9178
- }
9291
+ const self=this;
9292
+ this.mainLoopSound=getTimer(()=>{
9293
+ self.pause();
9294
+ if(doOnEndJump) doOnEndJump(self);
9295
+ },this.nativeAudioElement.duration*1000);
9296
+
9297
+ return this;
9298
+ }
9299
+
9300
+ fadeOut(durationMillis,doOnEndFade=null){
9179
9301
 
9180
- self.hasLoopStarted=true;
9181
- };
9182
-
9183
- // If it is the first time of the loop then there is no delay at the start:
9184
- if(!self.hasLoopStarted || !pauseMillis){
9185
- startChainedLoop();
9186
- }else{
9187
- self.mainLoopSound=getTimer(startChainedLoop, pauseMillis);
9188
- }
9189
-
9302
+ const self=this;
9303
+ setTimeout(()=>{
9190
9304
 
9191
- return self;
9192
- },
9193
- jumpIn:function(){
9194
- self.nativeAudioElement.currentTime=0;
9195
- self.play();
9196
- },
9197
- fadeIn:function(durationMillis){
9198
-
9199
- self.nativeAudioElement.currentTime=0;
9200
9305
  // We have to bypass the setVolume method here !
9201
- self.nativeAudioElement.volume=0;
9306
+ self.nativeAudioElement.volume=self.volume;
9202
9307
 
9203
9308
  let timeRatio=FADE_REFRESH_MILLIS/durationMillis;
9204
9309
  let volumeStep=timeRatio;
9205
9310
 
9206
9311
  self.fadeRoutine=setInterval(()=>{
9207
9312
  // We have to bypass the setVolume method here !
9208
- self.nativeAudioElement.volume=Math.min(self.volume,
9313
+ self.nativeAudioElement.volume=Math.max(0,
9209
9314
  // and here too :
9210
- self.nativeAudioElement.volume+volumeStep);
9315
+ self.nativeAudioElement.volume-volumeStep);
9211
9316
  },FADE_REFRESH_MILLIS);
9212
9317
 
9213
- setTimeout(()=>{
9214
- clearInterval(self.fadeRoutine);
9215
- },durationMillis);
9216
-
9217
- self.play();
9218
-
9219
- return self;
9220
- },
9318
+ },(this.nativeAudioElement.duration*1000-durationMillis));
9221
9319
 
9320
+ this.mainLoopSound=getTimer(()=>{
9321
+ clearInterval(self.fadeRoutine);
9322
+ self.pause();
9323
+ if(doOnEndFade) doOnEndFade(self);
9324
+ },this.nativeAudioElement.duration*1000);
9222
9325
 
9223
- jumpOut:function(doOnEndJump=null){
9224
-
9225
- self.mainLoopSound=getTimer(()=>{
9226
-
9227
- self.pause();
9228
-
9229
- if(doOnEndJump) doOnEndJump(self);
9230
-
9231
- },self.nativeAudioElement.duration*1000);
9232
-
9233
- return self;
9234
- },
9235
-
9236
- fadeOut:function(durationMillis,doOnEndFade=null){
9326
+ return this;
9327
+ }
9328
+
9329
+ isFinished(){
9330
+ return (this.nativeAudioElement.currentTime===this.nativeAudioElement.duration);
9331
+ }
9332
+
9333
+ }
9237
9334
 
9238
- setTimeout(()=>{
9239
-
9240
- // We have to bypass the setVolume method here !
9241
- self.nativeAudioElement.volume=self.volume;
9242
-
9243
- let timeRatio=FADE_REFRESH_MILLIS/durationMillis;
9244
- let volumeStep=timeRatio;
9245
-
9246
- self.fadeRoutine=setInterval(()=>{
9247
- // We have to bypass the setVolume method here !
9248
- self.nativeAudioElement.volume=Math.max(0,
9249
- // and here too :
9250
- self.nativeAudioElement.volume-volumeStep);
9251
- },FADE_REFRESH_MILLIS);
9252
-
9253
- },(self.nativeAudioElement.duration*1000-durationMillis));
9254
-
9255
- self.mainLoopSound=getTimer(()=>{
9256
- clearInterval(self.fadeRoutine);
9257
-
9258
- self.pause();
9259
-
9260
- if(doOnEndFade) doOnEndFade(self);
9261
-
9262
- },self.nativeAudioElement.duration*1000);
9263
-
9264
-
9265
- return self;
9266
- },
9267
-
9268
- isFinished:function(){
9269
- return (self.nativeAudioElement.currentTime===self.nativeAudioElement.duration);
9270
- }
9271
- };
9335
+
9336
+ function loadSound(filePath,doOnLoaded=null,doOnEnded=null){
9272
9337
 
9273
- self.nativeAudioElement.addEventListener("loadeddata", () => {
9274
- // Some of the more commonly used properties of the audio element include :
9275
- // src, currentTime, duration, paused, muted, and volume
9276
- self.isReady=true;
9277
- if(doOnLoaded) doOnLoaded(self);
9278
- });
9338
+ let self=new Sound(filePath,doOnEnded);
9339
+
9340
+ self.nativeAudioElement.addEventListener("loadeddata", () => {
9341
+ // Some of the more commonly used properties of the audio element include :
9342
+ // src, currentTime, duration, paused, muted, and volume
9343
+ self.isReady=true;
9344
+ if(doOnLoaded) doOnLoaded(self);
9345
+ });
9279
9346
 
9280
9347
  if(!self.isLoop && doOnEnded){
9281
9348
  self.nativeAudioElement.addEventListener("onended", () => {
@@ -9285,7 +9352,6 @@ function loadSound(filePath,doOnLoaded=null,doOnEnded=null){
9285
9352
  });
9286
9353
  }
9287
9354
 
9288
-
9289
9355
  return self;
9290
9356
  }
9291
9357
 
@@ -9405,427 +9471,388 @@ function drawImageAndCenterWithZooms(ctx,image,xParam=0,yParam=0,zooms={zx:1,zy:
9405
9471
 
9406
9472
 
9407
9473
 
9474
+ // ====================================================== SPRITES ======================================================
9408
9475
 
9409
9476
 
9410
-
9411
-
9412
- function getSpriteMonoThreaded(imagesPool,/*OPTIONAL*/refreshMillis=null,
9477
+ class SpriteMonoThreaded{
9478
+
9479
+ constructor(imagesPool,/*OPTIONAL*/refreshMillis=null,
9413
9480
  clipSize={w:100,h:100},scaleFactorW=1,scaleFactorH=1,
9414
9481
  orientation="horizontal",xOffset=0,yOffset=0,isLoop=true,isRandom=false,
9415
9482
  center={x:"left",y:"top"},
9416
9483
  zoomsParam={zx:1,zy:1}){
9417
-
9418
-
9419
- // Two-states : started, stopped.
9420
-
9421
- const isClip=!!clipSize;
9422
-
9423
-
9424
- let self= {
9425
-
9426
- center:center,
9427
- imagesPool:imagesPool,
9428
- scaleFactorW:scaleFactorW,
9429
- scaleFactorH:scaleFactorH,
9430
- xOffset:xOffset,
9431
- yOffset:yOffset,
9432
- zooms:nonull(zoomsParam,{zx:1,zy:1}),
9484
+
9485
+ this.center=center;
9486
+ this.imagesPool=imagesPool;
9487
+ this.scaleFactorW=scaleFactorW;
9488
+ this.scaleFactorH=scaleFactorH;
9489
+ this.xOffset=xOffset;
9490
+ this.yOffset=yOffset;
9491
+ this.isLoop=isLoop;
9492
+ this.zooms=nonull(zoomsParam,{zx:1,zy:1});
9433
9493
 
9434
9494
  // Clip only attributes:
9435
- clipSize:(isClip?clipSize:null),
9495
+ this.isClip=(!!clipSize);
9496
+ this.clipSize=(this.isClip?clipSize:null);
9436
9497
  // width:imagesPool.width,
9437
9498
  // height:imagesPool.height,
9438
- index:0,
9439
- framesNumber:( isClip?(orientation==="horizontal" ? Math.trunc(imagesPool.width/clipSize.w) : Math.trunc(imagesPool.height/clipSize.h)) : null ),
9440
- isRandom:isRandom,
9499
+ this.index=0;
9500
+ this.orientation=orientation;
9501
+ this.framesNumber=( this.isClip?(this.orientation==="horizontal" ? Math.trunc(imagesPool.width/clipSize.w) : Math.trunc(imagesPool.height/clipSize.h)) : null );
9502
+ this.isRandom=isRandom;
9441
9503
 
9442
9504
  // Time measurement :
9443
- time:getNow(),
9444
- refreshMillis:refreshMillis,
9445
- durationTimeFactorHolder:{durationTimeFactor:1},
9446
- setDurationTimeFactorHolder:function(durationTimeFactorHolder){
9447
- self.durationTimeFactorHolder=durationTimeFactorHolder;
9448
- return self;
9449
- },
9505
+ this.time=getNow();
9506
+ this.refreshMillis=refreshMillis;
9507
+ this.durationTimeFactorHolder={durationTimeFactor:1};
9450
9508
 
9451
- // Draw :
9452
- drawOnEachStepZoomedIfNeeded:function(ctx,xParam,yParam,angle=null,opacity=1){
9453
-
9454
- let drawingWidth, drawingHeight;
9455
- let img=null;
9456
-
9457
- if(isClip){
9458
-
9459
- // We only draw a frame : (to avoid blinking)
9509
+ // Two-states : started, stopped.
9510
+
9511
+ }
9460
9512
 
9461
- drawingWidth=Math.trunc(self.clipSize.w*self.scaleFactorW);
9462
- drawingHeight=Math.trunc(self.clipSize.h*self.scaleFactorH);
9513
+ setDurationTimeFactorHolder(durationTimeFactorHolder){
9514
+ this.durationTimeFactorHolder=durationTimeFactorHolder;
9515
+ return this;
9516
+ }
9463
9517
 
9464
- var clipX=0;
9465
- var clipY=0;
9466
- if(orientation==="horizontal"){
9467
- clipX=self.index*self.clipSize.w;
9468
- clipY=self.yOffset;
9469
- }else{
9470
- clipX=self.xOffset;
9471
- clipY=self.index*self.clipSize.h;
9472
- }
9473
-
9474
- }else{
9475
-
9476
- img=Math.getRandomInArray(self.imagesPool);
9477
-
9478
- self.clipSize={w:img.width,h:img.height};
9479
-
9480
-
9481
- drawingWidth=Math.trunc(img.width*self.scaleFactorW);
9482
- drawingHeight=Math.trunc(img.height*self.scaleFactorH);
9483
-
9484
-
9518
+ // Draw :
9519
+ drawOnEachStepZoomedIfNeeded(ctx,xParam,yParam,angle=null,opacity=1){
9520
+
9521
+ let drawingWidth, drawingHeight;
9522
+ let img=null;
9523
+ let clipX=0;
9524
+ let clipY=0;
9525
+ if(this.isClip){
9526
+ // We only draw a frame : (to avoid blinking)
9527
+ drawingWidth=Math.trunc(this.clipSize.w*this.scaleFactorW);
9528
+ drawingHeight=Math.trunc(this.clipSize.h*this.scaleFactorH);
9485
9529
 
9530
+ if(this.orientation==="horizontal"){
9531
+ clipX=this.index*this.clipSize.w;
9532
+ clipY=this.yOffset;
9533
+ }else{
9534
+ clipX=this.xOffset;
9535
+ clipY=this.index*this.clipSize.h;
9486
9536
  }
9487
9537
 
9488
-
9489
- const xImage=xParam+self.xOffset;
9490
- const yImage=yParam+self.yOffset;
9491
-
9492
- const zooms=self.zooms;
9493
-
9494
- drawImageAndCenterWithZooms(ctx, nonull(img,self.imagesPool),
9495
- xImage, yImage,
9496
- zooms,
9497
- self.center,
9498
- (self.scaleFactorW),
9499
- (self.scaleFactorH),
9500
- (!isClip?(drawingWidth):null),
9501
- (!isClip?(drawingHeight):null),
9502
- opacity,
9503
- false,
9504
- (isClip?{x:clipX,y:clipY, w:self.clipSize.w, h:self.clipSize.h}:{x:0,y:0, w:self.clipSize.w, h:self.clipSize.h}),
9505
- angle);
9506
-
9507
-
9538
+ }else{
9539
+ img=Math.getRandomInArray(this.imagesPool);
9540
+ this.clipSize={w:img.width,h:img.height};
9508
9541
 
9542
+ drawingWidth=Math.trunc(img.width*this.scaleFactorW);
9543
+ drawingHeight=Math.trunc(img.height*this.scaleFactorH);
9544
+ }
9509
9545
 
9510
- // Looping index with a delay :
9511
- var delayHasPassed= (!self.time || hasDelayPassed(self.time, self.refreshMillis*self.durationTimeFactorHolder.getDurationTimeFactor()));
9512
- if(!self.refreshMillis || delayHasPassed){
9513
- if(self.refreshMillis && delayHasPassed){
9514
- self.time=getNow();
9515
- }
9516
-
9517
-
9518
- if(isClip){
9519
- // We perform the step forward (if the loop is not finished):
9520
- if(isLoop || index!==self.framesNumber){
9521
- if(self.isRandom){
9522
- self.index=Math.getRandomInt(self.framesNumber-1, 0);
9523
- }else{
9524
- self.index=((self.index+1)%(self.framesNumber/*if max is reached, then index is looped to 0*/));
9525
- }
9546
+ const xImage=xParam+this.xOffset;
9547
+ const yImage=yParam+this.yOffset;
9548
+ const zooms=this.zooms;
9549
+
9550
+ drawImageAndCenterWithZooms(ctx, nonull(img,this.imagesPool),
9551
+ xImage, yImage,
9552
+ zooms,
9553
+ this.center,
9554
+ (this.scaleFactorW),
9555
+ (this.scaleFactorH),
9556
+ (!this.isClip?(drawingWidth):null),
9557
+ (!this.isClip?(drawingHeight):null),
9558
+ opacity,
9559
+ false,
9560
+ (this.isClip?{x:clipX,y:clipY, w:this.clipSize.w, h:this.clipSize.h}:{x:0,y:0, w:this.clipSize.w, h:this.clipSize.h}),
9561
+ angle);
9562
+
9563
+ // Looping index with a delay :
9564
+ if(!this.refreshMillis || this.hasDelayPassed()){
9565
+ if(this.refreshMillis && this.hasDelayPassed()){
9566
+ this.time=getNow();
9567
+ }
9568
+
9569
+ if(this.isClip){
9570
+ // We perform the step forward (if the loop is not finished):
9571
+ if(this.isLoop || index!==this.framesNumber){
9572
+ if(this.isRandom){
9573
+ this.index=Math.getRandomInt(this.framesNumber-1, 0);
9574
+ }else{
9575
+ this.index=((this.index+1)%(this.framesNumber/*if max is reached, then index is looped to 0*/));
9526
9576
  }
9527
9577
  }
9528
-
9529
9578
  }
9530
- },
9531
-
9532
- };
9579
+
9580
+ }
9581
+ }
9533
9582
 
9534
- return self;
9583
+ hasDelayPassed(){
9584
+ return (!this.time || hasDelayPassed(this.time, this.refreshMillis*this.durationTimeFactorHolder.getDurationTimeFactor()));
9585
+ }
9535
9586
 
9536
9587
  }
9537
9588
 
9538
9589
 
9590
+ function getSpriteMonoThreaded(imagesPool,/*OPTIONAL*/refreshMillis=null,
9591
+ clipSize={w:100,h:100},scaleFactorW=1,scaleFactorH=1,
9592
+ orientation="horizontal",xOffset=0,yOffset=0,isLoop=true,isRandom=false,
9593
+ center={x:"left",y:"top"},
9594
+ zoomsParam={zx:1,zy:1}){
9539
9595
 
9540
-
9541
-
9542
- //Routines
9543
- function getMonoThreadedTimeout(doOnStop=null, delayMillis=null){
9544
-
9545
- let self={
9546
- started:true,
9547
- time:getNow(),
9548
- delayMillis:delayMillis,
9549
- doOnStop:doOnStop,
9550
- isStarted:function(){
9551
- return self.started;
9552
- },
9553
- start:function(callingObject=null, args=null, doOnStop=null){
9554
- if(callingObject) self.callingObject=callingObject;
9555
- if(doOnStop) self.doOnStop=doOnStop;
9556
- if(hasDelayPassed(self.time, self.delayMillis)){
9557
- if(self.doOnStop) self.doOnStop.apply(callingObject, args);
9558
- return;
9559
- }
9560
- self.started=true;
9561
- return self;
9562
- },
9563
- doStep:function(args=null){
9564
- if(!self.started)
9565
- return;
9566
- if(!hasDelayPassed(self.time, self.delayMillis))
9567
- return;
9568
- if(self.doOnStop) self.doOnStop.apply(self.callingObject, args);
9569
- self.started=false;
9570
- return self;
9571
- },
9572
- stop:function(args=null){
9573
- if(!self.isStarted()) return;
9574
- self.started=false;
9575
- if(self.doOnStop) self.doOnStop.apply(self.callingObject, args);
9576
- return self;
9577
- },
9578
- reset:function(){
9579
- this.time=getNow();
9580
- return self;
9581
- },
9582
- };
9583
- return self;
9596
+ return new SpriteMonoThreaded(imagesPool,refreshMillis,
9597
+ clipSize,scaleFactorW,scaleFactorH,
9598
+ orientation,xOffset,yOffset,isLoop,isRandom,
9599
+ center,
9600
+ zoomsParam);
9584
9601
  }
9585
9602
 
9603
+ // ====================================================== ROUTINES ======================================================
9586
9604
 
9587
9605
 
9606
+ // ======================== Timeout ========================
9588
9607
 
9608
+ // CAUTION : This is not a routine, but a single-use timeout !
9609
+ class MonoThreadedTimeout{
9610
+ constructor(actuator, delayMillis){
9611
+ this.actuator=actuator;
9612
+ }
9613
+ start(delayMillis=null){
9614
+ this.started=true;
9615
+ this.time=getNow();
9616
+ this.delayMillis=delayMillis;
9617
+ }
9618
+ doStep(args=null){
9619
+ if(!this.started) return;
9620
+ if(!hasDelayPassed(this.time, this.delayMillis)) return;
9621
+ return this.stop(args);
9622
+ }
9623
+ stop(args=null){
9624
+ if(!this.started) return;
9625
+ this.started=false;
9626
+ if(this.actuator.doOnStop) this.actuator.doOnStop(args);
9627
+ return this;
9628
+ }
9629
+ }
9589
9630
 
9631
+ function getMonoThreadedTimeout(actuator=null){
9632
+ return new MonoThreadedTimeout(actuator);
9633
+ }
9590
9634
 
9591
- function getMonoThreadedRoutine(doOnEachStepCallback,terminateFunction=null,doOnStop=null,refreshMillis=null,startDependsOnParentOnly=false,doOnSuspend=null,doOnResume=null){
9592
-
9593
- // Three-states : started, paused, stopped.
9594
-
9595
- let self={
9596
-
9597
- startDependsOnParentOnly:startDependsOnParentOnly,
9598
- started:false,
9599
- paused:false,
9600
- time:getNow(),
9601
- refreshMillis:refreshMillis,
9602
- doOnEachStepCallback:doOnEachStepCallback,
9603
- terminateFunction:terminateFunction,
9604
- doOnStop:doOnStop,
9605
- doOnSuspend:doOnSuspend,
9606
- doOnResume:doOnResume,
9607
- durationTimeFactorHolder:{durationTimeFactor:1},
9608
- setDurationTimeFactorHolder:function(durationTimeFactorHolder){
9609
- self.durationTimeFactorHolder=durationTimeFactorHolder;
9610
- return self;
9611
- },
9612
- isStarted:function(){
9613
- return self.started || self.startDependsOnParentOnly;
9614
- },
9615
- start:function(callingObject=null,args=null){
9616
-
9617
- // if(self.isStarted()) return;
9618
- if(self.terminateFunction && self.terminateFunction(callingObject,args)){
9619
-
9620
- // CAUTION : Even if the routine is «pre-terminated» before even starting
9621
- // (ie. its stop conditions are fullfilled even before it has started)
9622
- // we all the same have to trigger its stop treatments :
9623
- if(self.doOnStop) self.doOnStop(callingObject,args);
9624
-
9625
- return;
9626
- }
9627
-
9628
- self.started=true;
9629
-
9630
- self.paused=false;
9631
-
9632
- return self;
9633
- },
9634
- stop:function(callingObject=null,args=null){
9635
-
9636
- if(!self.isStarted()) return;
9637
- self.started=false;
9638
9635
 
9639
- if(self.doOnStop) self.doOnStop(callingObject,args);
9640
-
9641
- },
9642
- pause:function(callingObject=null,args=null){
9643
- self.paused=true;
9644
-
9645
- if(self.doOnSuspend) self.doOnSuspend(callingObject,args);
9646
- },
9647
- resume:function(callingObject=null,args=null){
9648
- self.paused=false;
9649
-
9650
- if(self.doOnResume) self.doOnResume(callingObject,args);
9651
- },
9652
- isPaused:function(){
9653
- return self.paused;
9654
- },
9655
- doStep:function(callingObject=null,args=null){
9656
-
9636
+ // ======================== Routine ========================
9657
9637
 
9658
- if(!self.isStarted() || self.paused) return;
9638
+ class MonoThreadedRoutine{
9639
+ constructor(actuator,refreshMillis=null,startDependsOnParentOnly=false){
9659
9640
 
9660
- // Looping index with a delay :
9661
-
9662
- let delayHasPassed=hasDelayPassed(self.time, self.refreshMillis*self.durationTimeFactorHolder.getDurationTimeFactor());
9663
- if(!self.refreshMillis || delayHasPassed){
9664
- if(self.refreshMillis && delayHasPassed){
9665
- self.time=getNow();
9666
- }
9667
-
9668
- // We perform the step :
9669
- if(self.terminateFunction && self.terminateFunction(callingObject,args)){
9670
- self.stop(callingObject,args);
9671
- return;
9672
- }
9673
-
9641
+ this.actuator=actuator;
9642
+ this.startDependsOnParentOnly=startDependsOnParentOnly;
9643
+ this.started=false;
9644
+ this.paused=false;
9645
+ this.time=getNow();
9646
+ this.refreshMillis=refreshMillis;
9647
+
9648
+ this.durationTimeFactorHolder={durationTimeFactor:1};
9649
+ // Three-states : started, paused, stopped.
9674
9650
 
9675
- if(self.doOnEachStepCallback) self.doOnEachStepCallback(callingObject,args);
9651
+ }
9652
+
9653
+ setDurationTimeFactorHolder(durationTimeFactorHolder){
9654
+ this.durationTimeFactorHolder=durationTimeFactorHolder;
9655
+ return this;
9656
+ }
9657
+ isStarted(){
9658
+ return this.started || this.startDependsOnParentOnly;
9659
+ }
9660
+ start(args=null){
9661
+ // if(this.isStarted()) return;
9662
+ if(this.actuator.terminateFunction && this.actuator.terminateFunction(args)){
9663
+ // CAUTION : Even if the routine is «pre-terminated» before even starting
9664
+ // (ie. its stop conditions are fullfilled even before it has started)
9665
+ // we all the same have to trigger its stop treatments :
9666
+ if(this.actuator.doOnStop) this.actuator.doOnStop(args);
9667
+ return;
9668
+ }
9669
+ this.started=true;
9670
+ this.paused=false;
9671
+ return this;
9672
+ }
9673
+ stop(args=null){
9674
+ if(!this.isStarted()) return;
9675
+ this.started=false;
9676
+ if(this.actuator.doOnStop) this.actuator.doOnStop(args);
9677
+ }
9678
+ pause(args=null){
9679
+ this.paused=true;
9680
+ if(this.actuator.doOnSuspend) this.actuator.doOnSuspend(args);
9681
+ }
9682
+ resume(args=null){
9683
+ this.paused=false;
9684
+ if(this.actuator.doOnResume) this.actuator.doOnResume(args);
9685
+ }
9686
+ isPaused(){
9687
+ return this.paused;
9688
+ }
9689
+ doStep(args=null){
9690
+ if(!this.isStarted() || this.paused) return;
9691
+ // Looping index with a delay :
9692
+
9693
+ const delayHasPassed=hasDelayPassed(this.time, this.refreshMillis*this.durationTimeFactorHolder.getDurationTimeFactor());
9694
+ if(!this.refreshMillis || delayHasPassed){
9695
+ if(this.refreshMillis && delayHasPassed){
9696
+ this.time=getNow();
9676
9697
  }
9677
-
9698
+ // We perform the step :
9699
+ if(this.actuator.terminateFunction && this.actuator.terminateFunction(args)){
9700
+ this.stop(args);
9701
+ return;
9702
+ }
9703
+ if(this.actuator.doOnEachStep) this.actuator.doOnEachStep(args);
9678
9704
  }
9679
- };
9680
-
9681
-
9705
+
9706
+ }
9682
9707
 
9683
- return self;
9684
9708
  }
9685
9709
 
9686
9710
 
9711
+ function getMonoThreadedRoutine(actuator, refreshMillis=null, startDependsOnParentOnly=false){
9712
+ return new MonoThreadedRoutine(actuator, refreshMillis, startDependsOnParentOnly);
9713
+ }
9687
9714
 
9688
9715
 
9689
-
9690
- function getMonoThreadedGoToGoal(actualValue,goalValue,refreshMillis=null,totalTimeMillis=1000,mode="linear",doOnEachStepCallback=null,doOnStop=null){
9691
-
9692
- // const STEP_PRECISION=4;
9693
- // Three-states : started, paused, stopped.
9716
+ class MonoThreadedGoToGoal{
9717
+ constructor(actuator,actualValue,goalValue,refreshMillis=null,totalTimeMillis=1000,mode="linear"){
9718
+
9719
+ this.actuator=actuator;
9720
+
9721
+ this.actualValue=actualValue;
9722
+ this.mode=mode;
9723
+ this.totalTimeMillis=totalTimeMillis;
9724
+ this.stepValue=((refreshMillis*(goalValue-actualValue))/totalTimeMillis);
9725
+ this.value=actualValue;
9726
+ this.goalValue=goalValue;
9727
+ this.started=false;
9728
+ this.paused=false;
9729
+ this.time=getNow();
9730
+ this.refreshMillis=refreshMillis;
9731
+
9732
+ this.durationTimeFactorHolder={durationTimeFactor:1};
9694
9733
 
9695
- let self={
9696
-
9697
- mode:mode,
9698
- totalTimeMillis:totalTimeMillis,
9699
- stepValue:((refreshMillis*(goalValue-actualValue))/totalTimeMillis),
9700
- value:actualValue,
9701
- goalValue:goalValue,
9702
- started:false,
9703
- paused:false,
9704
- time:getNow(),
9705
- refreshMillis:refreshMillis,
9706
- doOnEachStepCallback:doOnEachStepCallback,
9707
- doOnStop:doOnStop,
9708
- durationTimeFactorHolder:{durationTimeFactor:1},
9709
- setDurationTimeFactorHolder:function(durationTimeFactorHolder){
9710
- self.durationTimeFactorHolder=durationTimeFactorHolder;
9711
-
9712
- // We recalculate total time-dependent values :
9713
- self.totalTimeMillis=totalTimeMillis*self.durationTimeFactorHolder.getDurationTimeFactor();
9714
- self.stepValue=((refreshMillis*(goalValue-actualValue))/self.totalTimeMillis);
9734
+ // Three-states : started, paused, stopped.
9735
+ }
9736
+ setDurationTimeFactorHolder(durationTimeFactorHolder){
9737
+ this.durationTimeFactorHolder=durationTimeFactorHolder;
9738
+
9739
+ // We recalculate total time-dependent values :
9740
+ this.totalTimeMillis=this.totalTimeMillis*this.durationTimeFactorHolder.getDurationTimeFactor();
9741
+ this.stepValue=((this.refreshMillis*(this.goalValue-this.actualValue))/this.totalTimeMillis);
9715
9742
 
9716
- return self;
9717
- },
9718
- isStarted:function(){
9719
- return self.started || self.startDependsOnParentOnly;
9720
- },
9721
- resetValueAndGoalTo:function(resetValue){
9722
- self.value=resetValue;
9723
- self.goalValue=resetValue;
9724
- return self;
9725
- },
9726
- setNewGoal:function(goalValue, refreshMillisParam=null, totalTimeMillis=null){
9727
- if(Math.round(self.value)===Math.round(goalValue)) return;
9728
- if(refreshMillisParam) self.refreshMillis=refreshMillisParam;
9729
- if(totalTimeMillis) self.totalTimeMillis=totalTimeMillis*self.durationTimeFactorHolder.getDurationTimeFactor();
9743
+ return this;
9744
+ }
9745
+ isStarted(){
9746
+ return this.started || this.startDependsOnParentOnly;
9747
+ }
9748
+ resetValueAndGoalTo(resetValue){
9749
+ this.value=resetValue;
9750
+ this.goalValue=resetValue;
9751
+ return this;
9752
+ }
9753
+ setNewGoal(goalValue, refreshMillisParam=null, totalTimeMillis=null){
9754
+ if(Math.round(this.value)===Math.round(goalValue)) return;
9755
+ if(refreshMillisParam) this.refreshMillis=refreshMillisParam;
9756
+ if(totalTimeMillis) this.totalTimeMillis=totalTimeMillis*this.durationTimeFactorHolder.getDurationTimeFactor();
9730
9757
 
9731
- self.stepValue=((self.refreshMillis*(goalValue-self.value))/self.totalTimeMillis),
9732
- self.goalValue=goalValue;
9733
-
9734
- if(!self.isStarted())
9735
- self.start();
9736
-
9737
- return self;
9738
- },
9739
- start:function(callingObject=null,args=null){
9758
+ this.goalValue=goalValue;
9759
+ this.stepValue=((this.refreshMillis*(this.goalValue-this.value))/this.totalTimeMillis);
9760
+
9761
+ if(!this.isStarted()) this.start();
9740
9762
 
9741
- if( self.value===self.goalValue
9742
- // || self.isStarted();
9743
- || self.terminateFunction(callingObject,args)){
9763
+ return this;
9764
+ }
9765
+ start(args=null){
9766
+
9767
+ if( this.value===this.goalValue
9768
+ // || this.isStarted();
9769
+ || (this.actuator.terminateFunction && this.actuator.terminateFunction())
9770
+ || this.hasReachedGoal()){
9744
9771
 
9745
- // CAUTION : Even if the routine is «pre-terminated» before even starting
9746
- // (ie. its stop conditions are fullfilled even before it has started)
9747
- // we all the same have to trigger its stop treatments :
9748
- if(self.doOnStop) self.doOnStop(callingObject,args);
9772
+ // CAUTION : Even if the routine is «pre-terminated» before even starting
9773
+ // (ie. its stop conditions are fullfilled even before it has started)
9774
+ // we all the same have to trigger its stop treatments :
9775
+ if(this.actuator.doOnStop) this.actuator.doOnStop(args);
9749
9776
 
9750
- return;
9751
- }
9752
-
9753
- self.started=true;
9754
-
9755
- self.paused=false;
9756
-
9757
- return self;
9758
- },
9759
- stop:function(callingObject=null,args=null){
9760
-
9761
- if(!self.isStarted()) return;
9762
- self.started=false;
9777
+ return;
9778
+ }
9779
+ this.started=true;
9780
+ this.paused=false;
9781
+
9782
+ return this;
9783
+ }
9784
+ stop(args=null){
9785
+ if(!this.isStarted()) return;
9786
+ this.started=false;
9787
+ if(this.actuator.doOnStop) this.actuator.doOnStop(args);
9788
+ }
9789
+ pause(){
9790
+ this.paused=true;
9791
+ }
9792
+ resume(){
9793
+ this.paused=false;
9794
+ }
9795
+ hasReachedGoal(){
9796
+ if(this.stepValue<0){
9797
+ if(this.value<=this.goalValue) return true;
9798
+ }else{
9799
+ if(this.goalValue<=this.value) return true;
9800
+ }
9801
+ return false;
9802
+ }
9803
+
9804
+ doStep(args=null){
9805
+ if(!this.isStarted() || this.paused) return this.value;
9763
9806
 
9764
- if(self.doOnStop) self.doOnStop(callingObject,args);
9765
-
9766
- },
9767
- pause:function(){
9768
- self.paused=true;
9769
- },
9770
- resume:function(){
9771
- self.paused=false;
9772
- },
9773
- terminateFunction:function(){
9807
+ // Looping index with a delay :
9808
+
9809
+ if(!this.refreshMillis || this.hasDelayPassed()){
9810
+ if(this.refreshMillis && this.hasDelayPassed()){
9811
+ this.time=getNow();
9812
+ }
9774
9813
 
9775
- if(self.stepValue<0){
9776
- if(self.value<=self.goalValue){
9777
- return true;
9814
+ // We check if we must stop :
9815
+ if(this.actuator.terminateFunction){
9816
+ if(this.actuator.terminateFunction()){
9817
+ this.stop();
9818
+ this.value=this.goalValue;
9819
+ return this.value;
9778
9820
  }
9779
9821
  }else{
9780
- if(self.goalValue<=self.value){
9781
- return true;
9822
+ if(this.hasReachedGoal()){
9823
+ this.stop();
9824
+ this.value=this.goalValue;
9825
+ return this.value;
9782
9826
  }
9783
9827
  }
9784
9828
 
9785
- return false;
9786
-
9787
- },
9788
-
9789
- doStep:function(callingObject=null,args=null){
9790
-
9791
- if(!self.isStarted() || self.paused) return self.value;
9792
-
9793
- // Looping index with a delay :
9794
-
9795
- var delayHasPassed=(!self.time || hasDelayPassed(self.time, self.refreshMillis*self.durationTimeFactorHolder.getDurationTimeFactor()));
9796
- if(!self.refreshMillis || delayHasPassed){
9797
- if(self.refreshMillis && delayHasPassed){
9798
- self.time=getNow();
9799
- }
9800
-
9801
- // We perform the step :
9802
- if(self.terminateFunction(callingObject)){
9803
- self.stop(callingObject);
9804
-
9805
- self.value=self.goalValue;
9806
-
9807
- return self.value;
9808
- }
9809
-
9810
-
9811
- if(self.mode==="exponential"){
9812
- self.value+=Math.pow(self.stepValue,2);
9813
- }else{ // default is "linear"
9814
- self.value+=self.stepValue;
9815
- }
9816
-
9817
- if(self.doOnEachStepCallback) self.doOnEachStepCallback(callingObject,args);
9829
+ // We perform the step :
9830
+ if(this.mode==="exponential"){
9831
+ this.value+=Math.pow(this.stepValue,2);
9832
+ }else{ // default is "linear"
9833
+ this.value+=this.stepValue;
9818
9834
  }
9819
9835
 
9820
- return self.value;
9821
-
9836
+ if(this.actuator.doOnEachStep) this.actuator.doOnEachStep(args);
9822
9837
  }
9823
- };
9838
+
9839
+ return this.value;
9840
+ }
9824
9841
 
9825
- return self;
9842
+ hasDelayPassed(){
9843
+ return (!this.time || hasDelayPassed(this.time, this.refreshMillis*this.durationTimeFactorHolder.getDurationTimeFactor()));
9844
+ }
9845
+
9846
+
9847
+ }
9848
+
9849
+
9850
+ function getMonoThreadedGoToGoal(actuator,actualValue,goalValue,refreshMillis=null,totalTimeMillis=1000,mode="linear"){
9851
+ return new MonoThreadedGoToGoal(actuator,actualValue,goalValue,refreshMillis,totalTimeMillis,mode);
9826
9852
  }
9827
9853
 
9828
9854
 
9855
+ // =====================================================================================================================
9829
9856
 
9830
9857
 
9831
9858
  //if signed :
@@ -12047,10 +12074,7 @@ class VNCFrame2D{
12047
12074
  "sound":(soundDataStr)=>{
12048
12075
  const soundData=getDecodedArrayFromSoundDataString(soundDataStr);
12049
12076
  playAudioData(soundData,self.audioCtx,self.screenAndAudioConfig.audioBufferSize,1,self.screenAndAudioConfig.sampleRate,()=>{
12050
- // doOnEndedPlaying
12051
-
12052
12077
  self.fusrodaClient.client.socketToServer.send("soundSampleRequest", {});
12053
-
12054
12078
  });
12055
12079
  }
12056
12080
  }, this.url, this.port, this.isSecure);
@@ -12332,7 +12356,6 @@ window.createOritaMainClient=function(
12332
12356
  microClientInfos.lastTime=getNow();
12333
12357
  mainClient.inputsGPIO=inputsGPIO;
12334
12358
 
12335
-
12336
12359
  doOnInputsCreated(mainClient, microClientId, mainClient.inputsGPIO);
12337
12360
  },
12338
12361
  });
@@ -12628,6 +12651,10 @@ window.createOritaMainClient=function(
12628
12651
 
12629
12652
  // SEE http://codefoster.com/pi-basicgpio/
12630
12653
 
12654
+ // INSTALL ON TARGET PLATFORM : https://ma2shita.medium.com/how-to-use-raspi-gpio-instead-of-gpio-of-wriring-pi-af2ab00eda57
12655
+
12656
+ // ON 10/2024 GPIO IS UTTERLY BROKEN ON THE "BOOKWORM" RASPBIAN DISTRIBUTION WITH NO SOLUTION !!!
12657
+
12631
12658
  const GPIO_BASE_PATH = "/sys/class/gpio";
12632
12659
  window.gpioUtils = {
12633
12660
  open: (gpioNumber, mode, doOnSuccess = null) => {
@@ -12649,6 +12676,7 @@ window.gpioUtils = {
12649
12676
  if (doOnSuccess) doOnSuccess();
12650
12677
  });
12651
12678
  };
12679
+
12652
12680
  fs.access(gpioFilePath, fs.constants.F_OK, (error) => {
12653
12681
  if (error) {
12654
12682
  // Case export file does not exists
@@ -12772,34 +12800,34 @@ window.gpioUtils = {
12772
12800
 
12773
12801
 
12774
12802
 
12775
- // const REFRESHING_RATE_MILLIS_AUDIO:100,
12803
+ // const REFRESHING_RATE_MILLIS_AUDIO:100,
12776
12804
 
12777
12805
 
12778
- // const MICRO_CLIENT_MESS_WITH_ALPHA=true;
12779
- // const MICRO_CLIENT_MESS_WITH_ALPHA=false;
12806
+ // const MICRO_CLIENT_MESS_WITH_ALPHA=true;
12807
+ // const MICRO_CLIENT_MESS_WITH_ALPHA=false;
12780
12808
 
12781
12809
 
12782
- const REFRESHING_RATE_MILLIS_DEFAULT = 1000;
12810
+ const REFRESHING_RATE_MILLIS_DEFAULT = 1000;
12783
12811
 
12784
12812
 
12785
- // Output constants :
12786
- const STEPPER_DELAY_MILLIS = 1;
12787
- const DEFAULT_STEPPER_SEQUENCE = [
12788
- [1, 0, 0, 0],
12789
- [0, 1, 0, 0],
12790
- [0, 0, 1, 0],
12791
- [0, 0, 0, 1],
12792
- ];
12793
- const HALF_STEP_STEPPER_SEQUENCE = [
12794
- [0, 1, 0, 0],
12795
- [0, 1, 0, 1],
12796
- [0, 0, 0, 1],
12797
- [1, 0, 0, 1],
12798
- [1, 0, 0, 0],
12799
- [1, 0, 1, 0],
12800
- [0, 0, 1, 0],
12801
- [0, 1, 1, 0],
12802
- ];
12813
+ // Output constants :
12814
+ const STEPPER_DELAY_MILLIS = 1;
12815
+ const DEFAULT_STEPPER_SEQUENCE = [
12816
+ [1, 0, 0, 0],
12817
+ [0, 1, 0, 0],
12818
+ [0, 0, 1, 0],
12819
+ [0, 0, 0, 1],
12820
+ ];
12821
+ const HALF_STEP_STEPPER_SEQUENCE = [
12822
+ [0, 1, 0, 0],
12823
+ [0, 1, 0, 1],
12824
+ [0, 0, 0, 1],
12825
+ [1, 0, 0, 1],
12826
+ [1, 0, 0, 0],
12827
+ [1, 0, 1, 0],
12828
+ [0, 0, 1, 0],
12829
+ [0, 1, 1, 0],
12830
+ ];
12803
12831
 
12804
12832
 
12805
12833
 
@@ -12880,7 +12908,6 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
12880
12908
 
12881
12909
  // TODO : FIXME : Utiliser initClient(...) au lieu de directement getStatic(...) avec le paramètre isNode transmis dans l'appel)
12882
12910
  //oritaClient=initClient(isNode,false,doOnServerConnection=null, url, port);
12883
-
12884
12911
  oritaClient.client={};
12885
12912
  oritaClient.client.socketToServer = WebsocketImplementation.getStatic(isNode).connectToServer(url, port);
12886
12913
  oritaClient.client.socketToServer.onConnectionToServer(() => {
@@ -13620,18 +13647,256 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
13620
13647
 
13621
13648
 
13622
13649
 
13650
+ //*********************************** AUTO-ORGANIZING REAL-TIME AORTAC CLUSTERIZATION (AORTAC) *********************************** */
13623
13651
 
13624
13652
 
13625
13653
 
13626
13654
 
13655
+ class AORTACClient{
13656
+
13657
+ constructor(clientId, serverNodeOrigin, model, view, isNodeContext=true, sslConfig={/*OPTIONAL*/certPath:null,/*OPTIONAL*/keyPath:null}){
13658
+ this.clientId=clientId;
13659
+ this.serverNodeOrigin=serverNodeOrigin;
13660
+ this.isNodeContext=isNodeContext;
13661
+ this.sslConfig=sslConfig;
13662
+
13663
+ this.model=model; // must implement functions
13664
+ this.view=view; // Must implement functions
13665
+ this.view.setClient(this);
13666
+
13667
+ this.clientInstanceToReferenceServerNode=null;
13668
+ this.currentServersNodes={};
13669
+
13670
+
13671
+ // TRACE
13672
+ lognow(`AORTAC client created with id ${this.clientId}.`);
13673
+
13674
+ }
13675
+
13676
+ start(){
13677
+
13678
+ const self=this;
13679
+
13680
+ const splits=splitURL(this.serverNodeOrigin);
13681
+ let protocol=nonull(splits.protocol,"ws"), host=nonull(splits.host,"localhost"), port=nonull(splits.port,"30000");
13682
+
13683
+
13684
+ // THIS IS ON THE REFERENCE SERER NODE ONLY :
13685
+ const clientInstanceToReferenceServerNode=initClient(this.isNodeContext, false, (socketToServer)=>{
13686
+
13687
+
13688
+ // First client needs to register to its main reference serer node :
13689
+ // TRACE
13690
+ lognow(` (client ${self.clientId}) Sending client registering request to server node...`);
13691
+
13692
+ const helloClientRequest={
13693
+ clientId:self.clientId,
13694
+ type:"request.register.client",
13695
+ isReferenceNode:true,
13696
+ };
13697
+ socketToServer.send("protocol", helloClientRequest);
13698
+
13699
+ // We place a listener from server on this client instance :
13700
+ socketToServer.receive("protocol",(message)=>{
13701
+
13702
+
13703
+ // Adding listeners :
13704
+ if(message.type==="response.register.client"){
13705
+
13706
+ // TRACE
13707
+ lognow(` (${self.clientId}) Receving registering response from reference server...`);
13708
+
13709
+ // Case reference node:
13710
+
13711
+ // Second, once client is registered at its refernce server, then it needs to know to which servers it needs to connect to :
13712
+ // TRACE
13713
+ lognow("(client "+self.clientId+") Sending client interrogation request to server node...");
13714
+
13715
+ const boundaries=self.view.getBoundaries();
13716
+ const interrogationRequest={
13717
+ clientId:self.clientId,
13718
+ type:"request.interrogation.client",
13719
+ boundaries:boundaries
13720
+ };
13721
+ socketToServer.send("protocol", interrogationRequest);
13722
+
13723
+
13724
+ }
13725
+ });
13726
+
13727
+ // We place a listener from server on this client instance :
13728
+ socketToServer.receive("protocol",(message)=>{
13729
+ if(message.type==="response.unregister.client"){
13730
+
13731
+ // TRACE
13732
+ lognow(` (${self.clientId}) Receving unregistering response from reference server...`);
13733
+
13734
+ // DO NOTHING
13735
+
13736
+ }
13737
+ });
13738
+
13739
+
13740
+ // We place a listener from server on this client instance :
13741
+ socketToServer.receive("protocol",(message)=>{
13742
+ if(message.type==="response.interrogation.client"){
13743
+
13744
+ // TRACE
13745
+ lognow("!!!!!! ("+self.clientId+") Receving interrogation response from requested server...",message);
13746
+
13747
+ // Now the client needs to know to which other nodes it needs to connect in order to get all its model objects :
13748
+ const serversNodesIdsForModelObjectsForClient=message.serversNodesIdsForModelObjectsForClient;
13749
+
13750
+ // Now the client needs to know to which other nodes it needs to connect in order to get all its model objects :
13751
+
13752
+
13753
+ // We connect (only) to the relevant servers directly :
13754
+ foreach(serversNodesIdsForModelObjectsForClient,(nodeInfoAndObjectsIds, serverNodeId)=>{
13755
+
13756
+ const splitsServerInfo=splitURL(nodeInfoAndObjectsIds.nodeServerInfo);
13757
+ const clientInstanceToAuthorityServerNode=initClient(this.isNodeContext, false, (socketToServer)=>{
13758
+
13759
+ // Then we register to them :
13760
+ // TRACE
13761
+ lognow(` (client ${self.clientId}) Sending client registering request to authority server node...`);
13762
+
13763
+ const helloToAuthorityClientRequest={
13764
+ clientId:self.clientId,
13765
+ type:"request.register.client",
13766
+ isReferenceNode:false,
13767
+ };
13768
+ socketToServer.send("protocol", helloToAuthorityClientRequest);
13769
+
13770
+
13771
+ }, splitsServerInfo.protocol+"://"+splitsServerInfo.host, splitsServerInfo.port, false);
13772
+ self.currentServersNodes[serverNodeId]={clientInstance:clientInstanceToAuthorityServerNode};
13773
+ clientInstanceToAuthorityServerNode.client.start();
13774
+
13775
+
13776
+ clientInstanceToAuthorityServerNode.client.socketToServer.receive("protocol", (message)=>{
13777
+
13778
+ // Adding listeners :
13779
+ if(message.type==="response.register.client"){
13780
+ // We place a listener from server on this client instance :
13781
+ // Adding listeners :
13782
+
13783
+ // Case non-reference authority node servers :
13784
+
13785
+ // TRACE
13786
+ lognow(` (${self.clientId}) Receving registering response from reference server...`);
13787
+
13788
+ // We merge our client's model with the partial model sent from the server it just registered to :
13789
+ const partialModelString=message.partialModelString;
13790
+ const partialModel=JSON.parseRecycle(partialModelString);
13791
+
13792
+ // Merging means that all objects in partialModel not being in model must be added to model :
13793
+ self.model.clientMergeWith(partialModel);
13794
+
13795
+ // TRACE
13796
+ lognow("(client "+self.clientId+") Client model has been merged with a partial model from a server node...",self.model);
13797
+
13798
+ }
13799
+
13800
+
13801
+ });
13802
+
13803
+
13804
+
13805
+ clientInstanceToAuthorityServerNode.client.socketToServer.receive("inputs", (message)=>{
13806
+
13807
+ // Adding listeners :
13808
+ if(message.type==="response.objectsModifiedOrAdded.client"){
13809
+ const modifiedOrAddedObjects=JSON.parseRecycle(message.objectsString);
13810
+
13811
+ if(!message.isAddingObjects){
13812
+ self.model.clientUpdateObjects(modifiedOrAddedObjects);
13813
+ }else{
13814
+ self.model.clientAddObjects(modifiedOrAddedObjects);
13815
+ }
13816
+
13817
+ // TRACE
13818
+ lognow(` (client ${self.clientId}) Receving modified or added objects after the inputs...modifiedOrAddedObjects=`,modifiedOrAddedObjects);
13819
+
13820
+
13821
+ }
13822
+
13823
+
13824
+ });
13825
+
13826
+
13827
+ });
13828
+
13829
+ }
13830
+ });
13831
+
13832
+
13833
+ //self.addListeners();
13834
+
13835
+
13836
+
13837
+ }, protocol+"://"+host, port, false);
13838
+ clientInstanceToReferenceServerNode.client.start();
13839
+ this.clientInstanceToReferenceServerNode={clientInstance:clientInstanceToReferenceServerNode};
13840
+
13841
+
13842
+ return this;
13843
+ }
13844
+
13845
+
13846
+ /*public*/sendInputs(inputs, subBoundaries=null){
13847
+
13848
+ const self=this;
13849
+
13850
+
13851
+ if(subBoundaries){
13852
+ // We send client's inputs to ALL its currently connected servers :
13853
+ const self=this;
13854
+ foreach(this.currentServersNodes,(serversNode,serverNodeId)=>{
13855
+ self.sendInputsToServer(serversNode, inputs, subBoundaries);
13856
+ });
13857
+ }else{
13858
+ // If we have no boundaries set for these inputs, then we only send them to one server node at random :
13859
+ const serversNode=Math.getRandomInArray(this.currentServersNodes);
13860
+ this.sendInputsToServer(serversNode, inputs, subBoundaries);
13861
+ }
13862
+
13863
+
13864
+ }
13865
+
13866
+ /*private*/sendInputsToServer(serversNode, inputs, subBoundaries=null){
13867
+ const clientInstance=serversNode.clientInstance;
13868
+
13869
+ const inputsMessage={
13870
+ clientId:this.clientId,
13871
+ type:"request.inputs.client",
13872
+ inputs:inputs,
13873
+ subBoundaries:subBoundaries,
13874
+ };
13875
+
13876
+ clientInstance.client.socketToServer.send("inputs", inputsMessage);
13877
+ }
13878
+
13879
+
13880
+
13881
+
13882
+
13883
+
13884
+ }
13885
+
13886
+
13887
+ getAORTACClient=function(clientId=getUUID(), serverNodeOrigin="ws://127.0.0.1:40000", model, view, isNodeContext=true){
13888
+ return new AORTACClient(clientId, serverNodeOrigin, model, view, isNodeContext);
13889
+ }
13890
+
13891
+
13892
+
13893
+
13894
+
13895
+
13896
+ /*utils GEOMETRY library associated with aotra version : «1_29072022-2359 (19/02/2025-23:41:19)»*/
13897
+ /*-----------------------------------------------------------------------------*/
13627
13898
 
13628
13899
 
13629
-
13630
-
13631
- /*utils GEOMETRY library associated with aotra version : «1_29072022-2359 (13/10/2024-22:45:41)»*/
13632
- /*-----------------------------------------------------------------------------*/
13633
-
13634
-
13635
13900
  /* ## Utility global methods in a javascript, at least console (nodejs) server, or vanilla javascript with no browser environment.
13636
13901
  *
13637
13902
  * This set of methods gathers utility generic-purpose methods usable in any JS project.
@@ -14867,7 +15132,7 @@ function rayVsUnitSphereClosestPoint(p, r) {
14867
15132
  // MUST REMAIN AT THE END OF THIS LIBRARY FILE !
14868
15133
 
14869
15134
  AOTRAUTILS_GEOMETRY_LIB_IS_LOADED=true;
14870
- /*utils AI library associated with aotra version : «1_29072022-2359 (13/10/2024-22:45:41)»*/
15135
+ /*utils AI library associated with aotra version : «1_29072022-2359 (19/02/2025-23:41:19)»*/
14871
15136
  /*-----------------------------------------------------------------------------*/
14872
15137
 
14873
15138
 
@@ -15011,7 +15276,7 @@ getOpenAIAPIClient=(modelName, apiURL, agentRole, defaultPrompt)=>{
15011
15276
 
15012
15277
 
15013
15278
 
15014
- /*utils SERVER library associated with aotra version : «1_29072022-2359 (13/10/2024-22:45:41)»*/
15279
+ /*utils SERVER library associated with aotra version : «1_29072022-2359 (19/02/2025-23:41:19)»*/
15015
15280
  /*-----------------------------------------------------------------------------*/
15016
15281
 
15017
15282
 
@@ -15189,12 +15454,12 @@ getServerParams=function(portParam=null, certPathParam=null, keyPathParam=null,
15189
15454
  // https=require("https");
15190
15455
  // fs=require("fs");
15191
15456
 
15192
- if(!https){
15457
+ if(typeof(https)==="undefined"){
15193
15458
  // TRACE
15194
15459
  console.log("WARN : Could not find the nodejs dependency «https», aborting SSL setup.");
15195
15460
  return null;
15196
15461
  }
15197
- if(!fs){
15462
+ if(typeof(fs)==="undefined"){
15198
15463
  // TRACE
15199
15464
  console.log("WARN : Could not find the nodejs dependency «fs», aborting SSL setup.");
15200
15465
  return null;
@@ -15237,7 +15502,17 @@ getServerParams=function(portParam=null, certPathParam=null, keyPathParam=null,
15237
15502
  return result;
15238
15503
  }
15239
15504
 
15240
-
15505
+ getConsoleParam(index=0, argsOffset=0){
15506
+ let result=null;
15507
+ if(!process){
15508
+ throw new Error("ERROR : Cannot extract console parameter in this context !");
15509
+ }
15510
+ process.argv.forEach((val, i)=>{
15511
+ if(i<=argsOffset+1) return;
15512
+ else if(i==argsOffset+index+1) result=val;
15513
+ });
15514
+ return result;
15515
+ }
15241
15516
 
15242
15517
 
15243
15518
 
@@ -15305,6 +15580,7 @@ WebsocketImplementation={
15305
15580
  if(typeof(WebSocket)==="undefined"){
15306
15581
  // TRACE
15307
15582
  console.log("«ws» SERVER library not called yet, calling it now.");
15583
+
15308
15584
  WebSocket=require("ws");
15309
15585
  if(typeof(WebSocket)==="undefined"){
15310
15586
  // TRACE
@@ -15406,7 +15682,7 @@ WebsocketImplementation={
15406
15682
  receive:(channelNameParam, doOnIncomingMessage, clientsRoomsTag=null)=>{
15407
15683
 
15408
15684
  // DBG
15409
- lognow("(SERVER) Registering receive for «"+channelNameParam+"»...",doOnIncomingMessage);
15685
+ lognow("(SERVER) Registering receive for «"+channelNameParam+"»...");
15410
15686
 
15411
15687
 
15412
15688
 
@@ -15444,10 +15720,11 @@ WebsocketImplementation={
15444
15720
 
15445
15721
  if(!isClientInRoom) return;
15446
15722
 
15447
- // DBG
15448
- lognow("(SERVER) doOnIncomingMessage:",doOnIncomingMessage);
15449
- if(doOnIncomingMessage) doOnIncomingMessage(dataWrapped.data, clientSocketParam);
15450
-
15723
+ if(doOnIncomingMessage){
15724
+ // DBG
15725
+ lognow("(SERVER) doOnIncomingMessage:");
15726
+ doOnIncomingMessage(dataWrapped.data, clientSocketParam);
15727
+ }
15451
15728
 
15452
15729
  },
15453
15730
  };
@@ -15705,7 +15982,7 @@ WebsocketImplementation={
15705
15982
  return nodeServerInstance;
15706
15983
  },
15707
15984
 
15708
-
15985
+ // DO NOT USE DIRECTLY, USE INSTEAD initClient(...) (this function uses connectToServer(...)) !
15709
15986
  // NODE / BROWSER CLIENT CONNECTS TO SERVER MAIN ENTRYPOINT:
15710
15987
  connectToServer:(serverURL, port, isSecure=false, timeout)=>{
15711
15988
 
@@ -16333,7 +16610,7 @@ launchNodeHTTPServer=function(port, doOnConnect=null, doOnFinalizeServer=null, /
16333
16610
  server.onFinalize((serverParam)=>{
16334
16611
 
16335
16612
  // DBG
16336
- lognow("onFinalize!!!!!");
16613
+ lognow("onFinalize() server");
16337
16614
 
16338
16615
  if(doOnFinalizeServer) doOnFinalizeServer(serverParam);
16339
16616
  });
@@ -16471,7 +16748,7 @@ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFina
16471
16748
  // Eventual encryption options :
16472
16749
  let sslOptions=null;
16473
16750
  if(!isForceUnsecure){
16474
- if(!fs){
16751
+ if(typeof(fs)==="undefined"){
16475
16752
  // TRACE
16476
16753
  lognow("ERROR : «fs» node subsystem not present, cannot access files. Aborting SSL configuration of server.");
16477
16754
  }else{
@@ -16522,7 +16799,7 @@ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFina
16522
16799
  //
16523
16800
  // const server=initNodeServerInfrastructureWrapper(
16524
16801
  // // On each client connection :
16525
- //// (serverParam, clientSocketParam)=>{},
16802
+ // // (serverParam, clientSocketParam)=>{},
16526
16803
  // null,
16527
16804
  // // On server finalization :
16528
16805
  // (serverParam)=>{
@@ -16555,15 +16832,15 @@ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFina
16555
16832
  // },portParam,certPathParam,keyPathParam);
16556
16833
  //
16557
16834
  //
16558
- //// const doOnConnect=(serverParam, clientSocketParam)=>{
16559
- //// };
16560
- //// const doOnFinalizeServer=(serverParam)=>{
16561
- //// /*DO NOTHING*/
16562
- //// };
16563
- //// const server={};
16564
- //// server.start=(port=6080)=>{
16565
- //// server.httpServer=launchNodeHTTPServer(port, doOnConnect, doOnFinalizeServer, sslOptions);
16566
- //// };
16835
+ // // const doOnConnect=(serverParam, clientSocketParam)=>{
16836
+ // // };
16837
+ // // const doOnFinalizeServer=(serverParam)=>{
16838
+ // // /*DO NOTHING*/
16839
+ // // };
16840
+ // // const server={};
16841
+ // // server.start=(port=6080)=>{
16842
+ // // server.httpServer=launchNodeHTTPServer(port, doOnConnect, doOnFinalizeServer, sslOptions);
16843
+ // // };
16567
16844
  //
16568
16845
  // return server;
16569
16846
  //}
@@ -16907,23 +17184,47 @@ appendGetParameters=function(apiURL, namedArgs){
16907
17184
 
16908
17185
  //*********************************** AUTO-ORGANIZING REAL-TIME AORTAC CLUSTERIZATION (AORTAC) *********************************** */
16909
17186
 
17187
+ AORTAC_OUTCOMING_SERVERS_CONNECTION_GLOBAL_TIMEOUT=30000;
17188
+ REQUESTS_IDS_HISTORY_SIZE=10;
17189
+
16910
17190
  class AORTACNode{
16911
- constructor(selfOrigin="127.0.0.1:40000",outcomingNodesOrigins=[], model, controller, sslConfig={/*OPTIONAL*/port:null,/*OPTIONAL*/certPath:null,/*OPTIONAL*/keyPath:null}){
17191
+
17192
+ constructor(nodeId, selfOrigin="127.0.0.1:40000",outcomingNodesOrigins=[], model, controller, allowAnonymousNodes=true, sslConfig={/*OPTIONAL*/certPath:null,/*OPTIONAL*/keyPath:null}){
17193
+
17194
+ this.nodeId=nodeId;
16912
17195
  this.selfOrigin=selfOrigin;
17196
+ this.sslConfig=sslConfig;
16913
17197
  this.outcomingNodesOrigins=outcomingNodesOrigins;
17198
+ this.allowAnonymousNodes=allowAnonymousNodes;
16914
17199
 
16915
- this.model=model; // must implement function
16916
- this.controller=controller; // Must implement function
16917
- this.serverId=getUUID();
17200
+ this.model=model; // must implement functions
17201
+ this.controller=controller; // Must implement functions
16918
17202
 
16919
17203
  this.server=null;
16920
17204
  this.clients={};
16921
17205
  this.incomingServers={};
16922
17206
  this.outcomingServers={};
17207
+ this.authorizedNodesIds=[this.nodeId];
17208
+
17209
+ this.listeners={"protocol":[],"cluster":[],"neighbors":[],"inputs":[]};
17210
+
17211
+ this.executedRequestIdsHistory=[];
16923
17212
 
17213
+ this.isConnectedToCluster=false;
17214
+
17215
+ this.modelObjectsDirectory={};
17216
+ this.objectsIndexesForClients={};
17217
+
17218
+ this.addProtocolListeners();
17219
+
17220
+ // TRACE
17221
+ lognow(`AORTAC node created with id ${this.nodeId}.`);
16924
17222
  }
16925
17223
 
16926
17224
  start(){
17225
+
17226
+ const splits=splitURL(this.selfOrigin);
17227
+ let port=nonull(splits.port,"40000");
16927
17228
 
16928
17229
  const self=this;
16929
17230
  this.server = initNodeServerInfrastructureWrapper(
@@ -16931,62 +17232,819 @@ class AORTACNode{
16931
17232
  null,
16932
17233
  // On client finalization :
16933
17234
  function(server){
16934
-
16935
17235
  self.server=server;
16936
17236
 
16937
- server.receive("protocol", (message, clientSocket)=>{
16938
-
16939
- // TRACE
16940
- console.log("INFO : SERVER : Client has sent a protocol message: ", message);
16941
-
16942
- // ============== OTHER SERVER / CLIENT REGISTRATION PHASE ==============
16943
-
16944
- if(message.type==="registerOtherServer"){
16945
- const incomingServerId=message.serverId;
16946
-
16947
- self.incomingServers[incomingServerId]={clientSocket:clientSocket};
17237
+ // Listeners handling ;
17238
+ foreach(Object.keys(self.listeners), channelName=>{
17239
+ self.server.receive(channelName, (message, clientSocket)=>{
16948
17240
 
16949
- }else if(message.type==="registerClient"){
16950
- const clientId=message.clientId;
17241
+ // INCOMING NODES LISTENERS HOOK :
17242
+ // TRACE
17243
+ console.log("INFO : SERVER : Client or incoming node has sent a "+channelName+" message: ", message);
17244
+ foreach(self.listeners[channelName], listener => {
17245
+ listener.execute(self, message, self.server, clientSocket);
17246
+ },(listener)=>(message.type===listener.messageType));
16951
17247
 
16952
- self.clients[incomingServerId]={clientSocket:clientSocket};
16953
- }
16954
-
17248
+ });
16955
17249
  });
17250
+
17251
+ // OLD : self.connectToOutcomingServers(self, server).then((server)=>{ self.doOnConnectedToCluster(); });
17252
+
17253
+
17254
+ }, port, this.sslConfig.certPath, this.sslConfig.keyPath);
17255
+
17256
+ this.server.serverManager.start();
17257
+
17258
+ // TRACE
17259
+ lognow(`AORTAC node started with id ${this.nodeId}.`);
17260
+
17261
+
17262
+ return this;
17263
+ }
17264
+
17265
+
17266
+ connect(){
17267
+
17268
+ const self=this;
17269
+ self.connectToOutcomingServers(self).then((server)=>{ self.doOnConnectedToCluster(); });
17270
+
17271
+ return this;
17272
+ }
17273
+
17274
+
17275
+
17276
+
17277
+ /*private*/addProtocolListeners(){
17278
+
17279
+ const self=this;
17280
+ // ============== OTHER SERVER / CLIENT REGISTRATION PHASE ==============
17281
+ // - OTHER SERVER REGISTRATION PHASE :
17282
+ this.listeners["protocol"].push({
17283
+ messageType:"request.register.server.incoming",
17284
+ execute:(self, message, server, clientSocket)=>{
17285
+ const incomingServerNodeId=message.nodeId;
17286
+
17287
+ // TRACE
17288
+ lognow(` (${self.nodeId}) Receiving registering request from node ${incomingServerNodeId}...`);
17289
+
17290
+ if(!self.allowAnonymousNodes && self.isInAuthorizedNodes(incomingServerNodeId)){
17291
+ // TRACE
17292
+ lognow("WARN : Cannot accept incoming server with node id «"+incomingServerNodeId+"» because it's not in the authorized nodes list.");
17293
+ return;
17294
+ }
17295
+ if(!contains(self.authorizedNodesIds,incomingServerNodeId)) self.authorizedNodesIds.push(incomingServerNodeId);
17296
+
17297
+ // TRACE
17298
+ lognow(` (${self.nodeId}) Adding registering node ${incomingServerNodeId} to incoming servers...`);
17299
+
17300
+ self.incomingServers[incomingServerNodeId]={clientSocket:clientSocket};
17301
+
17302
+ const welcomeResponse={
17303
+ nodeId:self.nodeId, // MUST BE SELF ! ELSE THE REGISTERING CLIENT WON'T KNOW WHICH ID IT HAS BEEN REGISTERED TO !
17304
+ type:"response.register.server.incoming",
17305
+ authorizedNodesIds:self.authorizedNodesIds
17306
+ };
17307
+ server.send("protocol", welcomeResponse, null, clientSocket);
17308
+
17309
+ // TRACE
17310
+ lognow(` (${self.nodeId}) Sent registering response to node ${incomingServerNodeId}.`);
17311
+ }
17312
+ });
17313
+
17314
+ // - CLIENT REGISTRATION PHASE : (for clients to the server, NOT INCOMING NODES !)
17315
+ this.handleClients();
17316
+
17317
+ }
17318
+
17319
+ /*private*/handleClients(){
17320
+ const self=this;
17321
+
17322
+ // --------- PROTOCOL HANDLING ---------
17323
+
17324
+ this.listeners["protocol"].push({
17325
+ messageType:"request.register.client",
17326
+ execute:(self, message, server, clientSocket)=>{
17327
+ const clientId=message.clientId;
17328
+
17329
+ self.clients[clientId]={clientSocket:clientSocket};
17330
+
17331
+ // TRACE
17332
+ lognow(` (${self.nodeId}) Receiving non-cluster client registration request from client ${clientId}.`);
17333
+
17334
+ const welcomeClientResponse={
17335
+ nodeId:self.nodeId, // MUST BE SELF ! ELSE THE REGISTERING CLIENT WON'T KNOW WHICH ID IT HAS BEEN REGISTERED TO !
17336
+ type:"response.register.client",
17337
+ // If necessary we also send them our model part :
17338
+ partialModelString:(message.isReferenceNode?null:JSON.stringifyDecycle(self.model)),
17339
+ isReferenceNode:(!!message.isReferenceNode),
17340
+ };
17341
+ server.send("protocol", welcomeClientResponse, null, clientSocket);
17342
+ }
17343
+ });
17344
+
17345
+ this.listeners["protocol"].push({
17346
+ messageType:"request.unregister.client",
17347
+ execute:(self, message, server, clientSocket)=>{
17348
+ const clientId=message.clientId;
17349
+
17350
+ delete self.clients[clientId];
17351
+
17352
+ // TRACE
17353
+ lognow(` (${self.nodeId}) Receiving non-cluster client unregistration request from client ${clientId}.`);
17354
+
17355
+ const unwelcomeClientResponse={
17356
+ nodeId:self.nodeId, // MUST BE SELF ! ELSE THE REGISTERING CLIENT WON'T KNOW WHICH ID IT HAS BEEN REGISTERED TO !
17357
+ type:"response.unregister.client",
17358
+ };
17359
+ server.send("protocol", unwelcomeClientResponse, null, clientSocket);
17360
+ }
17361
+ });
17362
+
17363
+
17364
+ this.listeners["protocol"].push({
17365
+ messageType:"request.interrogation.client",
17366
+ execute:(self, message, server, clientSocket)=>{
17367
+ const clientId=message.clientId;
17368
+
17369
+ // TRACE
17370
+ lognow(` (${self.nodeId}) Receiving non-cluster client interrogation request from client ${clientId}.`);
17371
+
17372
+ const clientBoundaries=message.boundaries;
17373
+
17374
+ // We ask the whole cluster to find the requested objects :
17375
+ const modelSeekObjectsRequest={
17376
+ type:"request.model.seekObjects",
17377
+ originatingClientId:clientId,
17378
+ clientBoundaries:clientBoundaries,
17379
+ };
17380
+ this.propagateToCluster(modelSeekObjectsRequest);
17381
+
17382
+ self.objectsIndexesForClients[clientId]={};
17383
+
17384
+ }
17385
+ });
17386
+
17387
+ // --------- INPUTS HANDLING ---------
17388
+
17389
+ this.listeners["inputs"].push({
17390
+ messageType:"request.inputs.client",
17391
+ execute:(self, message, server, clientSocket)=>{
17392
+ const clientId=message.clientId;
17393
+
17394
+ // TRACE
17395
+ lognow(` (${self.nodeId}) Receiving non-cluster client inputs request from client ${clientId}.`);
17396
+
17397
+ const clientInputs=message.inputs;
17398
+ const clientSubBoundaries=message.subBoundaries;
17399
+
17400
+
17401
+ // We let the controller interpret these inputs :
17402
+ const modifiedObjects=self.controller.interpretInputs(clientInputs, clientSubBoundaries);
17403
+
17404
+
17405
+ }
17406
+ });
17407
+
17408
+ }
17409
+
16956
17410
 
17411
+ /*private*/addPropagationForClientsListeners(){
17412
+
17413
+ const self=this;
17414
+
17415
+ // ------------------------------------- CLIENTS PROTOCOL HANDLING -------------------------------------
17416
+ // Adding propagation listener :
17417
+ this.setPropagation("cluster","request.model.seekObjects",(self, message, server, clientSocket)=>{
17418
+
17419
+ // If we receive this request from cluster, then we try to find the requested objects
17420
+ const relayNodeid=message.nodeId;
17421
+ const originatingClientId=message.originatingClientId;
17422
+ const clientBoundaries=message.clientBoundaries
17423
+ const objectsIds=self.model.getObjectsIdsWithinBoundaries(clientBoundaries);
17424
+
17425
+ // If server has not the seeked objects, it answers with an empty array.
17426
+
17427
+ // TRACE
17428
+ lognow(`(${self.nodeId}) Node receiving a seek objects request from relay node ${message.nodeId}...objectsIds=`,objectsIds);
16957
17429
 
17430
+ // TRACE
17431
+ lognow(`(${self.nodeId}) Sending objects ids to relay node ${relayNodeid}...`);
17432
+
17433
+ const modelObjectsSeekResponse={
17434
+ type:"response.model.seekObject",
17435
+ originatingClientId:originatingClientId,
17436
+ clientBoundaries:clientBoundaries,
17437
+ objectsIds:objectsIds,
17438
+ nodeServerInfo:self.selfOrigin
17439
+ };
17440
+ this.propagateToCluster(modelObjectsSeekResponse, relayNodeid/*destination node*/);
17441
+
17442
+ });
17443
+
17444
+ // Adding propagation listener :
17445
+ this.setPropagation("cluster","response.model.seekObject",(self, message, server, clientSocket)=>{
17446
+
17447
+ // Here the relay server gathers the information from the other nodes, and sends it back to client ;
17448
+
17449
+ const referenceNodeId=self.nodeId;
17450
+ const originatingClientId=message.originatingClientId;
17451
+ const originatingNodeId=message.nodeId;
17452
+ const clientBoundaries=message.clientBoundaries;
17453
+ const objectsIds=message.objectsIds;
17454
+ const nodeServerInfo=message.nodeServerInfo;
17455
+
17456
+ // TRACE
17457
+ lognow(`(${self.nodeId}) Node receiving a seek objects request from relay node ${message.nodeId}...objectsIds=`,objectsIds);
17458
+
17459
+ // Here the currently answering node fills its information :
17460
+ const objectsIndexForClient=self.objectsIndexesForClients[originatingClientId];
17461
+ objectsIndexForClient[originatingNodeId]={nodeServerInfo:nodeServerInfo, objectsIds:objectsIds};
17462
+
17463
+
17464
+ // We check if all known nodes have answered :
17465
+ if(getArraySize(self.authorizedNodesIds)-1/*Because we exclude the reference node*/<=getArraySize(objectsIndexForClient)){
17466
+
17467
+ // The reference node also answers to the request to its direct client :
17468
+ const objectsIdsFromReferenceNode=self.model.getObjectsIdsWithinBoundaries(clientBoundaries);
17469
+ const referenceNodeServerInfo=self.selfOrigin;
17470
+ objectsIndexForClient[referenceNodeId]={nodeServerInfo:referenceNodeServerInfo, objectsIds:objectsIdsFromReferenceNode};
17471
+
17472
+ const client=self.clients[originatingClientId];
17473
+ const clientSocket=client.clientSocket;
17474
+
17475
+ // TRACE
17476
+ lognow(`(${self.nodeId}) Sending objects ids to the client ${originatingClientId}...objectsIndexForClient=`,objectsIndexForClient);
17477
+
17478
+ // We filter all the index entries with an empty objetcs ids array :
17479
+ const objectsIndexForClientWithoutEmptyObjects={};
17480
+ foreach(objectsIndexForClient,(nodeInfoAndObjectsIds, serverNodeId)=>{
17481
+ objectsIndexForClientWithoutEmptyObjects[serverNodeId]=nodeInfoAndObjectsIds;
17482
+ },(nodeInfoAndObjectsIds,serverNodeId)=>(!empty(nodeInfoAndObjectsIds.objectsIds)));
17483
+
17484
+ // We send the final result information (objects ids) to client :
17485
+ const interrogationClientResponse={
17486
+ nodeId:self.nodeId,
17487
+ type:"response.interrogation.client",
17488
+ serversNodesIdsForModelObjectsForClient:objectsIndexForClientWithoutEmptyObjects
17489
+ };
17490
+ self.server.send("protocol", interrogationClientResponse, null, clientSocket);
17491
+ }
17492
+
17493
+ });
17494
+
17495
+
17496
+ // ------------------------------------- CLIENTS INPUTS HANDLING -------------------------------------
17497
+ // Adding propagation listener :
17498
+
17499
+
17500
+
17501
+ // DBG
17502
+ lognow(">>>>>>>>FOR CLIENTS this.listeners",this.listeners);
17503
+
17504
+ }
17505
+
17506
+ // /*private*/getServersNodesIdsForModelObjectsForClient(clientObjectsIds){
17507
+ // const serversNodesIdsForModelObjectsForClient={};
17508
+ // const self=this;
17509
+ // // TODO : FIXME : INEFFICIENT !!!
17510
+ // foreach(clientObjectsIds,(objectId)=>{
17511
+ // foreach(self.modelObjectsDirectory,(objectsInNode, nodeId)=>{
17512
+ // if(contains(objectsInNode,objectId))
17513
+ // serversNodesIdsForModelObjectsForClient[objectId]=nodeId;
17514
+ // });
17515
+ // });
17516
+ //
17517
+ // return serversNodesIdsForModelObjectsForClient;
17518
+ // }
17519
+
17520
+ /*private*/connectToOutcomingServers(self){
17521
+
17522
+ const server=self.server;
17523
+
17524
+ const theoreticNumberOfOutcomingServers=getArraySize(self.outcomingNodesOrigins);
17525
+
17526
+ return new Promise((resolve,error)=>{
17527
+
17528
+ // In case we timeout :
17529
+ setTimeout(()=>{
17530
+ if(self.isConnectedToCluster) return;
17531
+ self.isConnectedToCluster=true;
17532
+ resolve(server);
17533
+ },AORTAC_OUTCOMING_SERVERS_CONNECTION_GLOBAL_TIMEOUT);
17534
+
16958
17535
  // We try to connect to all the other outcoming servers :
17536
+
17537
+ // Special case : if node has no outcoming serves, (it's the seed node), then
17538
+ // it should be considered as connected to cluster nonetheless :
17539
+ if(empty(self.outcomingNodesOrigins)){
17540
+ self.finalizeClusterConnection(self.nodeId);
17541
+ resolve(server);
17542
+ return;
17543
+ }
17544
+
17545
+ // We try to connect to all outcoming nodes :
16959
17546
  foreach(self.outcomingNodesOrigins, outcomingNodeOrigin=>{
16960
- let outcomingNodeHost;
16961
- let outcomingNodePort;
16962
- if(contains(outcomingNodeOrigin,":")){
16963
- const splits=outcomingNodeOrigin.split(":");
16964
- outcomingNodeHost=splits[0];
16965
- outcomingNodePort=splits[1];
16966
- }
16967
- const isSecure=(contains(outcomingNodeOrigin,"wss"));
16968
- const clientInstance=connectToServer(outcomingNodeHost,outcomingNodePort, isSecure, 5000);
16969
17547
 
16970
- //self.outcomingServers[]={clientInstanceToServer:clientInstance};
17548
+ // TRACE
17549
+ lognow(` (${self.nodeId}) Sending registering response to node ${outcomingNodeOrigin}...`);
17550
+
17551
+ const splits=splitURL(outcomingNodeOrigin);
17552
+ const outcomingNodeProtocol=nonull(splits.protocol,"ws");
17553
+ const outcomingNodeHost=nonull(splits.host,"localhost");
17554
+ const outcomingNodePort=nonull(splits.port,"40000");
17555
+ //const isSecure=splits.isSecure; // UNUSED
17556
+
17557
+ const clientInstance=initClient(true, false, (socketToServer)=>{
17558
+
17559
+ // TODO : FIXME : IF CONNECTION TO SERVER FAILS, IT MUST BE ABLE TO TRY AGAIN LATER !
17560
+
17561
+ // TRACE
17562
+ lognow(` (${self.nodeId}) Sending registering request to outcoming node...`);
17563
+
17564
+ const helloRequest={
17565
+ nodeId:self.nodeId,
17566
+ type:"request.register.server.incoming"
17567
+ };
17568
+ socketToServer.send("protocol", helloRequest);
17569
+
17570
+
17571
+ // We place a listener from outcoming node on this client instance to this outcoming node :
17572
+ socketToServer.receive("protocol",(message)=>{
17573
+ if(message.type==="response.register.server.incoming"){
17574
+
17575
+ // TRACE
17576
+ lognow(` (${self.nodeId}) Receving registering response from requested outcoming node...`);
17577
+
17578
+ const welcomeNodeId=message.nodeId;
17579
+ const duplicatesFreeAuthorizedNodesIds=[...new Set([...self.authorizedNodesIds, ...message.authorizedNodesIds, welcomeNodeId ])];
17580
+ self.authorizedNodesIds=duplicatesFreeAuthorizedNodesIds;
17581
+
17582
+ self.outcomingServers[welcomeNodeId]={clientInstance:clientInstance};
17583
+
17584
+ // Once we have registered to all the outcoming nodes :
17585
+ const currentNumberOfOutcomingServers=getArraySize(self.outcomingServers);
17586
+ if(!self.isConnectedToCluster && theoreticNumberOfOutcomingServers<=currentNumberOfOutcomingServers){
17587
+ //self.finalizeClusterConnection(welcomeNodeId);
17588
+ self.finalizeClusterConnection(self.nodeId);
17589
+ resolve(server);
17590
+ }
17591
+ }
17592
+ });
17593
+
17594
+ // Listeners handling ;
17595
+ foreach(Object.keys(self.listeners), channelName=>{
17596
+ socketToServer.receive(channelName, (message, clientSocket)=>{
17597
+
17598
+ // OUTCOMING NODES LISTENERS HOOK :
17599
+ // TRACE
17600
+ console.log("INFO : SERVER : Outcoming node has sent a "+channelName+" message: ", message);
17601
+ foreach(self.listeners[channelName], listener => {
17602
+ listener.execute(self, message, self.server, clientSocket);
17603
+ },(listener)=>(message.type===listener.messageType));
17604
+
17605
+ });
17606
+ });
17607
+
17608
+
17609
+ }, outcomingNodeProtocol+"://"+outcomingNodeHost, outcomingNodePort, false);
17610
+ clientInstance.client.start();
16971
17611
 
16972
17612
  });
16973
17613
 
17614
+ });
17615
+
17616
+ }
17617
+
17618
+ /*private*/finalizeClusterConnection(welcomeNodeId){
17619
+ // TRACE
17620
+ lognow(` (${this.nodeId}) Propagating this new node to the whole cluster...`);
17621
+
17622
+ // We propagate the new node id to the whole cluster :
17623
+ const newNodeRequest={
17624
+ nodeId:welcomeNodeId,
17625
+ type:"request.node.new"
17626
+ };
17627
+ this.propagateToCluster(newNodeRequest);
17628
+
17629
+ this.isConnectedToCluster=true;
17630
+
17631
+ this.addPropagationListeners();
17632
+ this.addPropagationForClientsListeners();
17633
+ }
17634
+
17635
+
17636
+
17637
+
17638
+ /*public*/doOnConnectedToCluster(){
17639
+
17640
+ // TRACE
17641
+ lognow(`(${this.nodeId}) Node is connected to cluster...`);
17642
+
17643
+ // The node asks the cluster for its model partition :
17644
+
17645
+ // At this point, the new node does not know the state of the model in the cluster, only cluster nodes know.
17646
+
17647
+ // TRACE
17648
+ lognow(`(${this.nodeId}) Node is asking to its model partition...`);
17649
+
17650
+ // New node asks for its model partition :
17651
+
17652
+ // - First we need to know which server has the biggest model :
17653
+ const modelSizeRequest={
17654
+ type:"request.model.getSize",
17655
+ };
17656
+ this.propagateToCluster(modelSizeRequest);
17657
+
17658
+ }
17659
+
17660
+
17661
+ /*private*/addPropagationListeners(){
17662
+
17663
+ // Adding propagation listener for acknowledging new node added to cluster :
17664
+ this.setPropagation("cluster","request.node.new", (self, message, server, clientSocket)=>{
17665
+ const newNodeId=message.nodeId;
17666
+ // TRACE
17667
+ lognow(` (${self.nodeId}) Acknowledging new node ${newNodeId} added to cluster...`);
17668
+ if(!contains(self.authorizedNodesIds,newNodeId)) self.authorizedNodesIds.push(newNodeId);
17669
+ });
17670
+
17671
+ // Adding propagation listener if a node receives a model size request :
17672
+ this.setPropagation("cluster","request.model.getSize",(self, message, server, clientSocket)=>{
17673
+ const modelSize=self.model.getModelSize();
17674
+ // TRACE
17675
+ lognow(`(${self.nodeId}) Node gives its model size to node ${message.nodeId}, modelSize=${modelSize}...`);
17676
+ return modelSize;
17677
+ },"response.model.getSize");
17678
+
17679
+ // Adding propagation listener :
17680
+ this.modelSizes={};
17681
+ this.setPropagation("cluster","response.model.getSize",(self, message, server, clientSocket)=>{
17682
+
17683
+ const modelSizes=self.modelSizes;
17684
+ const currentClusterSize=getArraySize(self.authorizedNodesIds);
17685
+
17686
+ const concernedNodeId=message.nodeId;
17687
+ const modelSize=message.result;
17688
+
17689
+ let currentNumberOfAnswers=getArraySize(modelSizes);
17690
+
17691
+ if(currentNumberOfAnswers<=currentClusterSize-1){// -1 because we want to exclude this node also !
17692
+ modelSizes[concernedNodeId]=modelSize;
17693
+ }
17694
+
17695
+ currentNumberOfAnswers=getArraySize(modelSizes);
17696
+ // TRACE
17697
+ lognow(`(${self.nodeId}) Node receiving a model size from node ${message.nodeId}...currentClusterSize=${currentClusterSize} ; modelSizes:`,modelSizes);
17698
+ if(currentClusterSize-1<=currentNumberOfAnswers){
17699
+ const nodeIdWithBiggestModel=Math.maxInArray(modelSizes, true);
17700
+ // TRACE
17701
+ lognow(`(${self.nodeId}) Node with biggest model is nodeIdWithBiggestModel=${nodeIdWithBiggestModel}...`);
17702
+
17703
+ const modelPartitionRequest={
17704
+ type:"request.model.partition",
17705
+ };
17706
+ this.propagateToCluster(modelPartitionRequest, nodeIdWithBiggestModel);
17707
+ }
17708
+ });
17709
+
17710
+ // Adding propagation listener :
17711
+ // If a node receives a model partition request :
17712
+ this.setPropagation("cluster","request.model.partition",(self, message, server, clientSocket)=>{
17713
+ // Each node will give a little portion of its model, according to its size :
17714
+ // TRACE
17715
+ lognow(`(${self.nodeId}) Node splits its model for node ${message.nodeId}...`);
17716
+
17717
+ const ownModel=self.model;
17718
+ const splittedModel=ownModel.split();
17719
+
17720
+ // Node shares also its model objects directory with the newcomer node :
17721
+ const ownModelObjectsIds=ownModel.getAllObjectsIds();
17722
+ self.modelObjectsDirectory[self.nodeId]=ownModelObjectsIds;
17723
+
17724
+ const modelString=JSON.stringifyDecycle(splittedModel);
17725
+ const modelPartitionResponse={
17726
+ type:"response.model.partition",
17727
+ modelString:modelString,
17728
+ modelObjectsDirectory:self.modelObjectsDirectory
17729
+ };
17730
+ this.propagateToCluster(modelPartitionResponse, message.nodeId);
17731
+
17732
+ // This node also advertises that its model has changed to all other nodes (not the newcomer, because it's unnecessary) :
17733
+ const updatemodelObjectsDirectoryRequest={
17734
+ type:"request.update.modelObjectsDirectory",
17735
+ ownModelObjectsIds:ownModelObjectsIds
17736
+ };
17737
+ this.propagateToCluster(updatemodelObjectsDirectoryRequest, null, [message.nodeId]);
17738
+
17739
+ });
17740
+
17741
+ // Adding propagation listener :
17742
+ this.setPropagation("cluster","response.model.partition",(self, message, server, clientSocket)=>{
17743
+ // TRACE
17744
+ lognow(`(${self.nodeId}) Node receives a model partition from node ${message.nodeId}...`,message);
17745
+
17746
+ const newModel=JSON.parseRecycle(message.modelString);
17747
+ self.model.replaceBy(newModel);
16974
17748
 
17749
+ // We initialize this node's model objects directory from the one froom the node tha provided it its model partition :
17750
+ self.modelObjectsDirectory=message.modelObjectsDirectory;
16975
17751
 
17752
+ // Once this node has received its model partition, it updates its model objects directory
17753
+ const ownModelObjectsIds=newModel.getAllObjectsIds();
17754
+ self.modelObjectsDirectory[self.nodeId]=ownModelObjectsIds;
16976
17755
 
16977
- ////
16978
- // CURRENT AORTAC
17756
+ // TRACE
17757
+ lognow(`(${self.nodeId}) Node sends a model objects directory update request...ownModelObjectsIds=${ownModelObjectsIds}`);
16979
17758
 
17759
+ // And notifies the other nodes of the change :
17760
+ const updatemodelObjectsDirectoryRequest={
17761
+ type:"request.update.modelObjectsDirectory",
17762
+ ownModelObjectsIds:ownModelObjectsIds
17763
+ };
17764
+ this.propagateToCluster(updatemodelObjectsDirectoryRequest);
17765
+
17766
+ });
17767
+
17768
+ this.addModelObjectsDirectoryListeners();
17769
+
17770
+
17771
+ // DBG
17772
+ lognow(">>>>>>>>FOR OTHER NODES this.listeners",this.listeners);
17773
+
17774
+ }
17775
+
17776
+
17777
+
17778
+ /*private*/addModelObjectsDirectoryListeners(){
17779
+
17780
+ const self=this;
17781
+
17782
+ // Adding propagation listener :
17783
+ // If a node receives a model objects directory update request :
17784
+ this.setPropagation("cluster","request.update.modelObjectsDirectory",(self, message, server, clientSocket)=>{
17785
+
17786
+ // TRACE
17787
+ lognow(`(${self.nodeId}) !!!!!! Node receives a model objects directory update request from node ${message.nodeId}...`,message);
17788
+
17789
+ const modelObjectsIds=message.ownModelObjectsIds;
17790
+ self.modelObjectsDirectory[message.nodeId]=modelObjectsIds;
17791
+
17792
+ });
17793
+
17794
+
17795
+ // Adding propagation listener :
17796
+ // If a node receives a model objects directory update request :
17797
+ this.setPropagation("cluster","request.add.modelObjectsDirectory",(self, message, server, clientSocket)=>{
17798
+
17799
+ // TRACE
17800
+ lognow(`(${self.nodeId}) !!!!!! Node receives a model objects directory add request from node ${message.nodeId}...`,message);
16980
17801
 
16981
- },this.sslConfig.port, this.sslConfig.certPath, this.sslConfig.keyPath);
17802
+ const newObjectsIds=message.newObjectsIds;
17803
+ self.modelObjectsDirectory[message.nodeId].push(...newObjectsIds);
17804
+
17805
+ });
17806
+
17807
+ }
17808
+
17809
+
17810
+ /*public*/sendUpdatedObjects(modifiedObjects, modifiedObjectsIds){
17811
+
17812
+ if(empty(modifiedObjects)) return modifiedObjects;
17813
+
17814
+ // Then we send back the modified objects to all the concerned clients : (which are all clients connected to this node)
17815
+ return this.sendObjectsUpdatesToClients(modifiedObjects);
17816
+ }
17817
+
17818
+ /*public*/sendNewObjects(newObjects, newObjectsIds){
17819
+
17820
+ if(empty(newObjects)) return newObjects;
17821
+
17822
+ // At this point, node controller has already added the objects to its model.
17823
+ // We just want to update the objects directories of all the nodes in the cluster:
17824
+
17825
+ // !!! CAUTION : NOTE THAT THE EMITTER NODE IS ALWAYS INCLUDED IN THE THE VISITED NODES !!!
17826
+ // So this is why ir needs to update its mode objects directory itself, before sending the add request to the cluster :
17827
+ this.modelObjectsDirectory[this.nodeId].push(...newObjectsIds);
17828
+
17829
+ // This node also advertises that its model has changed to all other nodes (not the newcomer, because it's unnecessary) :
17830
+ const updatemodelObjectsDirectoryRequest={
17831
+ type:"request.add.modelObjectsDirectory",
17832
+ newObjectsIds:newObjectsIds
17833
+ };
17834
+ this.propagateToCluster(updatemodelObjectsDirectoryRequest);
17835
+
17836
+
17837
+ // But we also want all the concerned clients to update their local models : (which are all clients connected to this node)
17838
+ return this.sendObjectsUpdatesToClients(newObjects, true);
17839
+ }
17840
+
17841
+ /*private*/sendObjectsUpdatesToClients(modifiedOrAddedObjects, isAddingObjects=false){
17842
+
17843
+ const self=this;
17844
+ foreach(this.clients, client=>{
17845
+ const clientSocket=client.clientSocket;
17846
+ const objectsModifiedOrAddedClientResponse={
17847
+ nodeId:self.nodeId,
17848
+ type:"response.objectsModifiedOrAdded.client",
17849
+ objectsString:JSON.stringifyDecycle(modifiedOrAddedObjects),
17850
+ isAddingObjects:isAddingObjects,
17851
+ };
17852
+ self.server.send("inputs", objectsModifiedOrAddedClientResponse, null, clientSocket);
17853
+ });
17854
+
17855
+ return modifiedOrAddedObjects;
17856
+ }
17857
+
17858
+
17859
+
17860
+
17861
+ /*private*/getNeighborsNodesIds(){
17862
+ const neighborsNodesIds=[];
17863
+ foreach(this.incomingServers,(incomingServer,nodeId)=>{
17864
+ neighborsNodesIds.push(nodeId);
17865
+ });
17866
+ foreach(this.outcomingServers,(outcomingServer,nodeId)=>{
17867
+ neighborsNodesIds.push(nodeId);
17868
+ });
17869
+ return neighborsNodesIds;
17870
+ }
17871
+
17872
+ /*private*/getLeastOccupiedNeighborNodeId(){
17873
+ const self=this;
17874
+
17875
+ // We send the new objects to the neighbor node with the least amount of objects :
17876
+ let leastOccupiedNodeId=null;
17877
+ foreach(this.getNeighborsNodesIds(), (nodeId)=>{
17878
+ const entry=self.modelObjectsDirectory[nodeId];
17879
+ const numberOfObjects=getArraySize(entry);
17880
+ if(!leastOccupiedNodeId || numberOfObjects<getArraySize(self.modelObjectsDirectory[leastOccupiedNodeId]))
17881
+ leastOccupiedNodeId=nodeId;
17882
+ });
16982
17883
 
17884
+ if(!leastOccupiedNodeId){
17885
+ // TRACE
17886
+ lognow("WARN : Cannot find the least occupied node. Aborting.");
17887
+ }
17888
+ return leastOccupiedNodeId;
17889
+ }
17890
+
17891
+
17892
+ // ****************************************************************************************************
17893
+ /*private*/setPropagation(channelName, requestType, doOnReception=null, responseRequestType=null, propagateToAllCluster=true){
17894
+ const self=this;
17895
+ this.setUniqueReceptionPoint(channelName, requestType, (self, message, server, clientSocket)=>{
16983
17896
 
17897
+ const requestId=message.requestId;
17898
+ if(contains(self.executedRequestIdsHistory, requestId)){
17899
+ // TRACE
17900
+ lognow(`WARN : Request of type ${message.type} already answered. Aborting.`);
17901
+ return;
17902
+ }
17903
+ if(message.visitedNodeIds){
17904
+ if(contains(message.visitedNodeIds, self.nodeId)){
17905
+ return;
17906
+ }
17907
+ message.visitedNodeIds.push(self.nodeId);
17908
+ }
17909
+
17910
+ const hasNoExclusionsOrExclusionDoesNotApplyOnThisNode=(!message.excludedNodesIds || !contains(message.excludedNodesIds,self.nodeId));
17911
+ let response=null;
17912
+ if(!message.destinationNodeId && hasNoExclusionsOrExclusionDoesNotApplyOnThisNode){
17913
+ // Case broadcast message :
17914
+ if(doOnReception)
17915
+ response=doOnReception(self, message, server, clientSocket);
17916
+ pushInArrayAsQueue(self.executedRequestIdsHistory, REQUESTS_IDS_HISTORY_SIZE, requestId);
17917
+ if(propagateToAllCluster) self.sendToOtherNodes(channelName, message);
17918
+ }else{
17919
+ if(message.destinationNodeId===self.nodeId && hasNoExclusionsOrExclusionDoesNotApplyOnThisNode){
17920
+ if(doOnReception)
17921
+ response=doOnReception(self, message, server, clientSocket);
17922
+ pushInArrayAsQueue(self.executedRequestIdsHistory, REQUESTS_IDS_HISTORY_SIZE, requestId);
17923
+ }else{
17924
+ if(propagateToAllCluster) self.sendToOtherNodes(channelName, message);
17925
+ }
17926
+ }
17927
+
17928
+ // We send back the answer to the originating node :
17929
+ if(response!=null && responseRequestType){
17930
+ // TRACE
17931
+ lognow(`(${self.nodeId}) Node sending back a response of type ${responseRequestType}...`);
17932
+
17933
+ const clusterResponse={
17934
+ type:responseRequestType,
17935
+ result:response
17936
+ };
17937
+ const originNodeId=message.nodeId;
17938
+ if(propagateToAllCluster) self.propagateToCluster(clusterResponse, originNodeId);
17939
+ else self.propagateToSingleNeighbor(clusterResponse, originNodeId);
17940
+ }
17941
+
17942
+ });
17943
+ }
17944
+ // ****************************************************************************************************
17945
+
17946
+ /*private*/propagateToCluster(request, destinationNodeId=null, excludedNodesIds=null){
17947
+ request.requestId=getUUID();
17948
+ const originNodeId=this.nodeId;
17949
+ request.nodeId=originNodeId;
17950
+ if(destinationNodeId) request.destinationNodeId=destinationNodeId;
17951
+ if(excludedNodesIds) request.excludedNodesIds=excludedNodesIds;
17952
+ request.visitedNodeIds=[originNodeId];
17953
+ this.sendToOtherNodes("cluster",request);
17954
+ }
17955
+
17956
+ /*private*/propagateToNeighbors(request){
17957
+ request.requestId=getUUID();
17958
+ const originNodeId=this.nodeId;
17959
+ request.nodeId=originNodeId;
17960
+ this.sendToOtherNodes("neightbors", request);
17961
+ }
17962
+ /*private*/propagateToSingleNeighbor(request, destinationNodeId){
17963
+ request.requestId=getUUID();
17964
+ const originNodeId=this.nodeId;
17965
+ request.nodeId=originNodeId;
17966
+ request.destinationNodeId=destinationNodeId;
17967
+ this.sendToOtherSingleNode("neightbors", request, destinationNodeId);
16984
17968
  }
16985
17969
 
17970
+ // ****************************************************************************************************
17971
+
17972
+ /*private*/sendToOtherNodes(channelName, message){
17973
+ let hasBeenSentIncoming=this.sendToIncomingServers(channelName, message);
17974
+ let hasBeenSentOutcoming=this.sendToOutcomingServers(channelName, message);
17975
+ return (hasBeenSentIncoming || hasBeenSentOutcoming);
17976
+ }
17977
+ /*private*/sendToIncomingServers(channelName, message){
17978
+ const self=this;
17979
+ let hasBeenSent=false;
17980
+ foreach(this.incomingServers, (client)=>{
17981
+ const clientSocket=client.clientSocket;
17982
+ self.server.send(channelName, message, null, clientSocket);
17983
+ hasBeenSent=true;
17984
+ });
17985
+ return hasBeenSent;
17986
+ }
17987
+ /*private*/sendToOutcomingServers(channelName, message){
17988
+ let hasBeenSent=false;
17989
+ foreach(this.outcomingServers, (client)=>{
17990
+ const clientInstanceToOtherServer=client.clientInstance;
17991
+ clientInstanceToOtherServer.client.socketToServer.send(channelName, message);
17992
+ hasBeenSent=true;
17993
+ });
17994
+ return hasBeenSent;
17995
+ }
17996
+ /*private*/sendToOtherSingleNode(channelName, request, destinationNodeId){
17997
+ const self=this;
17998
+ let hasBeenSent=false;
17999
+ foreach(this.incomingServers, (client)=>{
18000
+ const clientSocket=client.clientSocket;
18001
+ self.server.send(channelName, message, null, clientSocket);
18002
+ hasBeenSent=true;
18003
+ },(c, nodeId)=>(nodeId===destinationNodeId));
18004
+ if(!hasBeenSent){
18005
+ foreach(this.outcomingServers, (client)=>{
18006
+ const clientInstanceToOtherServer=client.clientInstance;
18007
+ clientInstanceToOtherServer.client.socketToServer.send(channelName, message);
18008
+ hasBeenSent=true;
18009
+ },(c, nodeId)=>(nodeId===destinationNodeId));
18010
+ }
18011
+ return hasBeenSent;
18012
+ }
18013
+
18014
+ /*private*/setUniqueReceptionPoint(channelName, messageType, doOnReception){
18015
+ const self=this;
18016
+ const listeners=this.listeners[channelName];
18017
+ if(foreach(listeners,listener=>{
18018
+ if(listener.messageType===messageType) return true;
18019
+ })) return;
18020
+ listeners.push({
18021
+ messageType:messageType,
18022
+ execute:(self, message, server, clientSocket)=>{
18023
+ doOnReception(self, message, server, clientSocket);
18024
+ }
18025
+ });
18026
+ }
18027
+
18028
+ // ****************************************************************************************************
18029
+
18030
+ /*private*/isInAuthorizedNodes(incomingServerNodeId){
18031
+ return contains(this.authorizedNodesIds,incomingServerNodeId);
18032
+ }
18033
+
18034
+ /*public*/traceNode(){
18035
+ // TRACE
18036
+ lognow("-------------------------------------------------------------");
18037
+ lognow(`(${this.nodeId}) :`,this);
18038
+ lognow("-------------------------------------------------------------");
18039
+ }
18040
+
18041
+
18042
+
16986
18043
  }
16987
18044
 
16988
- getAORTACNode=function(selfOrigin="127.0.0.1:40000",outcomingNodesOrigins=[]){
16989
- return new AORTACNode(selfOrigin,outcomingNodesOrigins);
18045
+
18046
+ getAORTACNode=function(nodeId=getUUID(), selfOrigin="ws://127.0.0.1:40000", outcomingNodesOrigins=[], model, controller){
18047
+ return new AORTACNode(nodeId, selfOrigin, outcomingNodesOrigins, model, controller);
16990
18048
  }
16991
18049
 
16992
18050
 
@@ -16996,6 +18054,7 @@ getAORTACNode=function(selfOrigin="127.0.0.1:40000",outcomingNodesOrigins=[]){
16996
18054
 
16997
18055
 
16998
18056
 
18057
+
16999
18058
  /* INCLUDED EXTERNAL LIBRAIRIES
17000
18059
  */
17001
18060
  "use strict";var sjcl={cipher:{},hash:{},keyexchange:{},mode:{},misc:{},codec:{},exception:{corrupt:function(a){this.toString=function(){return"CORRUPT: "+this.message};this.message=a},invalid:function(a){this.toString=function(){return"INVALID: "+this.message};this.message=a},bug:function(a){this.toString=function(){return"BUG: "+this.message};this.message=a},notReady:function(a){this.toString=function(){return"NOT READY: "+this.message};this.message=a}}};