aotrautils 0.0.1068 → 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.
- aotrautils/aotrautils.build.js +1805 -746
- aotrautils/package.json +1 -1
aotrautils/aotrautils.build.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
|
2
2
|
|
3
|
-
/*utils COMMONS library associated with aotra version : «1_29072022-2359 (
|
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
|
-
|
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
|
-
|
2087
|
-
|
2088
|
-
|
2089
|
-
|
2090
|
-
//}
|
2091
|
-
|
2106
|
+
if(!compareFunction && sortKeys){
|
2107
|
+
keysToIterateOn=keysToIterateOn.sort();
|
2108
|
+
}
|
2109
|
+
|
2092
2110
|
var cnt=0;
|
2093
|
-
for(var key
|
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
|
-
|
2863
|
-
|
2864
|
-
|
2865
|
-
|
2866
|
-
|
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
|
-
|
2873
|
-
|
2874
|
-
|
2875
|
-
|
2876
|
-
|
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 (
|
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
|
-
|
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
|
-
|
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
|
-
|
8994
|
-
|
8995
|
-
|
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
|
-
|
9011
|
-
|
9012
|
-
|
9013
|
-
|
9014
|
-
|
9015
|
-
|
9016
|
-
|
9017
|
-
|
9018
|
-
|
9019
|
-
|
9020
|
-
|
9021
|
-
|
9022
|
-
|
9023
|
-
|
9024
|
-
|
9025
|
-
|
9026
|
-
|
9027
|
-
|
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
|
-
|
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(!
|
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
|
-
|
9130
|
+
class Sound{
|
9131
|
+
constructor(filePath,doOnEnded=null){
|
9132
|
+
|
9133
|
+
this.doOnEnded=doOnEnded;
|
9067
9134
|
|
9068
|
-
|
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
|
-
|
9072
|
-
|
9073
|
-
|
9074
|
-
|
9075
|
-
|
9076
|
-
|
9077
|
-
|
9078
|
-
|
9079
|
-
|
9080
|
-
|
9081
|
-
|
9082
|
-
|
9083
|
-
|
9084
|
-
|
9085
|
-
|
9086
|
-
|
9087
|
-
|
9088
|
-
|
9089
|
-
|
9090
|
-
|
9091
|
-
|
9092
|
-
|
9093
|
-
|
9094
|
-
|
9095
|
-
|
9096
|
-
|
9097
|
-
|
9098
|
-
|
9099
|
-
|
9100
|
-
|
9101
|
-
|
9102
|
-
|
9103
|
-
|
9104
|
-
|
9105
|
-
|
9106
|
-
|
9107
|
-
|
9108
|
-
|
9109
|
-
|
9110
|
-
|
9111
|
-
|
9112
|
-
|
9113
|
-
|
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
|
-
|
9117
|
-
|
9118
|
-
|
9119
|
-
|
9120
|
-
|
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
|
-
|
9125
|
-
|
9126
|
-
|
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
|
-
|
9130
|
-
|
9131
|
-
|
9132
|
-
|
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
|
-
|
9142
|
-
|
9143
|
-
if(
|
9144
|
-
|
9145
|
-
|
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
|
-
|
9148
|
-
|
9258
|
+
this.hasLoopStarted=true;
|
9259
|
+
}
|
9260
|
+
|
9261
|
+
jumpIn(){
|
9262
|
+
this.nativeAudioElement.currentTime=0;
|
9263
|
+
this.play();
|
9264
|
+
}
|
9265
|
+
fadeIn(durationMillis){
|
9149
9266
|
|
9150
|
-
|
9151
|
-
|
9152
|
-
|
9153
|
-
|
9154
|
-
|
9155
|
-
|
9156
|
-
|
9157
|
-
|
9158
|
-
|
9159
|
-
|
9160
|
-
|
9161
|
-
|
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
|
-
|
9166
|
-
|
9167
|
-
|
9168
|
-
|
9169
|
-
|
9170
|
-
|
9171
|
-
|
9172
|
-
|
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
|
-
|
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=
|
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.
|
9313
|
+
self.nativeAudioElement.volume=Math.max(0,
|
9209
9314
|
// and here too :
|
9210
|
-
self.nativeAudioElement.volume
|
9315
|
+
self.nativeAudioElement.volume-volumeStep);
|
9211
9316
|
},FADE_REFRESH_MILLIS);
|
9212
9317
|
|
9213
|
-
|
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
|
-
|
9224
|
-
|
9225
|
-
|
9226
|
-
|
9227
|
-
|
9228
|
-
|
9229
|
-
|
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
|
-
|
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
|
-
|
9274
|
-
|
9275
|
-
|
9276
|
-
|
9277
|
-
|
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
|
-
|
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
|
-
|
9420
|
-
|
9421
|
-
|
9422
|
-
|
9423
|
-
|
9424
|
-
|
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
|
-
|
9495
|
+
this.isClip=(!!clipSize);
|
9496
|
+
this.clipSize=(this.isClip?clipSize:null);
|
9436
9497
|
// width:imagesPool.width,
|
9437
9498
|
// height:imagesPool.height,
|
9438
|
-
index
|
9439
|
-
|
9440
|
-
|
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
|
9444
|
-
refreshMillis
|
9445
|
-
durationTimeFactorHolder
|
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
|
-
//
|
9452
|
-
|
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
|
-
|
9462
|
-
|
9513
|
+
setDurationTimeFactorHolder(durationTimeFactorHolder){
|
9514
|
+
this.durationTimeFactorHolder=durationTimeFactorHolder;
|
9515
|
+
return this;
|
9516
|
+
}
|
9463
9517
|
|
9464
|
-
|
9465
|
-
|
9466
|
-
|
9467
|
-
|
9468
|
-
|
9469
|
-
|
9470
|
-
|
9471
|
-
|
9472
|
-
|
9473
|
-
|
9474
|
-
|
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
|
-
|
9490
|
-
|
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
|
-
|
9511
|
-
|
9512
|
-
|
9513
|
-
|
9514
|
-
|
9515
|
-
|
9516
|
-
|
9517
|
-
|
9518
|
-
|
9519
|
-
|
9520
|
-
|
9521
|
-
|
9522
|
-
|
9523
|
-
|
9524
|
-
|
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
|
-
|
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
|
-
|
9543
|
-
|
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
|
-
|
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
|
-
|
9638
|
+
class MonoThreadedRoutine{
|
9639
|
+
constructor(actuator,refreshMillis=null,startDependsOnParentOnly=false){
|
9659
9640
|
|
9660
|
-
|
9661
|
-
|
9662
|
-
|
9663
|
-
|
9664
|
-
|
9665
|
-
|
9666
|
-
|
9667
|
-
|
9668
|
-
|
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
|
-
|
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
|
-
|
9691
|
-
|
9692
|
-
|
9693
|
-
|
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
|
-
|
9696
|
-
|
9697
|
-
|
9698
|
-
|
9699
|
-
|
9700
|
-
|
9701
|
-
|
9702
|
-
|
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
|
-
|
9717
|
-
|
9718
|
-
|
9719
|
-
|
9720
|
-
|
9721
|
-
|
9722
|
-
|
9723
|
-
|
9724
|
-
|
9725
|
-
|
9726
|
-
|
9727
|
-
|
9728
|
-
|
9729
|
-
|
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
|
-
|
9732
|
-
|
9733
|
-
|
9734
|
-
|
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
|
-
|
9742
|
-
|
9743
|
-
|
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
|
-
|
9746
|
-
|
9747
|
-
|
9748
|
-
|
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
|
-
|
9751
|
-
|
9752
|
-
|
9753
|
-
|
9754
|
-
|
9755
|
-
|
9756
|
-
|
9757
|
-
|
9758
|
-
|
9759
|
-
|
9760
|
-
|
9761
|
-
|
9762
|
-
|
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
|
-
|
9765
|
-
|
9766
|
-
|
9767
|
-
|
9768
|
-
|
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
|
9776
|
-
|
9777
|
-
|
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(
|
9781
|
-
|
9822
|
+
if(this.hasReachedGoal()){
|
9823
|
+
this.stop();
|
9824
|
+
this.value=this.goalValue;
|
9825
|
+
return this.value;
|
9782
9826
|
}
|
9783
9827
|
}
|
9784
9828
|
|
9785
|
-
|
9786
|
-
|
9787
|
-
|
9788
|
-
|
9789
|
-
|
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
|
-
|
9821
|
-
|
9836
|
+
if(this.actuator.doOnEachStep) this.actuator.doOnEachStep(args);
|
9822
9837
|
}
|
9823
|
-
|
9838
|
+
|
9839
|
+
return this.value;
|
9840
|
+
}
|
9824
9841
|
|
9825
|
-
|
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
|
-
|
12803
|
+
// const REFRESHING_RATE_MILLIS_AUDIO:100,
|
12776
12804
|
|
12777
12805
|
|
12778
|
-
|
12779
|
-
|
12806
|
+
// const MICRO_CLIENT_MESS_WITH_ALPHA=true;
|
12807
|
+
// const MICRO_CLIENT_MESS_WITH_ALPHA=false;
|
12780
12808
|
|
12781
12809
|
|
12782
|
-
|
12810
|
+
const REFRESHING_RATE_MILLIS_DEFAULT = 1000;
|
12783
12811
|
|
12784
12812
|
|
12785
|
-
|
12786
|
-
|
12787
|
-
|
12788
|
-
|
12789
|
-
|
12790
|
-
|
12791
|
-
|
12792
|
-
|
12793
|
-
|
12794
|
-
|
12795
|
-
|
12796
|
-
|
12797
|
-
|
12798
|
-
|
12799
|
-
|
12800
|
-
|
12801
|
-
|
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:46:15)»*/
|
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 (
|
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 (
|
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(
|
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(
|
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+"»..."
|
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
|
-
|
15448
|
-
|
15449
|
-
|
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(
|
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
|
-
|
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
|
-
|
16559
|
-
|
16560
|
-
|
16561
|
-
|
16562
|
-
|
16563
|
-
|
16564
|
-
|
16565
|
-
|
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
|
-
|
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
|
16916
|
-
this.controller=controller; // Must implement
|
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
|
-
|
16938
|
-
|
16939
|
-
|
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
|
-
|
16950
|
-
|
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
|
-
|
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
|
-
//
|
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
|
-
|
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
|
-
|
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
|
-
|
16989
|
-
|
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}}};
|