aotrautils 0.0.1842 → 0.0.1847

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
 
2
2
 
3
- /*utils COMMONS library associated with aotra version : «1_29072022-2359 (09/04/2026-01:57:45)»*/
3
+ /*utils COMMONS library associated with aotra version : «1_29072022-2359 (02/06/2026-01:05:39)»*/
4
4
  /*-----------------------------------------------------------------------------*/
5
5
 
6
6
 
@@ -35,7 +35,7 @@ PERFORM_TESTS_ON_LIBRARY=false;
35
35
 
36
36
  const ROOT_UUID="00000000-0000-0000-0000-000000000000";
37
37
  const DEFAULT_UUID_ATTR_NAME="JSONid";/* Yet Another UUID */
38
- const DEFAULT_CLASSNAME_ATTR_NAME="JSONtype";/* Yet Another Classname */
38
+ const DEFAULT_CLASSNAME_ATTR_NAME="JSONtype";/* Yet Another ClassName */
39
39
  const DEFAULT_POINTER_TO_ATTR_NAME="JSONref";/* Yet Another PointerTo */
40
40
 
41
41
  //=========================================================================
@@ -1760,7 +1760,7 @@ function encodeXORNoPattern(strToEncode,key){
1760
1760
 
1761
1761
  // CAUTION : This method is declared this way because it's also used in a nodejs context!
1762
1762
  // TODO : FIXME : DO THIS FOR ALL OF THESE COMMON METHODS AND FUNCTIONS !
1763
- generateRandomString=function(length,/*NULLABLE*/mode=null){
1763
+ generateRandomString=function(length,/*NULLABLE*/mode=null, separator="~>"){
1764
1764
 
1765
1765
  // This list must be very conservative, because we want to be able to pass it through GET URLs !
1766
1766
  // OLD (not enough conservative ?) : const ALLOWED_CHARS="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0912346789_-£¢¤¬²³¼½¾";
@@ -1768,7 +1768,7 @@ generateRandomString=function(length,/*NULLABLE*/mode=null){
1768
1768
 
1769
1769
 
1770
1770
  if(typeof(length)==="string"){
1771
- if(contains(length,"->")){
1771
+ if(contains(length,"~>")){
1772
1772
  length=Math.getRandomInRange(length);
1773
1773
  }else{
1774
1774
  length=64;
@@ -2049,7 +2049,7 @@ window.nothing=function(nullable, insist=false){
2049
2049
  }
2050
2050
 
2051
2051
  window.getOrCreateEmptyAttribute=function(parentObject, attributeNameOrAttributesNames,
2052
- // BECAUSE CAUTION : if we have defaultValue as parameter, sometimes because of the recursive call, it can refer to a persisting object ! Thus, creating a strange bug.
2052
+ // BECAUSE CAUTION : We don't allow to specify a default initialization value as parameter, because if we have defaultValue as parameter, sometimes because of the recursive call, it can refer to a persisting object ! Thus, creating a strange bug.
2053
2053
  // (SO WE ALWAYS NEED TO START AS A BLANK STATE FOR DEFAULT VALUE !)
2054
2054
  forceArrayAttributeDefaultValue=false){
2055
2055
  if(nothing(parentObject)) return null;
@@ -2085,6 +2085,7 @@ window.getOrCreateEmptyAttribute=function(parentObject, attributeNameOrAttribute
2085
2085
  return null; // (case no child was found or it could be not created.)
2086
2086
  }
2087
2087
 
2088
+ // ( Equivalent to syntax: value??defaultValue )
2088
2089
  window.nonull=function(value,defaultValue){
2089
2090
  if(value===false) return value;
2090
2091
  if(nothing(value)) return defaultValue;
@@ -2153,7 +2154,7 @@ window.pushInArrayAsQueue=function(array, maxSize, item){
2153
2154
  /*KNOWN LIMITATIONS : You will have an incomplete browsing loop if there are the string values "break" or "continue" in your array or associative array !*/
2154
2155
  // (Alternatively, you can use this instead : for (const key of Object.keys(obj)) { const item=obj[key]; ... })
2155
2156
  // CAUTION : Only use «return foreach» syntax in client code loops with SINGLE-LEVEL nested loops ONLY !
2156
- window.foreach=function(arrayOfValues,doOnEachFunction,
2157
+ window.foreach=function(arrayOfValues, doOnEachFunction,
2157
2158
  /*OPTIONAL*/filterFunction=null,
2158
2159
  // CAUTION ! Javascript sort compare function result is the same as in Java :
2159
2160
  // 0: if(a==b)
@@ -3693,7 +3694,7 @@ Math.coerceInRange=window.aotest({
3693
3694
  }, !PERFORM_TESTS_ON_LIBRARY);
3694
3695
 
3695
3696
 
3696
- Math.getMinMax=function(strMinMaxRange, separator="->"){
3697
+ Math.getMinMax=function(strMinMaxRange, separator="¬>"){
3697
3698
  if(strMinMaxRange && isString(strMinMaxRange) && contains(strMinMaxRange,separator)){
3698
3699
  let splits=strMinMaxRange.split(separator);
3699
3700
  let min=parseInt(splits[0]);
@@ -3704,7 +3705,7 @@ Math.getMinMax=function(strMinMaxRange, separator="->"){
3704
3705
  }
3705
3706
 
3706
3707
 
3707
- Math.isInMinMax=function(minMaxRange,value,mode="][",separator="->"){
3708
+ Math.isInMinMax=function(minMaxRange,value,mode="][",separator="¬>"){
3708
3709
  let minMax=null;
3709
3710
 
3710
3711
  if(minMaxRange){
@@ -3727,7 +3728,7 @@ Math.isInMinMax=function(minMaxRange,value,mode="][",separator="->"){
3727
3728
  }
3728
3729
 
3729
3730
 
3730
- Math.getRandomInRange=function(str, separator="->"){
3731
+ Math.getRandomInRange=function(str, separator="~>"){
3731
3732
  if(str && isString(str) && contains(str,separator)){
3732
3733
  let splits=Math.getMinMax(str,separator);
3733
3734
  let minValue=splits.min;
@@ -3912,7 +3913,10 @@ window.isBoolean=function(b){
3912
3913
  return typeof b === "boolean";
3913
3914
  }
3914
3915
  window.isFunction=function(functionToCheck){
3915
- return (functionToCheck && {}.toString.call(functionToCheck) === "[object Function]");
3916
+ return (functionToCheck && (isAsyncFunction(functionToCheck) || {}.toString.call(functionToCheck) === "[object Function]"));
3917
+ }
3918
+ window.isAsyncFunction=function(functionToCheck){
3919
+ return (functionToCheck && functionToCheck.constructor && functionToCheck.constructor.name==="AsyncFunction");
3916
3920
  }
3917
3921
  window.isPrimitive=function(p){
3918
3922
  return (isNumber(p) || isString(p) || isBoolean(p));
@@ -3920,6 +3924,8 @@ window.isPrimitive=function(p){
3920
3924
  window.isObject=function(o){ // (includes also plain objects)
3921
3925
  return !isFunction(o) && !isPrimitive(o);
3922
3926
  }
3927
+
3928
+
3923
3929
  // NO : DOES NOT WORK !!!
3924
3930
  //window.isPlainObject=function(o){
3925
3931
  // return Object.prototype.toString.call(o)==="[object Object]";
@@ -3928,9 +3934,19 @@ window.isClassObject=function(o){
3928
3934
  return isObject(o) && !isNativeClass(o);
3929
3935
  }
3930
3936
  window.getClassName=function(obj){
3931
- if(!obj) return null;
3937
+ if((typeof obj === "undefined") || obj==null) return null;
3932
3938
  if(!obj.constructor) return null;
3933
- return obj.constructor.name;
3939
+ //if(!empty(Object.getPrototypeOf(obj).constructor.name))
3940
+ // return Object.getPrototypeOf(obj).constructor.name;
3941
+ if(!empty(obj.constructor.name)){
3942
+ return obj.constructor.name;
3943
+ }else{
3944
+ //TRACE
3945
+ lognow("WARN : Object is an instance of an anonymous class ! Cannot infer its class name ! Please fix its class definition ! obj:", obj);
3946
+ }
3947
+ // WORKAROUND
3948
+ if(!empty(obj[CLASSNAME_ATTRIBUTE_NAME])) return obj[CLASSNAME_ATTRIBUTE_NAME];
3949
+ return Object.prototype.toString.call(obj).match(/^\[object\s(.*)\]$/)[1];
3934
3950
  }
3935
3951
  window.isNativeClass=function(obj){
3936
3952
  let className=getClassName(obj);
@@ -4097,8 +4113,12 @@ getUUID=function(mode=null){
4097
4113
  }
4098
4114
 
4099
4115
 
4116
+
4117
+ window.CLASSNAME_ATTRIBUTE_NAME="__className";
4118
+
4119
+
4100
4120
  // JSON
4101
- window.instanciate=function(className=null){
4121
+ window.instantiate=function(className=null){
4102
4122
  let newObj={};
4103
4123
 
4104
4124
  if(!className || !isString(className)) return newObj;
@@ -4116,7 +4136,9 @@ window.instanciate=function(className=null){
4116
4136
  // If class name is a simple array, a simple «[]» is enough :
4117
4137
  if(className==="Array") return [];
4118
4138
 
4139
+
4119
4140
 
4141
+
4120
4142
  let classInWindow=globalThis[className];
4121
4143
  if(typeof(classInWindow)=="undefined"){
4122
4144
  // TRACE
@@ -4130,20 +4152,26 @@ window.instanciate=function(className=null){
4130
4152
  classInWindow=global[className];
4131
4153
  if(!classInWindow){
4132
4154
  // TRACE
4133
- lognow(`ERROR : CAUTION : «${className}» class does not seem to be registered in the global object. Cannot instanciate it.`);
4155
+ lognow(`ERROR : CAUTION : «${className}» class does not seem to be registered in the global object. Cannot instantiate it.`);
4134
4156
 
4157
+ // DEBUG
4135
4158
  // TODO : FIXME : I don't like that at all, to use eval(...), but on today I know of no other solution... :
4136
4159
  newObj=eval("new "+className+"();");
4137
4160
 
4138
4161
  }
4139
4162
  }else{
4140
- // TRACE
4141
- lognow(`ERROR : CAUTION : «${className}» class does not seem to be accessible in the globalThis nor window object, and global object does not exist. Cannot instanciate it.`);
4163
+ // TRACEfireNewPuff()
4164
+ lognow(`ERROR : CAUTION : «${className}» class does not seem to be accessible in the globalThis nor window object, and global object does not exist. Cannot instantiate it.`);
4142
4165
 
4166
+ // DEBUG
4143
4167
  // TODO : FIXME : I don't like that at all, to use eval(...), but on today I know of no other solution... :
4144
4168
  newObj=eval("new "+className+"();");
4145
4169
 
4146
4170
  }
4171
+
4172
+ // WORKAROUND
4173
+ newObj[CLASSNAME_ATTRIBUTE_NAME]=className;
4174
+
4147
4175
  return newObj;
4148
4176
  }
4149
4177
  }
@@ -4154,21 +4182,21 @@ window.instanciate=function(className=null){
4154
4182
  // newObj=Reflect.construct(classInWindow);
4155
4183
  // }catch(e1){
4156
4184
  //// // TRACE
4157
- //// console.log("WARN : Could not instanciate class «"+className+"». (Maybe class is undefined or default constructor doesn't exist !)");
4185
+ //// console.log("WARN : Could not instantiate class «"+className+"». (Maybe class is undefined or default constructor doesn't exist !)");
4158
4186
  // // TODO : FIXME : I don't like that at all, to use eval(...), but on today I know of no other solution... :
4159
4187
  // newObj=eval("new "+className+"();");
4160
4188
  // }
4161
4189
  // }catch(e2){
4162
4190
  //// // TRACE
4163
- //// console.log("ERROR : Could not instanciate class «"+className+"» with eval. (Maybe class is undefined or default constructor doesn't exist !)");
4191
+ //// console.log("ERROR : Could not instantiate class «"+className+"» with eval. (Maybe class is undefined or default constructor doesn't exist !)");
4164
4192
  // try{
4165
4193
  //// // TODO : FIXME : I don't like that at all, to use eval(...), but on today I know of no other solution... :
4166
4194
  //// newObj=eval("new window."+className+"();");
4167
4195
  // newObj=Reflect.construct(classInWindow);
4168
4196
  // }catch(e3){
4169
4197
  //// // TRACE
4170
- //// console.log("ERROR : Could not instanciate class «"+className+"» with eval and «window.». (Maybe class is undefined or default constructor doesn't exist !)");
4171
- //// console.log("ERROR : Could not instanciate class «"+className+"» with Reflect and «window.». (Maybe class is undefined or default constructor doesn't exist !)");
4198
+ //// console.log("ERROR : Could not instantiate class «"+className+"» with eval and «window.». (Maybe class is undefined or default constructor doesn't exist !)");
4199
+ //// console.log("ERROR : Could not instantiate class «"+className+"» with Reflect and «window.». (Maybe class is undefined or default constructor doesn't exist !)");
4172
4200
  // // TODO : FIXME : This is the most performance-costing fallback :
4173
4201
  // try{
4174
4202
  // if(classInWindow){
@@ -4180,7 +4208,7 @@ window.instanciate=function(className=null){
4180
4208
  // }
4181
4209
  // }catch(e4){
4182
4210
  //// // TRACE
4183
- //// console.log("ERROR : Could not instanciate class «"+className+"» with prototype affectation.");
4211
+ //// console.log("ERROR : Could not instantiate class «"+className+"» with prototype affectation.");
4184
4212
  //// console.log("ERROR : Returning plain object since all instantiation methods have failed for class «"+className+"».");
4185
4213
  // }
4186
4214
  // }
@@ -4188,17 +4216,17 @@ window.instanciate=function(className=null){
4188
4216
 
4189
4217
  // NEW :
4190
4218
  newObj=new classInWindow();
4219
+
4220
+ // WORKAROUND
4221
+ newObj[CLASSNAME_ATTRIBUTE_NAME]=className;
4222
+
4191
4223
  return newObj;
4192
4224
  };
4193
4225
 
4194
4226
 
4195
4227
 
4196
-
4197
-
4198
-
4199
-
4200
4228
  JSON.stringifyDecycle=function(obj){
4201
- let decycled=JSON.decycle(obj,null,"$className");
4229
+ let decycled=JSON.decycle(obj,null,CLASSNAME_ATTRIBUTE_NAME);
4202
4230
  // CANNOT USE stringifyObject(...) function because we are in a common, lower-level library !
4203
4231
  return stringifyObject(decycled);
4204
4232
  }
@@ -4206,6 +4234,8 @@ JSON.stringifyDecycle=function(obj){
4206
4234
 
4207
4235
  JSON.parseRecycle=function(str){
4208
4236
 
4237
+ const attributeClassName=CLASSNAME_ATTRIBUTE_NAME;
4238
+
4209
4239
  const parsedDecycled=parseJSON(str);
4210
4240
  let restoreClass=function(objParam){
4211
4241
 
@@ -4215,11 +4245,11 @@ JSON.parseRecycle=function(str){
4215
4245
  if(isArray(objParam)){
4216
4246
  newObj=[];
4217
4247
  }else{
4218
- let className=objParam["$className"];
4248
+ let className=objParam[attributeClassName];
4219
4249
  if(!className || isNativeClassName(className)){
4220
4250
  newObj= {};
4221
4251
  }else{
4222
- newObj=instanciate(className);
4252
+ newObj=instantiate(className);
4223
4253
  }
4224
4254
  }
4225
4255
 
@@ -4249,8 +4279,11 @@ JSON.parseRecycle=function(str){
4249
4279
  ,(itemOrAttr)=>{ return !isFunction(itemOrAttr); }
4250
4280
  );
4251
4281
 
4252
- if(destination["$className"]){
4253
- delete destination["$className"];
4282
+ // However we have to keep the class information attribute for further house !
4283
+ if(destination[attributeClassName]
4284
+ //...except if the object is a simple object :
4285
+ && destination[attributeClassName]=="Object"){
4286
+ delete destination[attributeClassName];
4254
4287
  }
4255
4288
 
4256
4289
  };
@@ -4268,6 +4301,7 @@ JSON.parseRecycle=function(str){
4268
4301
 
4269
4302
 
4270
4303
  window.clone=function(obj){
4304
+ if(obj==null) return null;
4271
4305
  // OLD :
4272
4306
  const str=JSON.stringifyDecycle(obj);
4273
4307
  const newObj=JSON.parseRecycle(str);
@@ -4740,7 +4774,7 @@ window.isFlatMap=function(obj){
4740
4774
 
4741
4775
  window.getAsFlatStructure=function(rawObject
4742
4776
  ,UUID_ATTR_NAME=DEFAULT_UUID_ATTR_NAME/* Yet Another UUID */
4743
- ,CLASSNAME_ATTR_NAME=DEFAULT_CLASSNAME_ATTR_NAME/* Yet Another Classname */
4777
+ ,CLASSNAME_ATTR_NAME=DEFAULT_CLASSNAME_ATTR_NAME/* Yet Another ClassName */
4744
4778
  ,POINTER_TO_ATTR_NAME=DEFAULT_POINTER_TO_ATTR_NAME/* Yet Another PointerTo */
4745
4779
  ){
4746
4780
  // Flattening :
@@ -4791,7 +4825,7 @@ function flattenGraph(root, UUID_ATTR_NAME, CLASSNAME_ATTR_NAME, POINTER_TO_ATTR
4791
4825
 
4792
4826
  window.getAsTreeStructure=function(oldMap, keepClassName=false
4793
4827
  ,UUID_ATTR_NAME=DEFAULT_UUID_ATTR_NAME/* Yet Another UUID */
4794
- ,CLASSNAME_ATTR_NAME=DEFAULT_CLASSNAME_ATTR_NAME/* Yet Another Classname */
4828
+ ,CLASSNAME_ATTR_NAME=DEFAULT_CLASSNAME_ATTR_NAME/* Yet Another ClassName */
4795
4829
  ,POINTER_TO_ATTR_NAME=DEFAULT_POINTER_TO_ATTR_NAME/* Yet Another PointerTo */
4796
4830
  ){
4797
4831
 
@@ -4813,7 +4847,7 @@ window.restoreGraph=(flatData, keepClassName=false, UUID_ATTR_NAME, CLASSNAME_AT
4813
4847
 
4814
4848
  const objData = flatData[id];
4815
4849
  const className = objData[CLASSNAME_ATTR_NAME];
4816
- const newInstance = instanciate(className);
4850
+ const newInstance = instantiate(className);
4817
4851
 
4818
4852
  restoredObjects[id]=newInstance; // Assign ID early to handle circular references
4819
4853
 
@@ -5178,7 +5212,7 @@ window.getMonoThreadedTimeout=function(actuator=null){
5178
5212
 
5179
5213
  // ======================== Routine ========================
5180
5214
 
5181
- window.MonoThreadedRoutine=class {
5215
+ window.MonoThreadedRoutine=class MonoThreadedRoutine{
5182
5216
  constructor(actuator,refreshMillis=null,startDependsOnParentOnly=false){
5183
5217
 
5184
5218
  this.actuator=actuator;
@@ -5188,15 +5222,21 @@ window.MonoThreadedRoutine=class {
5188
5222
  this.time=getNow();
5189
5223
  this.refreshMillis=refreshMillis;
5190
5224
 
5191
- this.durationTimeFactorHolder={durationTimeFactor:1};
5225
+ this.managedTimeFactor=1;
5192
5226
  // Three-states : started, paused, stopped.
5193
5227
 
5194
5228
  }
5195
5229
 
5196
- setDurationTimeFactorHolder(durationTimeFactorHolder){
5197
- this.durationTimeFactorHolder=durationTimeFactorHolder;
5230
+ registerToTimeFactorManager(timeFactorManager){
5231
+ timeFactorManager.registerTimeFactorManagee(this);
5232
+ return this;
5233
+ }
5234
+
5235
+ changeManagedTimeFactor(newManagedTimeFactor){
5236
+ this.managedTimeFactor=newManagedTimeFactor;
5198
5237
  return this;
5199
5238
  }
5239
+
5200
5240
  isStarted(){
5201
5241
  return this.started || this.startDependsOnParentOnly;
5202
5242
  }
@@ -5233,7 +5273,7 @@ window.MonoThreadedRoutine=class {
5233
5273
  if(!this.isStarted() || this.paused) return;
5234
5274
  // Looping index with a delay :
5235
5275
 
5236
- const delayHasPassed=hasDelayPassed(this.time, this.refreshMillis*this.durationTimeFactorHolder.getDurationTimeFactor());
5276
+ const delayHasPassed=hasDelayPassed(this.time, this.refreshMillis*this.managedTimeFactor);
5237
5277
  if(!this.refreshMillis || delayHasPassed){
5238
5278
  if(this.refreshMillis && delayHasPassed){
5239
5279
  this.time=getNow();
@@ -5243,7 +5283,8 @@ window.MonoThreadedRoutine=class {
5243
5283
  this.stop(args);
5244
5284
  return;
5245
5285
  }
5246
- if(this.actuator.doOnEachStep) this.actuator.doOnEachStep(args);
5286
+ if(this.actuator.doOnEachStep)
5287
+ this.actuator.doOnEachStep(args);
5247
5288
  }
5248
5289
 
5249
5290
  }
@@ -5272,19 +5313,27 @@ window.MonoThreadedGoToGoal=class {
5272
5313
  this.time=getNow();
5273
5314
  this.refreshMillis=refreshMillis;
5274
5315
 
5275
- this.durationTimeFactorHolder={durationTimeFactor:1};
5316
+ this.managedTimeFactor=1;
5317
+
5276
5318
 
5277
5319
  // Three-states : started, paused, stopped.
5278
5320
  }
5279
- setDurationTimeFactorHolder(durationTimeFactorHolder){
5280
- this.durationTimeFactorHolder=durationTimeFactorHolder;
5321
+
5322
+ registerToTimeFactorManager(timeFactorManager){
5323
+ timeFactorManager.registerTimeFactorManagee(this);
5324
+ return this;
5325
+ }
5326
+
5327
+ changeManagedTimeFactor(newManagedTimeFactor){
5328
+ this.managedTimeFactor=newManagedTimeFactor;
5281
5329
 
5282
5330
  // We recalculate total time-dependent values :
5283
- this.totalTimeMillis=this.totalTimeMillis*this.durationTimeFactorHolder.getDurationTimeFactor();
5331
+ this.totalTimeMillis=this.totalTimeMillis*this.managedTimeFactor;
5284
5332
  this.stepValue=((this.refreshMillis*(this.goalValue-this.actualValue))/this.totalTimeMillis);
5285
5333
 
5286
5334
  return this;
5287
5335
  }
5336
+
5288
5337
  isStarted(){
5289
5338
  return this.started || this.startDependsOnParentOnly;
5290
5339
  }
@@ -5296,7 +5345,7 @@ window.MonoThreadedGoToGoal=class {
5296
5345
  setNewGoal(goalValue, refreshMillisParam=null, totalTimeMillis=null){
5297
5346
  if(Math.round(this.value)===Math.round(goalValue)) return;
5298
5347
  if(refreshMillisParam) this.refreshMillis=refreshMillisParam;
5299
- if(totalTimeMillis) this.totalTimeMillis=totalTimeMillis*this.durationTimeFactorHolder.getDurationTimeFactor();
5348
+ if(totalTimeMillis) this.totalTimeMillis=totalTimeMillis*this.managedTimeFactor;
5300
5349
 
5301
5350
  this.goalValue=goalValue;
5302
5351
  this.stepValue=((this.refreshMillis*(this.goalValue-this.value))/this.totalTimeMillis);
@@ -5383,7 +5432,7 @@ window.MonoThreadedGoToGoal=class {
5383
5432
  }
5384
5433
 
5385
5434
  hasDelayPassed(){
5386
- return (!this.time || hasDelayPassed(this.time, this.refreshMillis*this.durationTimeFactorHolder.getDurationTimeFactor()));
5435
+ return (!this.time || hasDelayPassed(this.time, this.refreshMillis*this.managedTimeFactor));
5387
5436
  }
5388
5437
 
5389
5438
 
@@ -5409,7 +5458,7 @@ AOTRAUTILS_LIB_IS_LOADED=true;
5409
5458
 
5410
5459
 
5411
5460
 
5412
- /*utils CLIENT library associated with aotra version : «1_29072022-2359 (09/04/2026-01:57:45)»*/
5461
+ /*utils CLIENT library associated with aotra version : «1_29072022-2359 (02/06/2026-01:05:39)»*/
5413
5462
  /*-----------------------------------------------------------------------------*/
5414
5463
  /* ## Utility global methods in a browser (htmljs) client environment.
5415
5464
  *
@@ -6124,7 +6173,7 @@ function getTextWordsExtract(textStrParam, wordsNumber,/* OPTIONAL */wordPositio
6124
6173
 
6125
6174
  //==================== CLIENT Geometry ====================
6126
6175
 
6127
- Math.isInScreen=function(coords,size=null,center=null,totalWidth,totalHeight,camera=null){
6176
+ Math.isInScreen=function(coords, size=null, center=null, totalWidth, totalHeight, camera=null){
6128
6177
 
6129
6178
  if(center==null) center={x:"center",y:"center"};
6130
6179
 
@@ -6148,8 +6197,6 @@ Math.isInScreen=function(coords,size=null,center=null,totalWidth,totalHeight,cam
6148
6197
  }
6149
6198
 
6150
6199
 
6151
-
6152
-
6153
6200
  let isInScreenX=false;
6154
6201
  {
6155
6202
  let min;
@@ -8328,7 +8375,7 @@ function filterPoints(allPoints ,ctx/*DBG*/){
8328
8375
 
8329
8376
  // This temporary, off-screen video is always needed to get the image data for further use :
8330
8377
  videoTag=document.createElement("video");
8331
- videoTag.id="tmpVideoElement";
8378
+ videoTag.id="tmpVideoElement"+getUUID("short");
8332
8379
  videoTag.autoplay=true;
8333
8380
  videoTag.width=canvasTag.width;
8334
8381
  videoTag.height=canvasTag.height;
@@ -8668,12 +8715,6 @@ drawVideoImage=function(canvas,videoImage,
8668
8715
  var x=nonull(xParam,0);
8669
8716
  var y=nonull(yParam,0);
8670
8717
 
8671
- // // DOES NOT WORK WELL :
8672
- // if(Object.getPrototypeOf(videoImage).constructor.name==="VideoFrame"){
8673
- // ctx.drawImage(videoImage, x, y, newWidthParam, newHeightParam);
8674
- // return;
8675
- // }
8676
-
8677
8718
  var oldWidth=videoImage.width;
8678
8719
  var oldHeight=videoImage.height;
8679
8720
 
@@ -9507,14 +9548,18 @@ class SpriteMonoThreaded{
9507
9548
  // Time measurement :
9508
9549
  this.time=getNow();
9509
9550
  this.refreshMillis=refreshMillis;
9510
- this.durationTimeFactorHolder={durationTimeFactor:1};
9511
9551
 
9552
+ this.managedTimeFactor=1;
9512
9553
  // Two-states : started, stopped.
9513
-
9554
+ }
9555
+
9556
+ registerToTimeFactorManager(timeFactorManager){
9557
+ timeFactorManager.registerTimeFactorManagee(this);
9558
+ return this;
9514
9559
  }
9515
9560
 
9516
- setDurationTimeFactorHolder(durationTimeFactorHolder){
9517
- this.durationTimeFactorHolder=durationTimeFactorHolder;
9561
+ changeManagedTimeFactor(newManagedTimeFactor){
9562
+ this.managedTimeFactor=newManagedTimeFactor;
9518
9563
  return this;
9519
9564
  }
9520
9565
 
@@ -9584,7 +9629,7 @@ class SpriteMonoThreaded{
9584
9629
  }
9585
9630
 
9586
9631
  hasDelayPassed(){
9587
- return (!this.time || hasDelayPassed(this.time, this.refreshMillis*this.durationTimeFactorHolder.getDurationTimeFactor()));
9632
+ return (!this.time || hasDelayPassed(this.time, this.refreshMillis*this.managedTimeFactor));
9588
9633
  }
9589
9634
 
9590
9635
  }
@@ -13235,7 +13280,7 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
13235
13280
 
13236
13281
 
13237
13282
 
13238
- /*utils GEOMETRY library associated with aotra version : «1_29072022-2359 (09/04/2026-01:57:45)»*/
13283
+ /*utils GEOMETRY library associated with aotra version : «1_29072022-2359 (02/06/2026-01:05:39)»*/
13239
13284
  /*-----------------------------------------------------------------------------*/
13240
13285
 
13241
13286
 
@@ -13990,171 +14035,6 @@ window.getSunShadowProjectedPoint=function(originalPoint,angleDegreesParam,groun
13990
14035
  }
13991
14036
 
13992
14037
 
13993
- window.isInZone=function(point, zone, center={x:"center",y:"center",z:"center"}, zoneOffsets=null, invertYAxis=false, zooms=null){
13994
-
13995
- if(!center){
13996
- center={x:"center",y:"center",z:"center"}
13997
- }
13998
- if(invertYAxis==null){
13999
- invertYAxis=false;
14000
- }
14001
- if(zooms){
14002
- point=getZoomedPosition(point, zooms);
14003
- zone=getZoomedZone(zone, zooms);
14004
- }
14005
-
14006
- let pointX=point.x;
14007
- let pointY=point.y;
14008
- let pointZ=point.z;
14009
-
14010
- let zoneX=zone.x;
14011
- let zoneY=zone.y;
14012
- let zoneZ=zone.z;
14013
- if(zoneOffsets){
14014
- if(zoneX!=null) zoneX+=nonull(zoneOffsets.x,0);
14015
- if(zoneY!=null) zoneY+=nonull(zoneOffsets.y,0);
14016
- if(zoneZ!=null) zoneZ+=nonull(zoneOffsets.z,0);
14017
- }
14018
-
14019
- // We ignore zone parameters if they are null or 0 :
14020
- let isXInZone=zone.w?false:true;
14021
- let isYInZone=zone.h?false:true;
14022
- let isZInZone=zone.d?false:true;
14023
-
14024
-
14025
- if(zone.w || zone.h || zone.d){
14026
-
14027
- if(zone.w){
14028
- if(pointX!=null && zoneX!=null){
14029
- if(center.x==="center"){
14030
- let demiSize=Math.floor(zone.w*.5);
14031
- if((zoneX - demiSize) <= pointX && pointX <= (zoneX + demiSize) )
14032
- isXInZone=true;
14033
- }else if(center.x==="right"){
14034
- if((zoneX - zone.w) <= pointX && pointX <= zoneX )
14035
- isXInZone=true;
14036
- }else if(center.x==="left"){
14037
- if(zoneX <= pointX && pointX <= (zoneX + zone.w) )
14038
- isXInZone=true;
14039
- }
14040
- }else{
14041
- isXInZone=true;
14042
- }
14043
- }
14044
-
14045
- if(zone.h){
14046
- if(pointY!=null && zoneY!=null){
14047
- if(center.y==="center"){
14048
- let demiSize=Math.floor(zone.h*.5);
14049
- if((zoneY - demiSize) <= pointY && pointY <= (zoneY + demiSize) )
14050
- isYInZone=true;
14051
- }else if(center.y==="top"){
14052
- if(!invertYAxis){
14053
- if((zoneY - zone.h) <= pointY && pointY <= zoneY )
14054
- isYInZone=true;
14055
- }else{
14056
- if(zoneY <= pointY && pointY <= (zoneY + zone.h) )
14057
- isYInZone=true;
14058
- }
14059
- }else if(center.y==="bottom"){
14060
- if(!invertYAxis){
14061
- if(zoneY <= pointY && pointY <= (zoneY + zone.h) )
14062
- isYInZone=true;
14063
- }else{
14064
- if((zoneY - zone.h) <= pointY && pointY <= zoneY )
14065
- isYInZone=true;
14066
- }
14067
- }
14068
- }else{
14069
- isYInZone=true;
14070
- }
14071
- }
14072
-
14073
- if(zone.d){
14074
- if(pointZ!=null && zoneZ!=null){
14075
- if(center.z==="center"){
14076
- let demiSize=Math.floor(zone.d*.5);
14077
- if((zoneZ - demiSize) <= pointZ && pointZ <= (zoneZ + demiSize) )
14078
- isZInZone=true;
14079
- }else if(center.z==="front"){
14080
- if((zoneZ - zone.d) <= pointZ && pointZ <= zoneZ )
14081
- isZInZone=true;
14082
- }else if(center.z==="back"){
14083
- if(zoneZ <= pointZ && pointZ <= (zoneZ + zone.d) )
14084
- isZInZone=true;
14085
- }
14086
- }else{
14087
- isZInZone=true;
14088
- }
14089
- }
14090
-
14091
-
14092
- }else{
14093
-
14094
- if(pointX!=null && zoneX!=null){
14095
- if(center.x==="center"){
14096
- let demiW=Math.floor(zone.w*.5);
14097
- if((zoneX - demiW) <= pointX && pointX <= (zoneX + demiW))
14098
- isXInZone=true;
14099
- }else if(center.x==="right"){
14100
- if((zoneX - zone.w) <= pointX && pointX <= zoneX )
14101
- isXInZone=true;
14102
- }else if(center.x==="left"){
14103
- if( zoneX <= pointX && pointX <= (zoneX + zone.w ) )
14104
- isXInZone=true;
14105
- }
14106
- }else{
14107
- isXInZone=true;
14108
- }
14109
-
14110
- if(pointY!=null && zoneY!=null){
14111
- if(center.y==="center"){
14112
- let demiH=Math.floor(zone.h*.5);
14113
- if((zoneY - demiH) <= pointY && pointY <= (zoneY + demiH))
14114
- isYInZone=true;
14115
- }else if(center.y==="top"){
14116
- if(!invertYAxis){
14117
- if((zoneY - zone.h) <= pointY && pointY <= zoneY )
14118
- isYInZone=true;
14119
- }else{
14120
- if( zoneY <= pointY && pointY <= (zoneY + zone.h ) )
14121
- isYInZone=true;
14122
- }
14123
- }else if(center.y==="bottom"){
14124
- if(!invertYAxis){
14125
- if( zoneY <= pointY && pointY <= (zoneY + zone.h ) )
14126
- isYInZone=true;
14127
- }else{
14128
- if((zoneY - zone.h) <= pointY && pointY <= zoneY )
14129
- isYInZone=true;
14130
- }
14131
- }
14132
- }else{
14133
- isYInZone=true;
14134
- }
14135
-
14136
- if(pointZ!=null && zoneZ!=null){
14137
- if(center.z==="center"){
14138
- let demiW=Math.floor(zone.d*.5);
14139
- if((zoneZ - demiW) <= pointZ && pointZ <= (zoneZ + demiW))
14140
- isZInZone=true;
14141
- }else if(center.z==="front"){
14142
- if((zoneZ - zone.d) <= pointZ && pointZ <= zoneZ )
14143
- isZInZone=true;
14144
- }else if(center.z==="back"){
14145
- if( zoneZ <= pointZ && pointZ <= (zoneZ + zone.d ) )
14146
- isZInZone=true;
14147
- }
14148
- }else{
14149
- isZInZone=true;
14150
- }
14151
-
14152
-
14153
- }
14154
-
14155
- return isXInZone && isYInZone && isZInZone;
14156
- }
14157
-
14158
14038
 
14159
14039
  Math.getQuadrant=function(centerPoint,point){
14160
14040
  let deltaX=point.x-centerPoint.x;
@@ -14291,85 +14171,6 @@ Math.getBidimensional8Direction=(currentPosition,destination,invertYAxis=false)=
14291
14171
 
14292
14172
 
14293
14173
 
14294
- /*public static*/window.getRandomPositionInZone=function(positionZone, xOffset=0, yOffset=0, zOffset=0, avoidOverlap=null, size=null, randomizeAngles=false){
14295
-
14296
- let w=nonull(positionZone.w,0);
14297
- let h=nonull(positionZone.h,0);
14298
- let d=nonull(positionZone.d,0);
14299
-
14300
- let demiW=w*.5;
14301
- let demiH=h*.5;
14302
- let demiD=d*.5;
14303
-
14304
- let xOrZero=nonull(positionZone.x,0);
14305
- let yOrZero=nonull(positionZone.y,0);
14306
- let zOrZero=nonull(positionZone.z,0);
14307
-
14308
- // If respectively width or height is 0, then there is no need to use a randomization !
14309
- let x= ( w<=0 ? xOrZero : (xOrZero + Math.getRandomInt(Math.floor(demiW), -Math.ceil(demiW)))) + xOffset;
14310
- let y= ( h<=0 ? yOrZero : (yOrZero + Math.getRandomInt(Math.floor(demiH), -Math.ceil(demiH)))) + yOffset;
14311
- let z= ( d<=0 ? zOrZero : (zOrZero + Math.getRandomInt(Math.floor(demiD), -Math.ceil(demiD)))) + zOffset;
14312
-
14313
- if(avoidOverlap && size){
14314
-
14315
- let itemW=nonull(size.w,0);
14316
- let itemH=nonull(size.h,0);
14317
- let itemD=nonull(size.d,0);
14318
-
14319
- if(xOrZero/*includes case==0*/ && 0<itemW && contains(avoidOverlap,"x")) x=Math.floor(x/itemW)*itemW;
14320
- if(yOrZero/*includes case==0*/ && 0<itemH && contains(avoidOverlap,"y")) y=Math.floor(y/itemH)*itemH;
14321
- if(zOrZero/*includes case==0*/ && 0<itemD && contains(avoidOverlap,"z")) z=Math.floor(z/itemD)*itemD;
14322
-
14323
- }
14324
-
14325
- let b=positionZone.b;
14326
- let g=positionZone.g;
14327
- let a=positionZone.a;
14328
- if(randomizeAngles){
14329
- b=Math.random()*Math.PI*2;
14330
- g=Math.random()*Math.PI*2;
14331
- a=Math.random()*Math.PI*2;
14332
- }
14333
-
14334
- return {x:x, y:y, z:z, b:b, g:g, a:a, parallax:positionZone.parallax};
14335
- }
14336
-
14337
-
14338
- // CAUTION : ONLY HANDLES «center-positionned» ZONES !
14339
- /*public static*/window.isZoneCollidingWithZone=function(zone1, zone2){
14340
-
14341
- let demiWidth1=zone1.w*.5;
14342
- let demiWidth2=zone2.w*.5;
14343
-
14344
- let xMin1=zone1.x-demiWidth1;
14345
- let xMax1=zone1.x+demiWidth1;
14346
- let xMin2=zone2.x-demiWidth2;
14347
- let xMax2=zone2.x+demiWidth2;
14348
-
14349
- //OLD : let widthsOverlap=((xMin2<xMin1 && xMin1<xMax2) || (xMin2<xMax1 && xMax1<xMax2)
14350
- // || (xMin1<xMin2 && xMin2<xMax1) || (xMin1<xMax2 && xMax2<xMax1));
14351
-
14352
- let widthsOverlap=(xMin1<xMax2 && xMin2<xMax1);
14353
-
14354
-
14355
- let demiHeight1=zone1.h*.5;
14356
- let demiHeight2=zone2.h*.5;
14357
-
14358
- let yMin1=zone1.y-demiHeight1;
14359
- let yMax1=zone1.y+demiHeight1;
14360
- let yMin2=zone2.y-demiHeight2;
14361
- let yMax2=zone2.y+demiHeight2;
14362
-
14363
- //OLD : let heightsOverlap=((yMin1<yMin2 && yMin2<yMax1) || (yMin1<yMax2 && yMax2<yMax1)
14364
- // || (yMin2<yMin1 && yMin1<yMax2) || (yMin2<yMax1 && yMax1<yMax2));
14365
-
14366
- let heightsOverlap=(yMin1<yMax2 && yMin2<yMax1);
14367
-
14368
- return widthsOverlap && heightsOverlap;
14369
- }
14370
-
14371
-
14372
-
14373
14174
  // CAUTION : ONLY HANDLES «center-positionned» ZONES !
14374
14175
  /*public static*/window.getIntersectionZone=function(zone1, zone2){
14375
14176
  if(!isZoneCollidingWithZone(zone1, zone2)) return null;
@@ -14564,10 +14365,10 @@ function rayVsUnitSphereClosestPoint(p, r) {
14564
14365
  // MUST REMAIN AT THE END OF THIS LIBRARY FILE !
14565
14366
 
14566
14367
  AOTRAUTILS_GEOMETRY_LIB_IS_LOADED=true;
14567
- /*utils 3D library associated with aotra version : «1_29072022-2359 (09/04/2026-01:57:45)»*/
14368
+ /*utils 3D library associated with aotra version : «1_29072022-2359 (02/06/2026-01:05:39)»*/
14568
14369
  /*-----------------------------------------------------------------------------*/
14569
14370
 
14570
- /*utils AI library associated with aotra version : «1_29072022-2359 (09/04/2026-01:57:45)»*/
14371
+ /*utils AI library associated with aotra version : «1_29072022-2359 (02/06/2026-01:05:39)»*/
14571
14372
  /*-----------------------------------------------------------------------------*/
14572
14373
 
14573
14374
 
@@ -14713,11 +14514,11 @@ getOpenAIAPIClient=(modelName, apiURL, agentRole, defaultPrompt)=>{
14713
14514
 
14714
14515
 
14715
14516
 
14716
- /*utils CONSOLE library associated with aotra version : «1_29072022-2359 (09/04/2026-01:57:45)»*/
14517
+ /*utils CONSOLE library associated with aotra version : «1_29072022-2359 (02/06/2026-01:05:39)»*/
14717
14518
  /*-----------------------------------------------------------------------------*/
14718
14519
 
14719
14520
 
14720
- /* ## Utility methods in a javascript, for AORTAC subsystem (server & client)
14521
+ /* ## Utility methods in a javascript, for AORTAC subsystem (client)
14721
14522
  *
14722
14523
  * This set of methods gathers utility generic-purpose methods usable in any JS project.
14723
14524
  * Several authors of snippets published freely on the Internet contributed to this library.
@@ -14740,163 +14541,584 @@ getOpenAIAPIClient=(modelName, apiURL, agentRole, defaultPrompt)=>{
14740
14541
  if(typeof(window)==="undefined") window=global;
14741
14542
 
14742
14543
 
14544
+
14545
+
14743
14546
  // ==================================================================================================================
14744
14547
 
14745
14548
 
14746
- // AORTAC SERVER
14549
+ // AORTAC CLIENT
14550
+
14747
14551
 
14748
14552
 
14749
14553
  //*********************************** AUTO-ORGANIZING REAL-TIME AORTAC CLUSTERIZATION (AORTAC) *********************************** */
14750
14554
 
14751
- AORTAC_OUTCOMING_SERVERS_CONNECTION_GLOBAL_TIMEOUT=30000;
14555
+ const AORTAC_CLIENT_FORCE_SSL_USAGE=true;
14752
14556
 
14753
14557
  // New implementation :
14754
- AORTAC_SERVER_CELL_PERIODICAL_CONNECTIVITY_CHECK_MILLIS=2000;
14755
- class AORTACServerCell{
14558
+
14559
+ class AORTACClientCell{
14756
14560
 
14757
- constructor(quorumNumber=1, selfOrigin, outcomingCellsOrigins, model, controller, sslConfig={/*OPTIONAL*/certPath:null,/*OPTIONAL*/keyPath:null}){
14561
+ constructor(serverCellOrigin, model, view, isNodeContext=false){
14562
+
14563
+ this.clientId=getUUID();
14758
14564
 
14759
14565
  this.model=model;
14760
- this.controller=controller;
14761
-
14762
- this.quorumNumber=quorumNumber;
14763
- this.selfOrigin=selfOrigin;
14764
- const infos=splitURL(this.selfOrigin);
14765
-
14766
- //DBG
14767
- lognow("infos:::::::::::::",infos);
14566
+ this.view=view;
14567
+ this.isNodeContext=isNodeContext;
14768
14568
 
14569
+ const infos=splitURL(serverCellOrigin);
14570
+
14769
14571
  this.protocol=nonull(infos.protocol,"ws");
14770
14572
  this.host=nonull(infos.host,"localhost");
14771
14573
  this.port=nonull(infos.port,"30000");
14772
- this.sslConfig=sslConfig;
14773
-
14774
- this.server=null;
14775
- this.incomingCells={};
14574
+ this.usesSSL=AORTAC_CLIENT_FORCE_SSL_USAGE;
14776
14575
 
14777
- this.outcomingCellsOrigins=outcomingCellsOrigins;
14778
- this.outcomingCells={};
14779
- const self=this;
14780
- foreach(this.outcomingCellsOrigins,outcomingCellOrigin=>{
14781
- self.outcomingCells[outcomingCellOrigin]={connected:false,client:null};
14782
- });
14783
- this.outcomingNetworkProber=null;
14576
+ this.startTime=null;
14784
14577
 
14785
- this.onReceiveMessageListeners={
14786
- "incoming":{"protocol":{}},
14787
- "outcoming":{"protocol":{}},
14788
- "both":{"protocol":{}},
14789
- };
14790
-
14791
- this.cellsOverview={};
14578
+ this.initialClientInstance=null;
14579
+ this.clientInstances={};
14792
14580
 
14793
- this.startTime=null;
14794
14581
 
14795
- this.quorumIsReachedSequenceIsInitiated=false;
14796
- // this.quorumCells=null; // (CAUTION : only the latest ready cell when quorum is reached has this attribute populated)
14797
-
14798
- this.liveModelObjects={};
14582
+
14583
+ this.ready=false;
14584
+
14585
+ this.lobuleZone=new ClientLobule();
14799
14586
 
14587
+
14800
14588
  }
14589
+
14590
+
14591
+ start(manifestationZone){
14592
+
14593
+ const self=this;
14801
14594
 
14802
- start(){
14803
14595
 
14596
+ this.manifestationZone=manifestationZone;
14804
14597
  this.startTime=getNow();
14805
- this.server=this.launchServerForIncomingConnections();
14806
-
14807
- if(empty(this.outcomingCellsOrigins) || (getArraySize(this.outcomingCellsOrigins)==1 && contains(this.outcomingCellsOrigins, this.selfOrigin))){
14808
- // TRACE
14809
- lognow("ERROR : Server cell must have an outcoming link to at least one other server cell of the blob. Aborting outcoming network probing.");
14810
- }else{
14811
- this.outcomingNetworkProber=new PeriodicalExecuter(this.probeOutcomingNetwork, AORTAC_SERVER_CELL_PERIODICAL_CONNECTIVITY_CHECK_MILLIS, this);
14812
- }
14813
14598
 
14814
- this.handleCommonListeners();
14815
-
14816
- // TRACE
14817
- lognow("AORTAC Server cell started on URL : ",this.selfOrigin);
14818
-
14819
- return this;
14820
- }
14821
-
14822
- /*private*/launchServerForIncomingConnections(){
14823
- const self=this;
14824
14599
 
14825
- const server=initNodeServerInfrastructureWrapper(
14826
- // On each client connection :
14827
- null,
14828
- // On client finalization :
14829
- function(server){
14830
14600
 
14831
- // On-receive message listeners handling ;
14832
- foreach(Object.keys(self.onReceiveMessageListeners["incoming"]), channelName=>{
14833
- server.receive(channelName, (message, clientSocket)=>{
14834
- self.executeListeners("incoming",channelName, message, clientSocket);
14835
- });
14836
- });
14837
- foreach(Object.keys(self.onReceiveMessageListeners["both"]), channelName=>{
14838
- server.receive(channelName, (message, clientSocket)=>{
14839
- self.executeListeners("both",channelName, message, clientSocket);
14840
- });
14841
- });
14601
+ return new Promise((resolve,reject)=>{
14602
+
14603
+
14604
+ // 1) Initial connection
14605
+ self.initialClientInstance=initClient(self.isNodeContext, false, (socketToServerClientInstance, initialClientInstanceLocal)=>{
14606
+
14607
+ // DBG
14608
+ lognow("DEBUG : Starting client...");
14609
+
14610
+ initialClientInstanceLocal.socketToServerClientInstance=socketToServerClientInstance;
14842
14611
 
14612
+ // INITIATE CLIENT HELLO SEQUENCE
14613
+ self.initiateClientHelloSequence();
14843
14614
 
14844
- // HANDLE HELLO SEQUENCE
14845
- self.handleHelloSequence();
14615
+ // HANDLE CLIENT HELLO SEQUENCE
14616
+ self.handleClientHelloSequence(resolve);
14617
+
14846
14618
 
14847
- }, this.port, this.sslConfig.certPath, this.sslConfig.keyPath);
14848
- server.serverManager.start();
14849
- return server;
14619
+ }, self.protocol+"://"+self.host, self.port, self.usesSSL);
14620
+ self.initialClientInstance.client.start();
14621
+
14622
+ });
14850
14623
  }
14851
14624
 
14852
- /*private*/probeOutcomingNetwork(){
14853
- const numberOfTotalServers=getArraySize(this.outcomingCells);
14854
- let numberOfConnectedOutcomingServers=0;
14855
- foreach(this.outcomingCells, outcomingCell=>{
14856
- numberOfConnectedOutcomingServers++
14857
- },outcomingCell=>outcomingCell.connected);
14858
-
14859
- // TRACE
14860
- lognow(`INFO : Number of connected outcoming servers : ${numberOfConnectedOutcomingServers}/${numberOfTotalServers}`);
14861
-
14862
- if(numberOfTotalServers<=numberOfConnectedOutcomingServers){
14863
- this.outcomingNetworkProber.stop();
14864
-
14865
- // TRACE
14866
- lognow(`INFO : All outcoming cells connected. Stopping outcoming network probing.`);
14867
14625
 
14868
- // INITIATE CELL IS READY SEQUENCE
14869
- this.initiateCellIsReadySequence();
14626
+ // ===================================================================================================
14627
+ // SEQUENCES
14628
+ // ===================================================================================================
14870
14629
 
14871
- return;
14872
- }
14630
+
14631
+ // ==========================================================
14632
+ // CLIENT HELLO SEQUENCE
14633
+
14634
+ // 1) We send a client hello request
14635
+ // 2) Server cell receives it and asks the blob for all servers which this client must reconnect to
14636
+ // (also server sends its objects corresponding to zone, if it has some of them)
14637
+ // 3) We receive the confirmation and reconnect to all the indicated servers
14638
+ // 4) Each concerned server cell receives it and registers the client and sends the hello confirmation
14639
+ // (and also the objects in the zone it had stored when trying to know if it's a concerned server or not)
14640
+ // 5) We have all the objects, we can initialize the client model.
14641
+
14642
+
14643
+ /*private*/initiateClientHelloSequence(){
14644
+ // 1- We want to present ourselves to server cell :
14645
+ const clientHelloRequest={
14646
+ clientId:this.clientId,
14647
+ type:"clientHello",
14648
+ manifestationZone:this.manifestationZone
14649
+ };
14650
+ this.initialClientInstance.socketToServerClientInstance.send("protocol", clientHelloRequest);
14651
+ }
14873
14652
 
14874
- const self=this;
14653
+ /*private*/handleClientHelloSequence(resolve){
14875
14654
 
14876
- // We try to connect to all outcoming cells :
14877
- foreach(this.outcomingCells, (outcomingCell, outcomingCellOrigin)=>{
14655
+ const self=this;
14656
+ this.initialClientInstance.socketToServerClientInstance.receive("protocol", (messageParam, clientSocket)=>{
14878
14657
 
14879
- const infos=splitURL(outcomingCellOrigin);
14880
- const outcomingCellProtocol=nonull(infos.protocol,"ws");
14881
- const outcomingCellHost=nonull(infos.host,"localhost");
14882
- const outcomingCellPort=nonull(infos.port,"40000");
14658
+ const message=JSON.recycle(messageParam);
14883
14659
 
14884
- const clientInstance=initClient(true, false, (socketToServerClientInstance)=>{
14885
-
14660
+ const servers=message.servers;
14661
+ // CAUTION : THE QLC ALWAYS SENDS AT LEAST THE ROOTCONTAINER (during the «client hello» phase) !!!
14662
+ const objects=message.objects;
14663
+
14664
+ lognow("DEBUG : Client received its servers :",servers);
14665
+ lognow("DEBUG : Client received some objects :",objects);
14666
+
14667
+
14668
+ self.lobuleZone.resetServersNumbers();
14669
+ self.lobuleZone.append(servers, "serversBag");
14670
+ // CAUTION : THE QLC ALWAYS SENDS AT LEAST ITS ROOTCONTAINER (during the «client hello» phase) !!!
14671
+ self.lobuleZone.append(objects);
14672
+
14673
+ const lobuleObjects=self.lobuleZone.getObjects();
14674
+ if(!servers || empty(servers)){
14675
+ // TRACE
14676
+ lognow("ERROR : Servers blob sent no servers to reconnect to. Aborting.");
14677
+ // (We only return the structural object, ie. the root container :)
14678
+ resolve(lobuleObjects);
14679
+ return;
14680
+ }
14681
+
14682
+ if(empty(servers)){
14683
+ // TRACE
14684
+ lognow("ERROR : No servers to connect to. Aborting (not initiaing the reconnection sequence).");
14685
+ // (We only return the structural object, ie. the root container :)
14686
+ resolve(lobuleObjects);
14687
+ return;
14688
+ }
14689
+
14690
+ // 2) Re-connections :
14691
+ foreach(servers, serverOrigin=>{
14692
+
14693
+ const infos=splitURL(serverOrigin);
14694
+
14695
+ const protocol=nonull(infos.protocol,"ws");
14696
+ const host=nonull(infos.host,"localhost");
14697
+ const port=nonull(infos.port,"30000");
14698
+ const usesSSL=AORTAC_CLIENT_FORCE_SSL_USAGE;
14699
+
14700
+ const reconnectedClientInstance=initClient(self.isNodeContext, false, (socketToServerClientInstance, reconnectedClientInstanceLocal)=>{
14701
+
14702
+ // DBG
14703
+ lognow("DEBUG : Starting reconnected client...");
14704
+
14705
+ reconnectedClientInstanceLocal.socketToServerClientInstance=socketToServerClientInstance;
14706
+
14707
+ // INITIATE CLIENT RECONNECTED HELLO SEQUENCE
14708
+ self.initiateReconnectedClientHelloSequence(reconnectedClientInstance);
14709
+
14710
+ // HANDLE CLIENT RECONNECTED HELLO SEQUENCE
14711
+ self.handleReconnectedClientHelloSequence(reconnectedClientInstance, resolve)
14712
+
14713
+
14714
+ }, protocol+"://"+host, port, usesSSL);
14715
+
14716
+ self.clientInstances[serverOrigin]=reconnectedClientInstance;
14717
+ reconnectedClientInstance.client.start();
14718
+ });
14719
+
14720
+
14721
+ },{listenerMessageType:"clientHello.response"});
14722
+
14723
+ }
14724
+
14725
+ // ----
14726
+ // RECONNECTED CLIENT HELLO SEQUENCE
14727
+
14728
+
14729
+ /*private*/initiateReconnectedClientHelloSequence(clientInstance){
14730
+
14731
+ // We want to present ourselves to server cell :
14732
+ const reconnectedClientHelloRequest={
14733
+ clientId:this.clientId,
14734
+ type:"reconnectedClientHello"
14735
+ };
14736
+ clientInstance.client.socketToServerClientInstance.send("protocol", reconnectedClientHelloRequest);
14737
+
14738
+ }
14739
+
14740
+ /*private*/handleReconnectedClientHelloSequence(clientInstance, resolve){
14741
+
14742
+
14743
+ const self=this;
14744
+
14745
+ clientInstance.client.socketToServerClientInstance.receive("protocol", (messageParam, clientSocket)=>{
14746
+
14747
+ const message=JSON.recycle(messageParam);
14748
+
14749
+ const objects=message.objects;
14750
+
14751
+ if(!objects || empty(objects)){
14752
+ // TRACE
14753
+ lognow("ERROR : This server cell sent no objects. Continuing.");
14754
+
14755
+ // DBG
14756
+ lognow("DEBUG : !!!!!!!!!!!!! message:",message);
14757
+
14758
+ }
14759
+
14760
+ lognow("DEBUG : Client received some objects :",objects);
14761
+
14762
+ // We accumulate the received objects in the lobule zone :
14763
+ self.lobuleZone.append(objects, "objectsBag");
14764
+
14765
+ self.lobuleZone.incrementContactedServersNumber();
14766
+
14767
+
14768
+ // DBG
14769
+ lognow("DEBUG : !!!!!!!!!!!!! self.lobuleZone.getObjects():",self.lobuleZone.getObjects());
14770
+
14771
+
14772
+ if(self.lobuleZone.hasReceivedFromAllServers()){
14773
+
14774
+ const lobuleObjects=self.lobuleZone.getObjects();
14775
+ if(!lobuleObjects || empty(lobuleObjects)){
14776
+ // TRACE
14777
+ lognow("ERROR : No objects at all accumulated from server cells blob. Aborting.");
14778
+ resolve(null);
14779
+ return;
14780
+ }
14781
+
14782
+ // const allClientModelObjectsByClassName=self.sortByClassName(lobuleObjects);
14783
+ const allClientModelObjects=lobuleObjects;
14784
+
14785
+ //DBG
14786
+ // lognow("DEBUG : allClientModelObjectsByClassName : ",allClientModelObjectsByClassName);
14787
+ lognow("DEBUG : allClientModelObjects : ",allClientModelObjects);
14788
+
14789
+ if(empty(allClientModelObjects)){
14790
+ // TRACE
14791
+ lognow("ERROR : Servers blob sent no game level. Aborting.");
14792
+ resolve(allClientModelObjects);
14793
+ return;
14794
+ }
14795
+
14796
+ // DBG
14797
+ lognow("DEBUG : Client received its model objects:",message.objects);
14798
+ // lognow("DEBUG : Client received its model objects (after sorting):",allClientModelObjectsByClassName);
14799
+
14800
+ // DBG
14801
+ lognow("DEBUG : Client has received all its objects from the servers cells blob: ");
14802
+
14803
+ resolve(allClientModelObjects);
14804
+ }
14805
+
14806
+
14807
+ },{listenerMessageType:"reconnectedClientHello.response"});
14808
+
14809
+ }
14810
+
14811
+
14812
+
14813
+ // ================================================================================
14814
+
14815
+
14816
+
14817
+
14818
+ }
14819
+
14820
+
14821
+
14822
+
14823
+ class ClientLobule{
14824
+
14825
+ constructor(){
14826
+ this.serversBag=null;
14827
+ this.objectsBag=null;
14828
+ this.numberOfContactedServers=0;
14829
+ this.totalNumberOfServersToContact=0;
14830
+ this.clear();
14831
+ }
14832
+ append(objs, attributeName="objectsBag"){
14833
+ const self=this;
14834
+ if(!this[attributeName])
14835
+ this[attributeName]=[];
14836
+ foreach(objs,obj=>{
14837
+ self[attributeName].push(obj);
14838
+ },(obj)=>(!contains(this[attributeName], obj)));
14839
+ if(attributeName=="serversBag")
14840
+ this.totalNumberOfServersToContact+=objs.length;
14841
+ }
14842
+ getObjects(filterFunction=null){
14843
+ if(!filterFunction) return this.objectsBag;
14844
+ if(!this.objectsBag) return null;
14845
+ const results=[];
14846
+ foreach(this.objectsBag, (obj)=>{
14847
+ results.push(obj);
14848
+ },filterFunction);
14849
+ return results;
14850
+ }
14851
+ getServers(){
14852
+ if(!this.serversBag) return null;
14853
+ return this.serversBag;
14854
+ }
14855
+ clear(attributeName=null){
14856
+ if(!attributeName){
14857
+ this.serversBag=[];
14858
+ this.objectsBag=[];
14859
+ }else{
14860
+ this[attributeName]=[];
14861
+ }
14862
+ this.resetServersNumbers();
14863
+ return this;
14864
+ }
14865
+ resetServersNumbers(){
14866
+ this.numberOfContactedServers=0;
14867
+ this.totalNumberOfServersToContact=0;
14868
+ }
14869
+ incrementContactedServersNumber(increment=1){
14870
+ this.numberOfContactedServers+=increment;
14871
+ }
14872
+ hasReceivedFromAllServers(){
14873
+ return (this.totalNumberOfServersToContact<=this.numberOfContactedServers);
14874
+ }
14875
+ }
14876
+
14877
+
14878
+
14879
+
14880
+
14881
+
14882
+ window.getAORTACClient=function(serverCellOrigin="ws://127.0.0.1:40000", model, view, isNodeContext=false){
14883
+ if(nothing(serverCellOrigin)){
14884
+ // TRACE
14885
+ lognow("ERROR : No known server node, cannot connect to servers cells blob. Aborting AORTAC client setup.");
14886
+ return null;
14887
+ }
14888
+ return new AORTACClientCell(serverCellOrigin, model, view, isNodeContext);
14889
+ }
14890
+
14891
+
14892
+
14893
+ /* ## Utility methods in a javascript, for AORTAC subsystem (server)
14894
+ *
14895
+ * This set of methods gathers utility generic-purpose methods usable in any JS project.
14896
+ * Several authors of snippets published freely on the Internet contributed to this library.
14897
+ * Feel free to use/modify-enhance/publish them under the terms of its license.
14898
+ *
14899
+ * # Library name : «aotrautils»
14900
+ * # Library license : HGPL(Help Burma) (see aotra README information for details : https://alqemia.com/aotra.js )
14901
+ * # Author name : Jérémie Ratomposon (massively helped by his native country free education system)
14902
+ * # Author email : info@alqemia.com
14903
+ * # Organization name : Alqemia
14904
+ * # Organization email : admin@alqemia.com
14905
+ * # Organization website : https://alqemia.com
14906
+ *
14907
+ *
14908
+ */
14909
+
14910
+
14911
+
14912
+ // COMPATIBILITY browser javascript / nodejs environment :
14913
+ if(typeof(window)==="undefined") window=global;
14914
+
14915
+
14916
+ // ==================================================================================================================
14917
+
14918
+
14919
+ // AORTAC SERVER
14920
+
14921
+
14922
+ //*********************************** AUTO-ORGANIZING REAL-TIME AORTAC CLUSTERIZATION (AORTAC) *********************************** */
14923
+
14924
+ AORTAC_OUTCOMING_SERVERS_CONNECTION_GLOBAL_TIMEOUT=30000;
14925
+
14926
+ AORTAC_SERVER_CELL_PERIODICAL_CONNECTIVITY_CHECK_MILLIS=2000;
14927
+
14928
+ class AORTACServerCell{
14929
+
14930
+ constructor(quorumNumber=1, selfOrigin, outcomingCellsOrigins, model, controller, sslConfig={/*OPTIONAL*/certPath:null,/*OPTIONAL*/keyPath:null}){
14931
+
14932
+ this.model=model;
14933
+ this.controller=controller;
14934
+
14935
+ this.quorumNumber=quorumNumber;
14936
+ this.selfOrigin=selfOrigin;
14937
+ const infos=splitURL(this.selfOrigin);
14938
+
14939
+ //DBG
14940
+ lognow("(SERVER) infos:::::::::::::",infos);
14941
+
14942
+ this.protocol=nonull(infos.protocol,"ws");
14943
+ this.host=nonull(infos.host,"localhost");
14944
+ this.port=nonull(infos.port,"30000");
14945
+ this.sslConfig=sslConfig;
14946
+
14947
+ this.serverWrapper=null;
14948
+ this.incomingCells={};
14949
+
14950
+ this.outcomingCellsOrigins=outcomingCellsOrigins;
14951
+ this.outcomingCells={};
14952
+ const self=this;
14953
+ foreach(this.outcomingCellsOrigins,outcomingCellOrigin=>{
14954
+ self.outcomingCells[outcomingCellOrigin]={connected:false,client:null};
14955
+ });
14956
+ this.outcomingNetworkProber=null;
14957
+
14958
+ this.onReceiveMessageListeners={
14959
+ "incoming":{"protocol":{}},
14960
+ "outcoming":{"protocol":{}},
14961
+ "both":{"protocol":{}},
14962
+ };
14963
+
14964
+ this.cellsOverview={};
14965
+ this.numberOfPartitions=null;
14966
+ this.partitionId=null;
14967
+
14968
+ this.isServerCellQuorumLastCell=false;
14969
+
14970
+ this.startTime=null;
14971
+
14972
+ this.quorumIsReachedSequenceIsInitiated=false;
14973
+ // this.quorumCells=null; // (CAUTION : only the latest ready cell when quorum is reached has this attribute populated)
14974
+
14975
+ this.lobuleZone=new ServerCellLobule();
14976
+
14977
+ this.clients={};
14978
+
14979
+ }
14980
+
14981
+ start(){
14982
+
14983
+ this.startTime=getNow();
14984
+ this.serverWrapper=this.launchServerForIncomingConnections();
14985
+
14986
+ if(empty(this.outcomingCellsOrigins) || (getArraySize(this.outcomingCellsOrigins)==1 && contains(this.outcomingCellsOrigins, this.selfOrigin))){
14987
+ // TRACE
14988
+ lognow("ERROR : Server cell must have an outcoming link to at least one other server cell of the blob. Aborting outcoming network probing.");
14989
+ }else{
14990
+ this.outcomingNetworkProber=new PeriodicalExecuter(this.probeOutcomingNetwork, AORTAC_SERVER_CELL_PERIODICAL_CONNECTIVITY_CHECK_MILLIS, this);
14991
+ }
14992
+
14993
+ this.handleCommonListeners();
14994
+
14995
+ // TRACE
14996
+ lognow("AORTAC Server cell started on URL : ",this.selfOrigin);
14997
+
14998
+ return this;
14999
+ }
15000
+
15001
+
15002
+ // ===================================================================================================
15003
+ // SERVER CELL CORE
15004
+ // ===================================================================================================
15005
+
15006
+ /*private*/launchServerForIncomingConnections(){
15007
+ const self=this;
15008
+
15009
+ const serverWrapper=initNodeServerInfrastructureWrapper(
15010
+ // On each client connection :
15011
+ null,
15012
+ // On client finalization :
15013
+ function(server){
15014
+
15015
+
15016
+ const channelsNames=[];
15017
+ foreach(Object.keys(self.onReceiveMessageListeners["incoming"]), channelName=>{channelsNames.push(channelName);}, channelName=>!contains(channelsNames,channelName));
15018
+ foreach(Object.keys(self.onReceiveMessageListeners["both"]), channelName=>{channelsNames.push(channelName);}, channelName=>!contains(channelsNames,channelName));
15019
+
15020
+
14886
15021
  // On-receive message listeners handling ;
14887
- foreach(Object.keys(self.onReceiveMessageListeners["outcoming"]), channelName=>{
14888
- socketToServerClientInstance.receive(channelName, (message, clientSocket)=>{
14889
- self.executeListeners("outcoming",channelName, message, clientSocket);
15022
+ foreach(channelsNames, channelName=>{
15023
+ server.receive(channelName, (message, clientSocket)=>{
15024
+
15025
+ // DBG
15026
+ lognow("DEBUG : Server cell receives message from incoming...");
15027
+
15028
+ self.executeListeners("incoming", channelName, message, clientSocket);
15029
+
15030
+ // DBG
15031
+ lognow("DEBUG : Server cell receives message from both (in)...");
15032
+
15033
+ self.executeListeners("both",channelName, message, clientSocket);
14890
15034
  });
14891
15035
  });
14892
- foreach(Object.keys(self.onReceiveMessageListeners["both"]), channelName=>{
15036
+
15037
+
15038
+ // HANDLE SERVER CELL HELLO SEQUENCE
15039
+ self.handleServerCellHelloSequence();
15040
+
15041
+
15042
+ // ******************************************
15043
+ // CLIENT
15044
+
15045
+ // HANDLE CLIENT HELLO SEQUENCE
15046
+ self.handleClientHelloSequence();
15047
+
15048
+
15049
+ },
15050
+ // On client connection lost :
15051
+ (clientId)=>{
15052
+ lognow(`INFO : Connection to client id «${clientId}» was lost.`);
15053
+ // TRACE
15054
+ lognow(`INFO : Removing client information for client id «${clientId}».`);
15055
+ self.lobuleZone.remove(clientId);
15056
+ },
15057
+ this.port, this.sslConfig.certPath, this.sslConfig.keyPath);
15058
+
15059
+ serverWrapper.serverManager.start();
15060
+ return serverWrapper;
15061
+ }
15062
+
15063
+ /*private*/probeOutcomingNetwork(){
15064
+
15065
+ const self=this;
15066
+
15067
+ const numberOfTotalServers=getArraySize(this.outcomingCells);
15068
+ let numberOfConnectedOutcomingServers=0;
15069
+ foreach(this.outcomingCells, outcomingCell=>{
15070
+ numberOfConnectedOutcomingServers++
15071
+ },outcomingCell=>outcomingCell.connected);
15072
+
15073
+ // TRACE
15074
+ lognow(`INFO : Number of connected outcoming servers : ${numberOfConnectedOutcomingServers}/${numberOfTotalServers}`);
15075
+
15076
+ if(numberOfTotalServers<=numberOfConnectedOutcomingServers){
15077
+ this.outcomingNetworkProber.stop();
15078
+
15079
+ // TRACE
15080
+ lognow(`INFO : All outcoming cells connected. Stopping outcoming network probing.`);
15081
+
15082
+ // INITIATE CELL IS READY SEQUENCE
15083
+ this.initiateCellIsReadySequence();
15084
+
15085
+ return;
15086
+ }
15087
+
15088
+
15089
+ // We try to connect to all outcoming cells :
15090
+ foreach(this.outcomingCells, (outcomingCell, outcomingCellOrigin)=>{
15091
+
15092
+ const infos=splitURL(outcomingCellOrigin);
15093
+ const outcomingCellProtocol=nonull(infos.protocol,"ws");
15094
+ const outcomingCellHost=nonull(infos.host,"localhost");
15095
+ const outcomingCellPort=nonull(infos.port,"40000");
15096
+
15097
+ const clientInstance=initClient(true, false, (socketToServerClientInstance)=>{
15098
+
15099
+
15100
+ const channelsNames=[];
15101
+ foreach(Object.keys(self.onReceiveMessageListeners["outcoming"]), channelName=>{channelsNames.push(channelName);}, channelName=>!contains(channelsNames,channelName));
15102
+ foreach(Object.keys(self.onReceiveMessageListeners["both"]), channelName=>{channelsNames.push(channelName);}, channelName=>!contains(channelsNames,channelName));
15103
+
15104
+
15105
+ // On-receive message listeners handling ;
15106
+ foreach(channelsNames, channelName=>{
14893
15107
  socketToServerClientInstance.receive(channelName, (message, clientSocket)=>{
15108
+
15109
+ // DBG
15110
+ lognow("DEBUG : Server cell receives message from outcoming...");
15111
+
15112
+ self.executeListeners("outcoming",channelName, message, clientSocket);
15113
+
15114
+ // DBG
15115
+ lognow("DEBUG : Server cell receives message from both (out)...");
15116
+
14894
15117
  self.executeListeners("both",channelName, message, clientSocket);
14895
15118
  });
14896
15119
  });
14897
-
14898
15120
 
14899
- // INITIATE HELLO SEQUENCE
15121
+ // INITIATE SERVER CELL HELLO SEQUENCE
14900
15122
  self.initiateHelloSequence(socketToServerClientInstance);
14901
15123
 
14902
15124
  // We are connected:
@@ -14920,47 +15142,68 @@ class AORTACServerCell{
14920
15142
 
14921
15143
  }
14922
15144
 
14923
-
14924
15145
  /*private*/handleCommonListeners(){
14925
-
14926
15146
  // HANDLE CELL IS READY SEQUENCE
14927
15147
  this.handleCellIsReadySequence();
14928
-
14929
15148
  // HANDLE PARTITION SEQUENCE
14930
15149
  this.handlePartitionSequence();
14931
15150
 
14932
- }
15151
+
15152
+ // ******************************************
15153
+ // CLIENT
15154
+ // HANDLE CLIENT MODEL INITIAL POPULATION SEQUENCE
15155
+ this.handleGetServersForZoneRequest();
15156
+
15157
+ // HANDLE RECONNECTED CLIENT HELLO SEQUENCE
15158
+ this.handleReconnectedClientHelloSequence();
14933
15159
 
15160
+ }
15161
+
14934
15162
 
14935
15163
  /*private*/executeListeners(outletName, channelName, message, clientSocket){
14936
15164
  const onReceiveMessageListeners=this.onReceiveMessageListeners[outletName][channelName];
14937
15165
  if(!onReceiveMessageListeners) return;
14938
15166
 
14939
- // OUTCOMING NODES LISTENERS HOOK :
14940
- // // TRACE
14941
- // lognow("INFO : SERVER : Received a "+channelName+" message: ", message);
14942
-
14943
- foreach(onReceiveMessageListeners, (listeners,listenerMessageType)=>{
14944
- foreach(listeners, (listener)=>{
14945
- // // TRACE
14946
- // lognow(`INFO : SERVER : Executing a onReceiveMessageListeners for messageType ${listenerMessageType}.`);
14947
-
14948
- listener.execute(this, message, this.server, clientSocket);
14949
-
14950
- },(listener)=>(message.type===listener.listenerMessageType));
14951
- });
14952
-
15167
+ const self=this;
15168
+ foreach(onReceiveMessageListeners, (listener, listenerMessageType)=>{
15169
+ listener.execute(self, message, self.serverWrapper, clientSocket);
15170
+ },(listener)=>(message.type===listener.listenerMessageType));
15171
+
15172
+ }
15173
+
15174
+
15175
+ /*private*/replyToBlobRequest(channelName, originalMessage, message){
15176
+ if(!message.originatingCellOrigin)
15177
+ message.originatingCellOrigin=originalMessage.originatingCellOrigin;
15178
+ if(!message.originatingPartitionId)
15179
+ message.originatingPartitionId=originalMessage.originatingPartitionId;
15180
+ if(!message.clientId)
15181
+ message.clientId=originalMessage.clientId;
15182
+ return this.sendMessageToBlob(channelName, message,
15183
+ {
15184
+ isOriginatingCell:false,
15185
+ destinationCellsOrigins:[originalMessage.originatingCellOrigin],
15186
+ excludeIncomingServersCellsAndClientsInTransmission:true,
15187
+ isRequest:false
15188
+ },
15189
+ originalMessage.type+".response");
14953
15190
  }
14954
15191
 
14955
15192
 
14956
15193
  /*private*/sendMessageToBlob(channelName, message,
14957
- broadcastConfig={isOriginatingCell:false, destinationCellsOrigins:null, includeIncomingConnectionInTransmission:false, isRequest:false}){
15194
+ broadcastConfig={
15195
+ isOriginatingCell:false,
15196
+ destinationCellsOrigins:null,
15197
+ excludeIncomingServersCellsAndClientsInTransmission:true,
15198
+ isRequest:false,
15199
+ contactAllQuorumBlob:false
15200
+ },
15201
+ overridingMessageType=null){
14958
15202
 
14959
- const self=this;
14960
-
14961
15203
  if(broadcastConfig){
14962
15204
  if(broadcastConfig.isOriginatingCell){
14963
15205
  message.originatingCellOrigin=this.selfOrigin;
15206
+ message.originatingPartitionId=this.partitionId;
14964
15207
  }
14965
15208
  if(broadcastConfig.isRequest){
14966
15209
  message.isRequest=true;
@@ -14973,10 +15216,36 @@ class AORTACServerCell{
14973
15216
  if(!message.visitedCells)
14974
15217
  message.visitedCells=[];
14975
15218
  else if(contains(message.visitedCells,this.selfOrigin))
14976
- return;
15219
+ return null;
14977
15220
  message.visitedCells.push(this.selfOrigin);
14978
15221
 
14979
- if(broadcastConfig && broadcastConfig.includeIncomingConnectionInTransmission){
15222
+
15223
+ // AUTOMATIC LISTENER SETUP :
15224
+ const messageTypeForResponse=nonull(overridingMessageType, message.type+".response");
15225
+ let blobResponseListener=null;
15226
+ // We only add a result listener for the one cell which sent the request message (we skip the listener adding for the others in this case) :
15227
+ if(message.isRequest && message.originatingCellOrigin==this.selfOrigin
15228
+ //&& (typeof(message.result)=="undefined" || message.result==null)
15229
+ ){
15230
+ // To emulate a promise-like behavior :
15231
+ blobResponseListener=this.getBlobResponseListenerForRequest(message, channelName, broadcastConfig, messageTypeForResponse);
15232
+
15233
+ const outletName=nonull(blobResponseListener.outletName,"both");
15234
+ if(!this.onReceiveMessageListeners[outletName][channelName][blobResponseListener.listenerMessageType]){
15235
+ this.onReceiveMessageListeners[outletName][channelName][blobResponseListener.listenerMessageType]=blobResponseListener;
15236
+ // TRACE
15237
+ lognow("INFO : Added automatic listener for message type «"+blobResponseListener.listenerMessageType+"».");
15238
+ }else{
15239
+ // TRACE
15240
+ lognow("INFO : Listener for message type «"+blobResponseListener.listenerMessageType+"» already existed. Done nothing.");
15241
+ }
15242
+ }else{
15243
+ // TRACE
15244
+ lognow(`INFO : No listener to setup, because broadcast is not a request, or cell is not the originating cell !`);
15245
+ }
15246
+
15247
+ // SENDING
15248
+ if(broadcastConfig && !broadcastConfig.excludeIncomingServersCellsAndClientsInTransmission){
14980
15249
  foreach(this.incomingCells,(incomingCell)=>{
14981
15250
  // As a server, we send (forward) the message to the currently iterated upon client that is connected to us :
14982
15251
  incomingCell.server.send(channelName, message, null, incomingCell.clientSocket);
@@ -14984,7 +15253,6 @@ class AORTACServerCell{
14984
15253
  (incomingCell.connected && !contains(message.visitedCells, incomingCellOrigin))
14985
15254
  );
14986
15255
  }
14987
-
14988
15256
  foreach(this.outcomingCells,(outcomingCell)=>{
14989
15257
  // As a client, we send (forward) the message to the currently iterated upon server we are connected to :
14990
15258
  outcomingCell.socketToServerClientInstance.send(channelName, message);
@@ -14992,49 +15260,84 @@ class AORTACServerCell{
14992
15260
  (outcomingCell.connected && !contains(message.visitedCells, outcomingCellOrigin))
14993
15261
  );
14994
15262
 
15263
+ return blobResponseListener;
15264
+ }
15265
+
15266
+
15267
+ /*private*/getBlobResponseListenerForRequest(message, channelName, broadcastConfig, messageTypeForResponse){
15268
+
15269
+ const outletName="both"; // «incoming» & «outcoming»
15270
+ // We want to monitor when the server cell receives the RESPONSE of the message it sent as request !!!
15271
+
15272
+ // First we check if the listener for request already exists :
15273
+ let blobResponseListener=this.onReceiveMessageListeners[outletName][channelName][messageTypeForResponse];
15274
+ if(blobResponseListener){
15275
+ // TRACE
15276
+ lognow(`INFO : Blob response listener for cell ${this.selfOrigin} on channel ${channelName} for message type «${messageTypeForResponse}» already exists.`);
15277
+ return blobResponseListener;
15278
+ }
14995
15279
 
14996
- // We only add a result listener for the cell which sent the request message :
14997
- if(message.isRequest && message.originatingCellOrigin==this.selfOrigin
14998
- //&& (typeof(message.result)=="undefined" || message.result==null)
14999
- ){
15000
-
15001
- const outletName="both";
15002
-
15003
- // To emulate a promise-like behavior :
15004
- let blobResponseListeners=this.onReceiveMessageListeners[outletName][channelName][message.type];
15005
- if(blobResponseListeners && blobResponseListeners["forRequestsIssuedBySelf"]){
15006
- // TRACE
15007
- lognow(`INFO : Blob response «forRequestsIssuedBySelf» listener for cell ${this.selfOrigin} on channel ${channelName} for «${message.type}» already exists.`);
15008
- return blobResponseListeners["forRequestsIssuedBySelf"];
15009
- }else{
15010
- blobResponseListeners=getOrCreateEmptyAttribute(this.onReceiveMessageListeners[outletName][channelName],message.type);
15011
- }
15012
-
15013
- const blobResponseListener={
15014
- thenCallback:null,
15015
- execute:(self, message, server, clientSocket)=>{
15016
- // When we have received the message :
15017
- this.thenCallback(message);
15280
+
15281
+ blobResponseListener={
15282
+ broadcastConfig:broadcastConfig,
15283
+ thenCallback:null,
15284
+ allBlobHasBeenContactedCallback:null,
15285
+ hasAllBlobBeenContacted:false,
15286
+ // We use the partition ids to know which server cells have been contacted :
15287
+ partitionIdsThatCouldBeContacted:[],
15288
+ // This message will be the RESPONSE to the REQUEST (ti will be of type «<messageTypeForResponse>», meaning the request message type + «.response» !!)
15289
+ outletName:outletName,
15290
+ listenerMessageType:messageTypeForResponse,
15291
+ execute:(selfParam, messageParam, serverWrapper, clientSocket)=>{
15292
+
15293
+ // This is what the requesting cell does every time any cell that is not the requesting cell sends back a response to its request to blob :
15294
+
15295
+ // When we have received the message :
15296
+ if(blobResponseListener.thenCallback) blobResponseListener.thenCallback(selfParam, messageParam);
15297
+
15298
+ // We try to determine if the whole blob has been contacted or not :
15299
+ if(blobResponseListener.broadcastConfig.contactAllQuorumBlob && !blobResponseListener.hasAllBlobBeenContacted){
15018
15300
 
15019
- // We need to remove the result listener once it is completed though.
15020
- delete blobResponseListeners["forRequestsIssuedBySelf"];
15021
- },
15022
- then:(thenCallback)=>{
15023
- this.thenCallback=thenCallback;
15301
+ if(!contains(blobResponseListener.partitionIdsThatCouldBeContacted, messageParam.originatingPartitionId))
15302
+ blobResponseListener.partitionIdsThatCouldBeContacted.push(messageParam.originatingPartitionId);
15303
+ // We need to exclude the current serevr cell itself :
15304
+ if(selfParam.numberOfPartitions-1<=blobResponseListener.partitionIdsThatCouldBeContacted.length){
15305
+ if(blobResponseListener.allBlobHasBeenContactedCallback)
15306
+ blobResponseListener.allBlobHasBeenContactedCallback(selfParam, messageParam);
15307
+ blobResponseListener.hasAllBlobBeenContacted=true;
15308
+ }
15024
15309
  }
15025
- };
15026
- blobResponseListeners["forRequestsIssuedBySelf"]=blobResponseListener;
15027
-
15028
- return blobResponseListener;
15029
- }else{
15030
- return null;
15031
- }
15310
+
15311
+ // We need to remove the result listener once it has been completed though.
15312
+ if(!blobResponseListener.broadcastConfig.contactAllQuorumBlob || blobResponseListener.hasAllBlobBeenContacted){
15313
+ //UNUSEFUL : delete blobResponseListeners[listenerTypeForRequest];
15314
+ delete selfParam.onReceiveMessageListeners[outletName][channelName][messageTypeForResponse];
15315
+ }
15316
+
15317
+ },
15318
+ thenOnAnyResponseFromBlobRequestingCellOnly:(thenCallback)=>{
15319
+ blobResponseListener.thenCallback=thenCallback;
15320
+ return blobResponseListener;
15321
+ },
15322
+ doOnceAllBlobHasBeenContacted:(allBlobHasBeenContactedCallback)=>{
15323
+ blobResponseListener.allBlobHasBeenContactedCallback=allBlobHasBeenContactedCallback;
15324
+ return blobResponseListener;
15325
+ },
15326
+ };
15032
15327
 
15328
+
15329
+
15330
+ return blobResponseListener;
15033
15331
  }
15332
+
15333
+
15034
15334
 
15335
+ // ===================================================================================================
15336
+ // SERVER CELLS
15337
+ // ===================================================================================================
15035
15338
 
15036
15339
  // ==========================================================
15037
- // HELLO SEQUENCE
15340
+ // SERVER CELL HELLO SEQUENCE
15038
15341
 
15039
15342
  /*private*/initiateHelloSequence(socketToServerClientInstance){
15040
15343
 
@@ -15047,28 +15350,30 @@ class AORTACServerCell{
15047
15350
  }
15048
15351
 
15049
15352
 
15050
- /*private*/handleHelloSequence(){
15353
+ /*private*/handleServerCellHelloSequence(){
15051
15354
 
15052
15355
  // 2- We wait to receive the hello request of the incoming cell connection :
15053
- getOrCreateEmptyAttribute(
15054
- this.onReceiveMessageListeners["both"]["protocol"],"helloRequest")["forRequestsIssuedByOthers"]={
15055
- execute:(selfParam, message, server, clientSocket)=>{
15356
+ this.onReceiveMessageListeners["both"]["protocol"]["helloRequest"]={
15357
+ execute:(selfParam, messageParam, serverWrapper, clientSocket)=>{
15056
15358
 
15057
- const cellOriginToCheck=message.originatingCellOrigin;
15359
+ const cellOriginToCheck=messageParam.originatingCellOrigin;
15058
15360
 
15059
15361
  // TRACE
15060
- lognow(`INFO : Incoming node ${cellOriginToCheck} has said hello. Updating its local information...`);
15362
+ lognow(`INFO : (handleServerCellHelloSequence()) Incoming node ${cellOriginToCheck} has said hello. Updating its local information...`);
15061
15363
 
15062
15364
 
15063
15365
  selfParam.incomingCells[cellOriginToCheck]={
15064
15366
  connected:true,
15065
- server:server,
15367
+ server:serverWrapper.server,
15066
15368
  // DO NOT USE TO SEND/RECEIVE ANYTHINIG !
15067
15369
  // For this, use the server attribute (+ the clientSocket as argument) instead.
15068
15370
  clientSocket:clientSocket,
15069
15371
  };
15070
15372
 
15071
- },
15373
+
15374
+ // NO SERVER CELL HELLO CONFIRMATION SENT BACK TO THE OTHER SERVER CELL (to save time)
15375
+
15376
+ },
15072
15377
  listenerMessageType:"helloRequest"
15073
15378
  };
15074
15379
 
@@ -15093,18 +15398,14 @@ class AORTACServerCell{
15093
15398
  }
15094
15399
 
15095
15400
  /*private*/handleCellIsReadySequence(){
15096
- getOrCreateEmptyAttribute(
15097
- this.onReceiveMessageListeners["both"]["protocol"],"cellIsReady")["forRequestsIssuedByOthers"]={
15098
- execute:(selfParam, message, server, clientSocket)=>{
15099
15401
 
15100
- selfParam.cellsOverview[message.originatingCellOrigin]={ready:true,startTime:message.startTime};
15101
-
15102
- selfParam.sendMessageToBlob("protocol",message);
15402
+ this.onReceiveMessageListeners["both"]["protocol"]["cellIsReady"]={
15403
+
15404
+ execute:(selfParam, messageParam, serverWrapper, clientSocket)=>{
15405
+
15406
+ selfParam.cellsOverview[messageParam.originatingCellOrigin]={ready:true,startTime:messageParam.startTime};
15103
15407
 
15104
- // // TRACE
15105
- // lognow("INFO : Updated cells overview for this cell ("+selfParam.selfOrigin+") :",selfParam.cellsOverview);
15106
- // lognow("DEBUG : selfParam.outcomingCells :",Object.keys(selfParam.outcomingCells));
15107
- // lognow("DEBUG : selfParam.incomingCells :",Object.keys(selfParam.incomingCells));
15408
+ selfParam.sendMessageToBlob("protocol",messageParam);
15108
15409
 
15109
15410
  // INITIATE QUORUM IS REACHED SEQUENCE
15110
15411
  selfParam.initiateQuorumIsReachedSequenceIfNecessary();
@@ -15130,11 +15431,11 @@ class AORTACServerCell{
15130
15431
  const quorumCells=copy(this.cellsOverview);
15131
15432
 
15132
15433
  // TRACE
15133
- lognow("INFO : Quorum is reached and this is the latest started cell. quorumCells :",quorumCells);
15134
-
15135
- this.initializeModel(quorumCells);
15136
-
15137
-
15434
+ lognow("INFO : Quorum is reached and this is the latest started cell (QLC). quorumCells:",quorumCells);
15435
+
15436
+ this.isServerCellQuorumLastCell=true;
15437
+
15438
+ this.launchModelPartitionBlobSequence(quorumCells);
15138
15439
 
15139
15440
  }
15140
15441
 
@@ -15145,249 +15446,694 @@ class AORTACServerCell{
15145
15446
  // ==========================================================
15146
15447
  // MODEL MANAGEMENT
15147
15448
 
15148
- /*private*/initializeModel(quorumCells){
15449
+ /*private*/async launchModelPartitionBlobSequence(quorumCells){
15149
15450
 
15150
15451
  const self=this;
15151
15452
 
15152
- const numberOfPartitions=getArraySize(quorumCells);
15453
+ this.numberOfPartitions=getArraySize(quorumCells);
15454
+
15455
+ const controller=this.controller;
15456
+ // const model=this.model;
15457
+
15458
+ const partitionsInstantiationZones=controller.getPartitionsZones(this.numberOfPartitions);
15459
+
15460
+ const firstPartitionZone=getAt(partitionsInstantiationZones,0);
15461
+
15462
+ // We populate and initialize the server cell model, using the first partition zone :
15463
+ const model=await this.doOnModelZonePartitionReception(firstPartitionZone, this.selfOrigin);
15464
+
15465
+ // DBG
15466
+ lognow("DEBUG : MODEL WAS JUST CREATED.");
15467
+
15468
+ // We send the models partition zones objects to the required cells :
15469
+ const qlcRootContainer=self.partiallyPopulatedRootContainer();
15470
+
15471
+ let i=1;
15472
+ foreach(quorumCells, (quorumCell, quorumCellOrigin)=>{
15473
+
15474
+ const modelPartitionZone=getAt(partitionsInstantiationZones,i);
15475
+
15476
+ const message={qlcOrigin: self.selfOrigin, qlcRootContainer:qlcRootContainer, type:"modelPartitionZone", partitionZone:modelPartitionZone};
15477
+
15478
+ // TRACE
15479
+ lognow(`INFO : Server cell ${self.selfOrigin} is sending a partition zone to server cell ${quorumCellOrigin}...`, message);
15480
+
15481
+ self.sendMessageToBlob("protocol", message,
15482
+ {isOriginatingCell:true, destinationCellsOrigins:[quorumCellOrigin],
15483
+ excludeIncomingServersCellsAndClientsInTransmission:true,
15484
+ isRequest:false});
15485
+
15486
+ i++;
15487
+ },(quorumCell,quorumCellOrigin)=>quorumCellOrigin!=self.selfOrigin);
15488
+
15489
+
15490
+ // Each quorum member cell is responsible for a model partition
15491
+ // Then the sattelite cells will be handling the duplication
15492
+
15493
+ }
15494
+
15495
+
15496
+ // HANDLE PARTITION SEQUENCE
15497
+
15498
+ /*private*/handlePartitionSequence(){
15499
+
15500
+ // CAUTION : For *non-QLC* server cells only :
15501
+
15502
+ // 2- We wait to receive the hello request of the incoming cell connection :
15503
+ this.onReceiveMessageListeners["both"]["protocol"]["modelPartitionZone"]={
15504
+ execute:(selfParam, messageParam, serverWrapper, clientSocket)=>{
15505
+
15506
+ // If this cell is in the destinations of this message, we pass it along and then we do what it says :
15507
+ selfParam.sendMessageToBlob("protocol", messageParam);
15508
+
15509
+ if(contains(messageParam.destinationCellsOrigins, selfParam.selfOrigin)){
15510
+
15511
+ // We populate and initialize the server cell model :
15512
+ selfParam.doOnModelZonePartitionReception(messageParam.partitionZone, messageParam.qlcOrigin, messageParam.qlcRootContainer).then(model=>{
15513
+
15514
+ });
15515
+ }
15516
+
15517
+ },
15518
+ listenerMessageType:"modelPartitionZone"
15519
+ };
15520
+
15521
+ }
15522
+
15523
+
15524
+ /*private*/doOnModelZonePartitionReception(partitionZone, qlcOrigin, qlcRootContainer=null){
15525
+
15526
+ return new Promise((resolve,reject)=>{
15527
+
15528
+ // TRACE
15529
+ lognow(`INFO : Applying partition zone for cell ${this.selfOrigin}. Updating local server model...`);
15530
+ lognow(`DEBUG : partitionZone:`, partitionZone);
15531
+
15532
+ this.partitionId=partitionZone.id;
15533
+
15534
+ // TRACE
15535
+ lognow("INFO : Populating model for this partition zone...");
15536
+
15537
+ if(this.isServerCellQuorumLastCell){ // Case QLC :
15538
+
15539
+ // DBG
15540
+ lognow("DEBUG : (this cell is the last quorum server cell (QLC)) ");
15541
+
15542
+ // We initialize the portion of the model :
15543
+ // (controller will handle if a persisted model already exists)
15544
+ // (in this particular case, qlcOrigin and this.selfOrigin is exactly the same !)
15545
+ this.controller.populateModelInZone(this.model, partitionZone, getHashedString(qlcOrigin), getHashedString(this.selfOrigin) ).then((model)=>{
15546
+
15547
+ // DBG
15548
+ lognow("DEBUG : model is populated in QLC server cell.");
15549
+
15550
+ resolve(model);
15551
+ });
15552
+
15553
+ }else{ // Case NON-QLC :
15554
+
15555
+
15556
+ // BASICALLY IT'S THE EXACT SAME TREATMENT AS FOR A BLOB CLIENT !
15557
+
15558
+ // DBG
15559
+ lognow("DEBUG : (this cell is *NOT* last quorum server cell (NON-QLC)) ");
15560
+
15561
+
15562
+ // We initialize the portion of the model :
15563
+ // (controller will handle if a persisted model already exists)
15564
+ this.controller.populateModelInZone(this.model, partitionZone, getHashedString(qlcOrigin), getHashedString(this.selfOrigin), qlcRootContainer ).then((model)=>{
15565
+
15566
+ // DBG
15567
+ lognow("DEBUG : model is populated in NON-QLC server cell.");
15568
+
15569
+ resolve(model);
15570
+ });
15571
+
15572
+ }
15573
+
15574
+
15575
+ });
15576
+
15577
+ }
15578
+
15579
+
15580
+ // ===================================================================================================
15581
+ // CLIENT
15582
+ // ===================================================================================================
15583
+
15584
+ // ==========================================================
15585
+ // HANDLE CLIENT HELLO SEQUENCE
15586
+
15587
+ handleClientHelloSequence(){
15588
+
15589
+ // We wait to receive the hello request of the incoming client connection :
15590
+ this.onReceiveMessageListeners["incoming"]["protocol"]["clientHello"]={
15591
+ execute:(selfParam, messageParam, serverWrapper, clientSocket)=>{
15592
+
15593
+ const clientId=messageParam.clientId;
15594
+ const manifestationZone=messageParam.manifestationZone;
15595
+
15596
+ // TRACE
15597
+ lognow(`INFO : (handleClientHelloSequence()) Incoming client ${clientId} has said hello. Starting its reconnection sequence...`);
15598
+
15599
+ // We send the servers on which the client must reconnect to :
15600
+
15601
+ // Init lobule :
15602
+ selfParam.lobuleZone.clear(clientId);
15603
+ selfParam.lobuleZone.setClientConnectionInfo(clientId, serverWrapper, clientSocket);
15604
+
15605
+ // Caution : client must ignore this server cell if it has no objects for it !
15606
+ let allObjectsCorrespondingToZoneInServerCell=[];
15607
+ try{
15608
+ selfParam.model.checkAllMethodsCustomModelClassPrerequisites();
15609
+ allObjectsCorrespondingToZoneInServerCell=selfParam.model.getAllPartitionnableObjectsInZone(manifestationZone);
15610
+ }catch(e){
15611
+ //TRACE
15612
+ lognow(e);
15613
+ }
15614
+
15615
+ if(!empty(allObjectsCorrespondingToZoneInServerCell)){
15616
+ selfParam.lobuleZone.append(clientId, [selfParam.selfOrigin], "serversBag");
15617
+ selfParam.lobuleZone.append(clientId, allObjectsCorrespondingToZoneInServerCell);
15618
+ }else{
15619
+ // TRACE
15620
+ lognow("INFO : Server cell has no relevant objects to send to client");
15621
+ }
15622
+
15623
+ // The QLC in all cases, sends its structural objects to client :
15624
+ if(selfParam.isServerCellQuorumLastCell){
15625
+ // Case this server cell is the QLC :
15626
+ const rootContainer=selfParam.partiallyPopulatedRootContainer(allObjectsCorrespondingToZoneInServerCell);
15627
+ // Then we send the structure and, if any, the concerned objects in client manifestation zone of this server cell :
15628
+ selfParam.lobuleZone.append(clientId, [rootContainer]);
15629
+ }
15630
+
15631
+ // At this point , in the QLC server cell's lobule zone for this client we have :
15632
+ // (- maybe its concerned zone objects.)
15633
+ // - its root container
15634
+
15635
+
15636
+ // We ask the blob
15637
+ selfParam.sendMessageToBlob("protocol", {type:"getServersForZone", clientId:clientId, manifestationZone:manifestationZone},
15638
+ {isOriginatingCell:true,
15639
+ excludeIncomingServersCellsAndClientsInTransmission:true,
15640
+ isRequest:true,
15641
+ contactAllQuorumBlob:true})
15642
+ .thenOnAnyResponseFromBlobRequestingCellOnly((selfParam2, messageParamLocal)=>{
15643
+ // Triggered every time this server cell receives a response to its request from blob :
15644
+
15645
+ const clientIdLocal=messageParamLocal.clientId;
15646
+ const answeringServerOrigin=messageParamLocal.answeringServerOrigin;
15647
+ const objectsCount=messageParamLocal.objectsCount;
15648
+
15649
+ // DBG
15650
+ lognow("DEBUG : This server cell has received a response from another server cell : answeringServerOrigin:",answeringServerOrigin);
15651
+
15652
+ // When another server cells answers to this server cell :
15653
+
15654
+ if(!objectsCount || objectsCount<=0){
15655
+ // TRACE
15656
+ lognow("INFO : No objects corresponding to this zone to send to collector server cell. This server cell must be ignored.");
15657
+ }else{
15658
+ // We accumulate the server origin in the lobule :
15659
+ selfParam2.lobuleZone.append(clientIdLocal, [answeringServerOrigin], "serversBag");
15660
+ }
15661
+
15662
+
15663
+ }).doOnceAllBlobHasBeenContacted((selfParam2, messageParamLocal)=>{
15664
+
15665
+ // Triggered once the requesting server cell has received the response from the last possible other server cell, according to the partition ids !
15666
+
15667
+ const clientIdLocal=messageParamLocal.clientId;
15668
+
15669
+ // At this point we should have all the needed servers in the lobule :
15670
+ const servers=selfParam2.lobuleZone.getServers(clientIdLocal);
15671
+ // CAUTION : THE QLC ALWAYS SENDS AT LEAST THE ROOTCONTAINER (during the «client hello» phase) !!!
15672
+ const objects=selfParam2.lobuleZone.getObjects(clientIdLocal);
15673
+
15674
+ // ???
15675
+ // if(!empty(objects)){
15676
+ // selfParam2.removeLinks(objects);
15677
+ // }
15678
+
15679
+ // CAUTION : THE QLC ALWAYS SENDS AT LEAST THE ROOTCONTAINER (during the «client hello» phase) !!!
15680
+ const messageWrapped=JSON.decycle({type:"clientHello.response", servers:servers, objects:objects});
15153
15681
 
15154
- const controller=this.controller;
15155
- const model=this.model;
15156
-
15157
-
15158
- // We initialize the whole model :
15159
- // (controller will handle if a persisted model exists)
15160
- controller.initializeModelForAORTACNode(model);
15161
-
15162
- // This must return the asked number of partitions, indexed by partition id :
15163
- // CAUTION : The partition function MUST return ALL the objects in a partition, WHATEVER THEIR NESTING LEVEL !!!
15164
- const modelPartitions=model.getPartitions(numberOfPartitions);
15682
+ // DBG
15683
+ lognow("DEBUG : All blob has been contacted : messageWrapped:",messageWrapped);
15684
+
15685
+ // We send back all the collected servers to the requesting client :
15686
+ const clientConnectionInfo=selfParam2.lobuleZone.getClientConnectionInfo(clientIdLocal);
15687
+ clientConnectionInfo.serverWrapper.server.send("protocol", messageWrapped, null, clientConnectionInfo.clientSocket);
15165
15688
 
15166
- // We exclude this cell from the partition sending, because we already have the model partition at hand.
15167
- this.removeLinks(modelPartitions[0].objects);
15168
- this.doOnModelPartitionReception(modelPartitions[0]);
15169
-
15170
- // We send the models objects to the required cells :
15171
- let i=1;
15172
- foreach(quorumCells, (quorumCell,quorumCellOrigin)=>{
15689
+ selfParam2.lobuleZone.clear(clientIdLocal);
15690
+
15691
+ });
15692
+
15173
15693
 
15174
- const modelPartition=modelPartitions[i];
15175
- self.removeLinks(modelPartition.objects);
15176
-
15177
- const message=JSON.decycle({type:"modelPartition",partition:modelPartition});
15178
-
15179
- // TRACE
15180
- lognow(`INFO : Cell ${self.selfOrigin} is sending a partition to cell ${quorumCellOrigin}...`,message);
15181
-
15182
- self.sendMessageToBlob("protocol", message,
15183
- {isOriginatingCell:true, destinationCellsOrigins:[quorumCellOrigin], includeIncomingConnectionInTransmission:false, isRequest:false});
15694
+ },
15695
+ listenerMessageType:"clientHello"
15696
+ };
15184
15697
 
15185
- i++;
15186
- },(quorumCell,quorumCellOrigin)=>quorumCellOrigin!=self.selfOrigin);
15187
-
15188
-
15189
- // Each quorum member cell is responsible for a model partition
15190
- // Then the sattelite cells will be handling the duplication
15191
-
15192
15698
  }
15193
-
15194
15699
 
15195
- // HANDLE PARTITION SEQUENCE
15196
15700
 
15197
- /*private*/handlePartitionSequence(){
15701
+ // This is what other cells do when they receive a request for «getServersForZone» from a cell in the blob
15702
+ /*private*/handleGetServersForZoneRequest(){
15198
15703
 
15199
- // 2- We wait to receive the hello request of the incoming cell connection :
15200
- getOrCreateEmptyAttribute(
15201
- this.onReceiveMessageListeners["both"]["protocol"],"modelPartition")["forRequestsIssuedByOthers"]={
15202
- execute:(selfParam, messageParam, server, clientSocket)=>{
15704
+ this.onReceiveMessageListeners["both"]["protocol"]["getServersForZone"]={
15705
+ execute:(selfParam, messageParam, serverWrapper, clientSocket)=>{
15706
+
15707
+ // DBG
15708
+ lognow("DEBUG : handleGetServersForZoneRequest() : messageParam", messageParam);
15709
+
15710
+ const clientId=messageParam.clientId;
15711
+ const manifestationZone=messageParam.manifestationZone;
15712
+
15713
+ // When we receive a request from a server cell :
15714
+ let allObjectsCorrespondingToZoneInServerCell=[];
15715
+ try{
15716
+ selfParam.model.checkAllMethodsCustomModelClassPrerequisites();
15717
+ allObjectsCorrespondingToZoneInServerCell=selfParam.model.getAllPartitionnableObjectsInZone(manifestationZone);
15718
+ }catch(e){
15719
+ // TRACE
15720
+ lognow(e);
15721
+ }
15722
+
15723
+ // We store the objects for this client, because we know it will very soon ask them :
15724
+ // Init lobule :
15725
+ selfParam.lobuleZone.clear(clientId);
15726
+ selfParam.lobuleZone.setClientConnectionInfo(clientId, serverWrapper, clientSocket);
15727
+
15728
+ selfParam.lobuleZone.append(clientId, allObjectsCorrespondingToZoneInServerCell);
15729
+
15203
15730
 
15204
- // If this cell is in the destinations of this message, we pass it along and then we do what it says :
15205
- selfParam.sendMessageToBlob("protocol",messageParam);
15731
+ const message={type:"getServersForZone.response", objectsCount:allObjectsCorrespondingToZoneInServerCell.length, answeringServerOrigin:selfParam.selfOrigin};
15206
15732
 
15207
- if(contains(messageParam.destinationCellsOrigins,selfParam.selfOrigin)){
15208
-
15209
- const message=JSON.recycle(messageParam);
15210
-
15211
- selfParam.doOnModelPartitionReception(message.partition);
15212
-
15213
- }
15733
+ // TRACE
15734
+ lognow(`INFO : Server cell ${selfParam.selfOrigin} is sending its origin to server cell ${messageParam.originatingCellOrigin}...: message:`, message);
15214
15735
 
15215
- },
15216
- listenerMessageType:"modelPartition"
15736
+ selfParam.replyToBlobRequest("protocol", messageParam, message);
15737
+ },listenerMessageType:"getServersForZone"
15217
15738
  };
15218
15739
 
15219
15740
  }
15220
15741
 
15221
15742
 
15222
- /*private*/doOnModelPartitionReception(partition){
15223
-
15224
- // TRACE
15225
- lognow(`INFO : Incoming partition for cell ${this.selfOrigin}. Updating local model...`,partition);
15226
- lognow(`>>>>`,stringifyObject(JSON.decycle(partition.objects),1));
15743
+ // ----
15744
+ // HANDLE RECONNECTED CLIENT HELLO SEQUENCE
15745
+ handleReconnectedClientHelloSequence(){
15227
15746
 
15747
+ // Here we only want to collect the servers on which to reconnect to later :
15228
15748
 
15749
+ // 2- We wait to receive the hello request of the incoming client connection :
15750
+ this.onReceiveMessageListeners["incoming"]["protocol"]["reconnectedClientHello"]={
15751
+ execute:(selfParam, messageParam, serverWrapper, clientSocket)=>{
15752
+
15753
+ const clientId=messageParam.clientId;
15754
+
15755
+ // TRACE
15756
+ lognow(`INFO : Incoming reconnected client ${clientId} has said hello. Updating its local information...`);
15757
+
15758
+ selfParam.clients[clientId]={
15759
+ connected:true,
15760
+ server:serverWrapper,
15761
+ // DO NOT USE TO SEND/RECEIVE ANYTHINIG !
15762
+ // For this, use the server attribute (+ the clientSocket as argument) instead.
15763
+ clientSocket:clientSocket,
15764
+ };
15765
+
15766
+ const objects=selfParam.lobuleZone.getObjects(clientId);
15767
+
15768
+ // RECONNECTED CLIENT HELLO CONFIRMATION
15769
+
15770
+
15771
+ const messageResponse={type:"reconnectedClientHello.response", objects:objects};
15772
+ const messageResponseWrapped=JSON.decycle(messageResponse);
15773
+
15774
+ // DBG
15775
+ lognow("DEBUG : Sending «reconnectedClientHello.response»...");
15776
+
15777
+ serverWrapper.server.send("protocol", messageResponseWrapped, null, clientSocket);
15778
+
15779
+ selfParam.lobuleZone.clear(clientId);
15780
+
15781
+ },
15782
+ listenerMessageType:"reconnectedClientHello"
15783
+ };
15784
+
15229
15785
 
15230
15786
  }
15231
15787
 
15232
15788
 
15233
- /*private*/removeLinks(linkedObjects, currentObject=null){
15234
-
15235
- const self=this;
15236
-
15237
- if(!currentObject){
15238
- foreach(linkedObjects, obj=>{
15239
- self.removeLinksOnSingleObject(linkedObjects, obj);
15240
- });
15241
- }else{
15242
- self.removeLinksOnSingleObject(linkedObjects, currentObject);
15243
- }
15244
-
15245
- return linkedObjects;
15246
- }
15789
+ // ==========================================================
15247
15790
 
15248
- /*private*/removeLinksOnSingleObject(linkedObjects, currentObject){
15791
+
15792
+ // ???
15793
+ // /*private*/removeLinks(linkedObjects, currentObject=null){
15794
+ // const self=this;
15795
+ // if(!currentObject){
15796
+ // foreach(linkedObjects, obj=>{
15797
+ // self.removeLinksOnSingleObject(linkedObjects, obj);
15798
+ // });
15799
+ // }else{
15800
+ // self.removeLinksOnSingleObject(linkedObjects, currentObject);
15801
+ // }
15802
+ // return linkedObjects;
15803
+ // }
15804
+ //
15805
+ // ???
15806
+ // /*private*/removeLinksOnSingleObject(linkedObjects, currentObject){
15807
+ // const self=this;
15808
+ // foreach(currentObject, (attr,attrNameOrIndex)=>{
15809
+ // // We only remove links to the objects not in the partition
15810
+ // if(isClassObject(attr)){
15811
+ // let aortacId=attr.aortacId;
15812
+ // if(!aortacId){
15813
+ // aortacId=getUUID();
15814
+ // attr.aortacId=aortacId;
15815
+ // }
15816
+ // if(!contains(linkedObjects,attr)){
15817
+ // currentObject[attrNameOrIndex]=aortacId+"@aortacId";
15818
+ // // CAUTION : No need for recursive call here, because the partition function returns ALL the objects in a partition,
15819
+ // // WHATEVER THEIR NESTING LEVEL !!!
15820
+ // }
15821
+ // }else{
15822
+ // // However, we need a recursive call for any simple object or array that may reference a class object in another partition :
15823
+ // self.removeLinksOnSingleObject(linkedObjects, attr);
15824
+ // }
15825
+ // },obj=>(isObject(obj) || isArray(obj)));
15826
+ // }
15249
15827
 
15250
- const self=this;
15251
- foreach(currentObject, (attr,attrNameOrIndex)=>{
15252
- // We only remove links to the objects not in the partition
15253
- if(isClassObject(attr)){
15254
- let aortacId=attr.aortacId;
15255
- if(!aortacId){
15256
- aortacId=getUUID();
15257
- attr.aortacId=aortacId;
15258
- }
15259
- if(!contains(linkedObjects,attr)){
15260
- currentObject[attrNameOrIndex]=aortacId+"@aortacId";
15261
- // CAUTION : No need for recursive call here, because the partition function returns ALL the objects in a partition,
15262
- // WHATEVER THEIR NESTING LEVEL !!!
15263
- }
15264
- }else{
15265
- // However, we need a recursive call for any simple object or array that may reference a class object in another partition :
15266
- self.removeLinksOnSingleObject(linkedObjects, attr);
15267
- }
15268
- },obj=>(isObject(obj) || isArray(obj)));
15269
15828
 
15270
- }
15271
15829
 
15272
15830
 
15273
- // ******************************************************************
15274
-
15275
- /*private*/collectDependencies(inputObjects){
15831
+ // **********************************************************************
15832
+
15833
+ /*private*/partiallyPopulatedRootContainer(allObjectsCorrespondingToZoneInServerCell=[]){
15276
15834
 
15277
- ////
15278
- lognow("DEBUG : collectDependencies()...",inputObjects);
15835
+ const rootContainerLocal=this.model.getRootContainer();
15279
15836
 
15837
+ const rootContainer=this.model.populateRootContainerWithObjectsSubset(rootContainerLocal, allObjectsCorrespondingToZoneInServerCell, true);
15838
+ rootContainer.isRootContainer=true;
15280
15839
 
15840
+ return rootContainer;
15281
15841
  }
15282
-
15283
- /*private*/repercutChangesIfNeeded(inputObjects){
15284
-
15285
-
15286
- ////
15287
- lognow("DEBUG : repercutChangesIfNeeded()...",inputObjects);
15288
15842
 
15289
-
15290
- }
15291
-
15292
-
15293
15843
  }
15294
15844
 
15295
15845
 
15296
15846
 
15297
- // ******************************************************************
15298
15847
 
15299
- // Public static hydration method :
15300
- window.ao=(incompleteModelObjectToDecorate)=>{
15301
-
15302
- const localServerCell=window.aortacCServerNodeInstance;
15303
- if(!localServerCell){
15304
- // TRACE
15305
- lognow("ERROR : No AORTACCServerNode singleton instance. Doing nothing on the model object.");
15306
- return incompleteModelObjectToDecorate;
15307
- }
15308
-
15309
- const liveModelObjects=localServerCell.liveModelObjects;
15310
-
15311
- // First we clone the object, for its attriutes values information :
15312
- const clonedObject=clone(incompleteModelObjectToDecorate);
15313
-
15314
- // Then we replace all its methods :
15315
- foreach(clonedObject, (method, methodName)=>{
15316
- clonedObject[methodName]=new Proxy(method,
15317
- {
15318
- apply: function(methodToEnhance, thisArg, argumentsList) {
15319
15848
 
15320
- const inputObjects=[thisArg];
15321
- inputObjects.push(...argumentsList);
15322
- localServerCell.collectDependencies(inputObjects);
15323
-
15324
- // // --- Treatment BEFORE function execution ---
15325
- // console.log(`Calling function "${methodToEnhance.name}" with arguments: ${argumentsList}`);
15326
-
15327
- // Call the original function (target) with its intended 'this' context (thisArg)
15328
- // and arguments (argumentsList) using Reflect.apply
15329
- const result = Reflect.apply(methodToEnhance, thisArg, argumentsList);
15330
-
15331
- // // --- Treatment AFTER function execution ---
15332
- // console.log(`Function "${methodToEnhance.name}" returned: ${result}`);
15333
-
15334
- inputObjects.push(result);
15335
- localServerCell.repercutChangesIfNeeded(inputObjects);
15336
15849
 
15337
- return result;
15338
- },
15339
- }
15340
- );
15341
- },attribute=>isFunction(attribute));
15850
+
15851
+
15852
+
15853
+ class ServerCellLobule{
15342
15854
 
15343
- return clonedObject;
15344
- };
15855
+ constructor(){
15856
+ this.serversBag=null;
15857
+ this.objectsBag=null;
15858
+ this.clear();
15859
+ this.clientsConnectionsInfos={};
15860
+ }
15861
+ append(clientId, objs, attributeName="objectsBag"){
15862
+ const self=this;
15863
+ if(!this[attributeName][clientId])
15864
+ this[attributeName][clientId]=[];
15865
+ foreach(objs,obj=>{
15866
+ self[attributeName][clientId].push(obj);
15867
+ },(obj)=>(!contains(this[attributeName][clientId], obj)));
15868
+ }
15869
+ getObjects(clientId, filterFunction=null){
15870
+ if(!filterFunction) return this.objectsBag[clientId];
15871
+ if(!this.objectsBag[clientId]) return null;
15872
+ const results=[];
15873
+ foreach(this.objectsBag[clientId], (obj)=>{
15874
+ results.push(obj);
15875
+ },filterFunction);
15876
+ return results;
15877
+ }
15878
+ getServers(clientId){
15879
+ if(!this.serversBag[clientId]) return null;
15880
+ return this.serversBag[clientId];
15881
+ }
15882
+ clear(clientId=null, attributeName=null){
15883
+ if(!attributeName){
15884
+ if(!clientId){
15885
+ this.serversBag={};
15886
+ this.objectsBag={};
15887
+ }else{
15888
+ this.serversBag[clientId]=[];
15889
+ this.objectsBag[clientId]=[];
15890
+ delete this.clientsConnectionsInfos[clientId];
15891
+ }
15892
+ }else{
15893
+ if(!clientId){
15894
+ this[attributeName]={};
15895
+ }else{
15896
+ this[attributeName][clientId]=[];
15897
+ delete this.clientsConnectionsInfos[clientId];
15898
+ }
15899
+ }
15900
+
15901
+ //DBG
15902
+ if(clientId) lognow("DEBUG : CLEAR() FOR clientId:",clientId);
15903
+
15904
+ return this;
15905
+ }
15906
+ remove(clientId){
15907
+ delete this.serversBag[clientId];
15908
+ delete this.objectsBag[clientId];
15909
+ delete this.clientsConnectionsInfos[clientId];
15910
+ }
15911
+ getClientConnectionInfo(clientId){
15912
+ return this.clientsConnectionsInfos[clientId];
15913
+ }
15914
+ setClientConnectionInfo(clientId, serverWrapper, clientSocket){
15915
+ this.clientsConnectionsInfos[clientId]={serverWrapper:serverWrapper, clientSocket:clientSocket};
15916
+ }
15917
+ }
15345
15918
 
15346
15919
 
15347
15920
 
15348
- window.getAORTACServerNode=function(quorumNumber=1, selfOrigin="ws://127.0.0.1:40000", outcomingCellsOrigins=[], model, controller){
15921
+ window.getAORTACServerCell=function(quorumNumber=1, selfOrigin="ws://127.0.0.1:40000", outcomingCellsOrigins=[], model, controller){
15349
15922
  //return new AORTACServerNode("node_"+getUUID("short"), selfOrigin, outcomingCellsOrigins, model, controller);
15350
- if(window.aortacCServerNodeInstance){
15923
+ if(window.aortacCServerCellInstance){
15351
15924
  // TRACE
15352
- throw new Error("ERROR : The AORTACCServerNode singleton instance already exists. It cannot be instantiated again in the same process. Aborting.");
15925
+ throw new Error("ERROR : The aortacCServerCellInstance singleton instance already exists. It cannot be instantiated again in the same process. Aborting.");
15353
15926
  }
15354
- window.aortacCServerNodeInstance=new AORTACServerCell(quorumNumber, selfOrigin, outcomingCellsOrigins, model, controller);
15355
- return window.aortacCServerNodeInstance;
15927
+ window.aortacCServerCellInstance=new AORTACServerCell(quorumNumber, selfOrigin, outcomingCellsOrigins, model, controller);
15928
+ return window.aortacCServerCellInstance;
15356
15929
  }
15357
15930
 
15358
15931
 
15359
15932
 
15360
15933
 
15934
+ // ===============================================================================================================================
15935
+ // ===============================================================================================================================
15936
+ // UTILITY METHODS
15361
15937
 
15362
15938
 
15939
+ // USAGE EXAMPLE :
15363
15940
 
15364
- // ==================================================================================================================
15365
15941
 
15942
+ // 4 tests only :
15943
+ //class PricingService {
15944
+ // constructor(currency) {
15945
+ // this.currency = currency;
15946
+ // this.callCount = 0;
15947
+ // }
15948
+ //
15949
+ // getPrice(eventId) {
15950
+ // this.callCount++;
15951
+ // return { eventId, amount: 89.99, currency: this.currency };
15952
+ // }
15953
+ //
15954
+ // async updatePrice(eventId, amount) {
15955
+ // await new Promise(r => setTimeout(r, 10)); // simulate async
15956
+ // return { eventId, amount, saved: true };
15957
+ // }
15958
+ //}
15959
+ //
15960
+ //proxyClass(PricingService);
15961
+ //const proxiedClassInstance=new PricingService("CAD");
15962
+ //proxiedClassInstance.currency; // [GET] PricingService.currency{ value: "CAD" }
15963
+ //proxiedClassInstance.currency="USD"; // [SET] PricingService.currency{ value: "USD" }
15964
+ //proxiedClassInstance.getPrice("evt-42"); // [CALL] PricingService.getPrice(){ args: ["evt-42"], result:{...} }
15965
+ //proxiedClassInstance.callCount; // [GET] PricingService.callCount{ value: 1 }
15966
+ //await proxiedClassInstance.updatePrice("evt-42", 120); // [CALL] PricingService.updatePrice(){ args: [...], result:{...} }
15967
+ //restoreNonProxiedClass(PricingService); // undo — new PricingService() works normally again
15366
15968
 
15367
- // AORTAC CLIENT
15368
15969
 
15970
+ window.proxyClass=function(classToProxy,
15971
+ doOnAccess=(className, propertyName, parameter)=>{lognow(`Accessing ${className}.${propertyName}:`,parameter);}){
15369
15972
 
15973
+ const className=classToProxy.name;
15370
15974
 
15371
- //*********************************** AUTO-ORGANIZING REAL-TIME AORTAC CLUSTERIZATION (AORTAC) *********************************** */
15975
+ // Build the Proxy handler
15976
+ const proxyHandler={
15372
15977
 
15373
- // New implementation :
15978
+ // Intercepts: obj.property AND obj.method()
15979
+ get(concernedObject, property, thisArg){
15980
+ const value=Reflect.get(concernedObject, property, thisArg);
15374
15981
 
15375
- class AORTACClientCell{
15982
+ // We skip symbols (ex. Symbol.toPrimitive) — not meaningful to intercept
15983
+ if(typeof(property)!=="string") return value;
15984
+
15985
+ if(typeof(value)==="function"){
15986
+
15987
+ // Return a wrapper so we can intercept args + return value at call time
15988
+ return function(...args){
15989
+ const result=value.apply(concernedObject, args); // `concernedObject` keeps correct `this`
15990
+
15991
+ if(result instanceof Promise){
15992
+ // Async method — intercept after the promise settles
15993
+ return result
15994
+ .then(v=>{ doOnAccess(className, property, { args, result: v }); return v; })
15995
+ .catch(e=>{ doOnAccess(className, property, { args, error: e }); throw e; });
15996
+ }
15997
+
15998
+ // Sync method — intercept immediately
15999
+ doOnAccess(className, property, { args, result }/*(SYNTAX : Equivalent to { args:args, result:result })*/);
16000
+ return result;
16001
+ };
16002
+ }
16003
+
16004
+ // Plain attribute read
16005
+ doOnAccess(className, property, { value });
16006
+ return value;
16007
+ },
16008
+
16009
+ // Intercepts: obj.property=value
16010
+ set(concernedObject, property, valueToSet, thisArg){
16011
+ if(typeof(property)==="string")
16012
+ doOnAccess(className, property, { valueToSet });
16013
+ return Reflect.set(concernedObject, property, valueToSet, thisArg);
16014
+ },
16015
+ };
16016
+
16017
+ // Patch the constructor
16018
+ // Save the original so we can restore later
16019
+ const originalConstructor=classToProxy.prototype.constructor;
16020
+ classToProxy.prototype.originalConstructor=originalConstructor;
16021
+
16022
+ // Override the constructor: it runs first, then we wrap `this` in a Proxy
16023
+ classToProxy.prototype.constructor=function(...args){
16024
+ classToProxy.prototype.originalConstructor.apply(this, args); // run original setup
16025
+ return new Proxy(this, proxyHandler); // return proxy instead of raw `this`
16026
+ };
16027
+
16028
+ // Keep the name and prototype intact so instanceof still works
16029
+ Object.defineProperty(classToProxy.prototype.constructor, "name", { value: className });
16030
+ classToProxy.prototype.constructor.prototype=classToProxy.prototype;
16031
+
16032
+ return classToProxy;
16033
+ };
16034
+
16035
+ // Return a restore function
16036
+ window.restoreNonProxiedClass=function(classToProxy){
16037
+ if(!classToProxy.prototype.originalConstructor){
16038
+ // TRACE
16039
+ lognow("WARN : Class has not been proxied. We do nothing. Concerned class:",classToProxy);
16040
+ return;
16041
+ }
16042
+ classToProxy.prototype.constructor=classToProxy.prototype.originalConstructor;
16043
+ };
16044
+
16045
+
16046
+
16047
+
16048
+ // ******************************************************************
16049
+
16050
+ window.getCurrentLevelConfigForAORTACServerCell=()=>{
16051
+ const allPrototypesConfig=PROTOTYPES_CONFIG.allPrototypes;
16052
+ const levelConfig=allPrototypesConfig.GameLevel;
16053
+
16054
+ const currentLevelPrototypeName=getCurrentLevelPrototypeNameForAORTACServerCell();
16055
+ if(nothing(currentLevelPrototypeName)){
16056
+ // TRACE
16057
+ lognow("WARN : No currentLevelPrototypeName found. Aborting.");
16058
+ return null;
16059
+ }
16060
+ const referenceCurrentLevelConfig=levelConfig[currentLevelPrototypeName];
16061
+ return referenceCurrentLevelConfig;
16062
+ };
16063
+
16064
+ window.getCurrentLevelPrototypeNameForAORTACServerCell=()=>{
15376
16065
 
15377
- constructor(serverNodeOrigin, model, view, isNodeContext=false){
16066
+ const aortacConfig=GAME_CONFIG.aortac;
16067
+ if(nothing(aortacConfig)){
16068
+ // TRACE
16069
+ lognow("WARN : No AORTAC config, Aborting..");
16070
+ return null;
16071
+ }
15378
16072
 
16073
+ const allPrototypesConfig=PROTOTYPES_CONFIG.allPrototypes;
16074
+ const levelConfig=allPrototypesConfig.GameLevel;
16075
+
16076
+ let currentLevelPrototypeName=aortacConfig.startingLevelPrototypeName;
16077
+ if(nothing(currentLevelPrototypeName)){
16078
+ currentLevelPrototypeName=getKeyAt(levelConfig,0);
16079
+ // TRACE
16080
+ lognow("WARN : No startingLevelPrototypeName specified in AORTAC config, using first level name as default «"+currentLevelPrototypeName+"».");
15379
16081
  }
15380
16082
 
15381
- }
16083
+ return currentLevelPrototypeName;
16084
+ };
15382
16085
 
15383
16086
 
15384
16087
 
15385
16088
 
15386
- window.getAORTACClient=function(serverNodeOrigin="ws://127.0.0.1:40000", model, view, isNodeContext=false){
15387
- //return new AORTACClient("client_"+getUUID(), serverNodeOrigin, model, view, isNodeContext);
15388
- return new AORTACClientCell(serverNodeOrigin, model, view, isNodeContext);
15389
- }
15390
16089
 
16090
+ //
16091
+ //// Public static hydration method :
16092
+ //window.ao=(incompleteModelObjectToDecorate)=>{
16093
+ //
16094
+ // const localServerCell=window.aortacCServerCellInstance;
16095
+ // if(!localServerCell){
16096
+ // // TRACE
16097
+ // lognow("ERROR : No AORTACCServerNode singleton instance. Doing nothing on the model object.");
16098
+ // return incompleteModelObjectToDecorate;
16099
+ // }
16100
+ //
16101
+ // const liveModelObjects=localServerCell.liveModelObjects;
16102
+ //
16103
+ // // First we clone the object, for its attriutes values information :
16104
+ // const clonedObject=clone(incompleteModelObjectToDecorate);
16105
+ //
16106
+ // // Then we replace all its methods :
16107
+ // foreach(clonedObject, (method, methodName)=>{
16108
+ // clonedObject[methodName]=new Proxy(method,
16109
+ // {
16110
+ // apply: function(methodToEnhance, thisArg, argumentsList){
16111
+ //
16112
+ // const inputObjects=[thisArg];
16113
+ // inputObjects.push(...argumentsList);
16114
+ // localServerCell.collectDependencies(inputObjects);
16115
+ //
16116
+ //// // --- Treatment BEFORE function execution ---
16117
+ //// console.log(`Calling function "${methodToEnhance.name}" with arguments: ${argumentsList}`);
16118
+ //
16119
+ // // Call the original function(target) with its intended "this" context (thisArg)
16120
+ // // and arguments (argumentsList) using Reflect.apply
16121
+ // const result=Reflect.apply(methodToEnhance, thisArg, argumentsList);
16122
+ //
16123
+ //// // --- Treatment AFTER function execution ---
16124
+ //// console.log(`Function "${methodToEnhance.name}" returned: ${result}`);
16125
+ //
16126
+ // inputObjects.push(result);
16127
+ // localServerCell.repercutChangesIfNeeded(inputObjects);
16128
+ //
16129
+ // return result;
16130
+ // },
16131
+ // }
16132
+ // );
16133
+ // },attribute=>isFunction(attribute));
16134
+ //
16135
+ // return clonedObject;
16136
+ //};
15391
16137
 
15392
16138
 
15393
16139
 
@@ -16103,7 +16849,7 @@ WebsocketImplementation={
16103
16849
  },
16104
16850
 
16105
16851
 
16106
- /*private*/getMessageDataBothImplementations:(eventOrMessageParam)=>{
16852
+ /*public*//*static*/getMessageDataBothImplementations:(eventOrMessageParam)=>{
16107
16853
 
16108
16854
  const eventOrMessage=(!WebsocketImplementation.useSocketIOImplementation?eventOrMessageParam.data:eventOrMessageParam);
16109
16855
 
@@ -16300,12 +17046,11 @@ WebsocketImplementation={
16300
17046
 
16301
17047
 
16302
17048
 
16303
- launchNodeHTTPServer=function(port, doOnConnect=null, doOnFinalizeServer=null, /*OPTIONAL*/sslOptions=null, httpHandlerParam=null, addCORSHeader=ADD_CORS_HEADER){
17049
+ launchNodeHTTPServer=function(port, doOnConnect=null, doOnFinalizeServer=null, doOnConnectionLost=null, /*OPTIONAL*/sslOptions=null, httpHandlerParam=null, addCORSHeader=ADD_CORS_HEADER){
16304
17050
 
16305
17051
  const EXCLUDED_FILENAMES_PARTS=[".keyHash.",".pem"];
16306
17052
 
16307
17053
 
16308
-
16309
17054
  if(typeof(https)==="undefined"){
16310
17055
  // TRACE
16311
17056
  console.log("«https» SERVER library not called yet, calling it now.");
@@ -16397,8 +17142,6 @@ launchNodeHTTPServer=function(port, doOnConnect=null, doOnFinalizeServer=null, /
16397
17142
 
16398
17143
  const handler=nonull(httpHandlerParam, DEFAULT_HANDLER);
16399
17144
 
16400
-
16401
-
16402
17145
  let listenableServer;
16403
17146
  if(sslOptions){
16404
17147
  let httpsServer=https.createServer(sslOptions, handler).listen(port);
@@ -16419,7 +17162,7 @@ launchNodeHTTPServer=function(port, doOnConnect=null, doOnFinalizeServer=null, /
16419
17162
  // CAUTION : MUST BE CALLED ONLY ONCE !
16420
17163
  server.onConnectionToClient((serverParam, clientSocketParam)=>{
16421
17164
  if(doOnConnect) doOnConnect(serverParam, clientSocketParam);
16422
- });
17165
+ }, doOnConnectionLost);
16423
17166
 
16424
17167
 
16425
17168
  server.onFinalize((serverParam)=>{
@@ -16434,15 +17177,13 @@ launchNodeHTTPServer=function(port, doOnConnect=null, doOnFinalizeServer=null, /
16434
17177
  // TRACE
16435
17178
  console.log("INFO : SERVER : Generic Nodejs server launched and listening on port:" + port + "!");
16436
17179
 
16437
-
16438
-
16439
17180
 
16440
17181
 
16441
17182
  return server;
16442
17183
  }
16443
17184
 
16444
17185
 
16445
- initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFinalizeServer=null,
17186
+ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFinalizeServer=null, doOnConnectionLost=null,
16446
17187
  /*OPTIONAL*/portParam,
16447
17188
  /*OPTIONAL*/certPathParam,
16448
17189
  /*OPTIONAL*/keyPathParam){
@@ -16495,13 +17236,11 @@ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFina
16495
17236
 
16496
17237
 
16497
17238
 
16498
-
16499
-
16500
17239
  const aotraNodeServer={config:serverConfig};
16501
17240
  aotraNodeServer.serverManager={ start:()=>{/*DEFAULT START FUNCTION, WILL BE OVERRIDEN LATER*/}};
16502
17241
 
16503
17242
  if(isHashAsked){
16504
- // We instanciate a temporary persister just to read the key hash file:
17243
+ // We instantiate a temporary persister just to read the key hash file:
16505
17244
  const persister=getPersister("./");
16506
17245
  let persisterIdSplits=persisterId.split("@");
16507
17246
  if(empty(persisterIdSplits) || persisterIdSplits.length!=2){
@@ -16584,9 +17323,9 @@ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFina
16584
17323
  }
16585
17324
  }
16586
17325
 
16587
- aotraNodeServer.server=launchNodeHTTPServer(port, doOnClientConnection, doOnFinalizeServer, sslOptions);
17326
+ aotraNodeServer.server=launchNodeHTTPServer(port, doOnClientConnection, doOnFinalizeServer, doOnConnectionLost, sslOptions);
17327
+
16588
17328
 
16589
-
16590
17329
  return aotraNodeServer;
16591
17330
  };
16592
17331
 
@@ -16649,7 +17388,10 @@ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFina
16649
17388
  // },REFRESH_SCREENSHOTS_MILLIS);
16650
17389
  //
16651
17390
  //
16652
- // },portParam,certPathParam,keyPathParam);
17391
+ // },
17392
+ // // On client connection lost :
17393
+ // (clientId)=>{lognow(`INFO : Connection to client id «${clientId}» was lost.`);},
17394
+ // portParam,certPathParam,keyPathParam);
16653
17395
  //
16654
17396
  //
16655
17397
  // // const doOnConnect=(serverParam, clientSocketParam)=>{
@@ -17117,8 +17859,8 @@ initClient=function(isNodeContext=true, useSocketIOImplementation=/*DEBUG*/false
17117
17859
  // CAUTION : MUST BE CALLED ONLY ONCE !
17118
17860
  socketToServerClientInstance.onConnectionToServer(()=>{
17119
17861
  if(doOnServerConnection){
17120
- if(selfParam) doOnServerConnection.apply(selfParam,[socketToServerClientInstance]);
17121
- else doOnServerConnection(socketToServerClientInstance);
17862
+ if(selfParam) doOnServerConnection.apply(selfParam,[socketToServerClientInstance, aotraClient]);
17863
+ else doOnServerConnection(socketToServerClientInstance, aotraClient);
17122
17864
  }
17123
17865
 
17124
17866
 
@@ -17215,8 +17957,9 @@ class ClientReceptionEntryPoint{
17215
17957
 
17216
17958
 
17217
17959
  // We check if the message matches the required message type :
17218
- if( this.listenerConfig && this.listenerConfig.listenerMessageType && dataWrapped.type
17219
- && this.listenerConfig.listenerMessageType!==dataWrapped.type){
17960
+ if( this.listenerConfig && this.listenerConfig.listenerMessageType
17961
+ && dataWrapped.data
17962
+ && dataWrapped.data.type && this.listenerConfig.listenerMessageType!==dataWrapped.data.type){
17220
17963
  return;
17221
17964
  }
17222
17965
 
@@ -17240,8 +17983,12 @@ class ClientReceptionEntryPoint{
17240
17983
  if(this.listenerConfig && this.listenerConfig.destroyListenerAfterReceiving)
17241
17984
  remove(clientReceptionEntryPoints, this);
17242
17985
 
17243
- if(this.doOnIncomingMessage)
17244
- this.doOnIncomingMessage(dataLocal, clientSocket);
17986
+ if(this.doOnIncomingMessage){
17987
+
17988
+ const dataRestored=restoreClassesInformation(dataLocal);
17989
+
17990
+ this.doOnIncomingMessage(dataRestored, clientSocket);
17991
+ }
17245
17992
 
17246
17993
  }
17247
17994
 
@@ -17284,7 +18031,9 @@ class ClientInstance{
17284
18031
  // I-
17285
18032
  //
17286
18033
  self.receive(channelNameForResponse, doOnIncomingMessageForResponse,
17287
- clientsRoomsTag, listenerId, {messageId:messageId, destroyListenerAfterReceiving:true});
18034
+ {messageId:messageId, destroyListenerAfterReceiving:true},
18035
+ clientsRoomsTag, listenerId,
18036
+ );
17288
18037
  //
17289
18038
 
17290
18039
  return resultPromise;
@@ -17299,7 +18048,7 @@ class ClientInstance{
17299
18048
  }
17300
18049
 
17301
18050
  // II-
17302
- receive(channelNameParam, doOnIncomingMessage, clientsRoomsTag=null, entryPointId=null, listenerConfig={destroyListenerAfterReceiving:false}){
18051
+ receive(channelNameParam, doOnIncomingMessage, listenerConfig={destroyListenerAfterReceiving:false, listenerMessageType:null}, clientsRoomsTag=null, entryPointId=null){
17303
18052
  const self=this;
17304
18053
 
17305
18054
  // // DBG
@@ -17339,8 +18088,11 @@ class ClientInstance{
17339
18088
  if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
17340
18089
 
17341
18090
  // Channel information is stored in exchanged data :
17342
- let dataWrapped={channelName:channelNameParam, data:data};
17343
-
18091
+ let dataWrapped=saveClassesInformation({channelName:channelNameParam, data:data});
18092
+
18093
+ // CAUTION : WE CAN ONLY SEND STRINGS !!!
18094
+ // (or else we'll have TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received an instance of Object)
18095
+ // (Don't worry, the underlying sub-system will turn it back into Objects without you need to do anything!)
17344
18096
  dataWrapped=stringifyObject(dataWrapped);
17345
18097
 
17346
18098
  // TODO : FIXME : Use one single interface !
@@ -17472,8 +18224,9 @@ class ServerReceptionEntryPoint{
17472
18224
  if(!isClientInRoom) return;
17473
18225
 
17474
18226
  // We check if the message matches the required message type :
17475
- if( this.listenerConfig && this.listenerConfig.listenerMessageType && dataWrapped.type
17476
- && this.listenerConfig.listenerMessageType!==dataWrapped.type){
18227
+ if( this.listenerConfig && this.listenerConfig.listenerMessageType
18228
+ && dataWrapped.data
18229
+ && dataWrapped.data.type && this.listenerConfig.listenerMessageType!==dataWrapped.data.type){
17477
18230
  return;
17478
18231
  }
17479
18232
 
@@ -17481,7 +18234,11 @@ class ServerReceptionEntryPoint{
17481
18234
  // // DBG
17482
18235
  // lognow("(SERVER) this.doOnIncomingMessage:");
17483
18236
 
17484
- this.doOnIncomingMessage(dataWrapped.data, clientSocketParam);
18237
+ const dataLocal=dataWrapped.data;
18238
+
18239
+ const dataRestored=restoreClassesInformation(dataLocal);
18240
+
18241
+ this.doOnIncomingMessage(dataRestored, clientSocketParam);
17485
18242
  }
17486
18243
 
17487
18244
  }
@@ -17529,13 +18286,10 @@ class NodeServerInstance{
17529
18286
  });
17530
18287
  }
17531
18288
 
17532
-
17533
-
17534
18289
  // TODO : DEVELOP...
17535
18290
  //sendChainable(channelNameParam, data, clientsRoomsTag=null){
17536
18291
  //}
17537
18292
 
17538
-
17539
18293
  receive(channelNameParam, doOnIncomingMessage, listenerConfig=null, clientsRoomsTag=null){
17540
18294
 
17541
18295
  // DBG
@@ -17583,14 +18337,16 @@ class NodeServerInstance{
17583
18337
  if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
17584
18338
 
17585
18339
  // Channel information is stored in exchanged data :
17586
- let dataWrapped={channelName:channelName, data:data};
17587
-
17588
-
18340
+ let dataWrapped=saveClassesInformation({channelName:channelName, data:data});
18341
+
18342
+ // CAUTION : WE CAN ONLY SEND STRINGS !!!
18343
+ // (or else we'll have TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received an instance of Object)
18344
+ // (Don't worry, the underlying sub-system will turn it back into Objects without you need to do anything!)
17589
18345
  dataWrapped=stringifyObject(dataWrapped);
17590
18346
 
17591
18347
  // TODO : FIXME : Use one single interface !
17592
18348
  if(!WebsocketImplementation.useSocketIOImplementation) clientSocket.send(dataWrapped);
17593
- else clientSocket.emit(channelName,dataWrapped);
18349
+ else clientSocket.emit(channelName, dataWrapped);
17594
18350
 
17595
18351
  });
17596
18352
 
@@ -17610,12 +18366,12 @@ class NodeServerInstance{
17610
18366
  if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
17611
18367
 
17612
18368
  // Channel information is stored in exchanged data :
17613
- let dataWrapped={channelName:channelName, data:data};
17614
- dataWrapped=stringifyObject(dataWrapped);
18369
+ let dataWrapped=saveClassesInformation({channelName:channelName, data:data});
17615
18370
 
17616
-
17617
- // DBG
17618
- lognow("(SERVER) WebsocketImplementation.useSocketIOImplementation:"+WebsocketImplementation.useSocketIOImplementation);
18371
+ // CAUTION : WE CAN ONLY SEND STRINGS !!!
18372
+ // (or else we'll have TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received an instance of Object)
18373
+ // (Don't worry, the underlying sub-system will turn it back into Objects without you need to do anything!)
18374
+ dataWrapped=stringifyObject(dataWrapped);
17619
18375
 
17620
18376
 
17621
18377
  // TODO : FIXME : Use one single interface !
@@ -17628,8 +18384,8 @@ class NodeServerInstance{
17628
18384
  return this;
17629
18385
  }
17630
18386
 
17631
-
17632
- onConnectionToClient(doOnConnection){
18387
+
18388
+ onConnectionToClient(doOnConnection, doOnConnectionLost=null){
17633
18389
  const self=this;
17634
18390
 
17635
18391
 
@@ -17638,7 +18394,6 @@ class NodeServerInstance{
17638
18394
 
17639
18395
  // DBG
17640
18396
  console.log("SERVER : ON CONNECTION !");
17641
-
17642
18397
 
17643
18398
  const clientId="autogeneratedid_"+getUUID();
17644
18399
 
@@ -17689,6 +18444,9 @@ class NodeServerInstance{
17689
18444
 
17690
18445
 
17691
18446
  clearInterval(clientSocket.stateCheckInterval);
18447
+
18448
+ if(doOnConnectionLost) doOnConnectionLost(clientSocket.clientId);
18449
+
17692
18450
  return;
17693
18451
  }
17694
18452
 
@@ -17747,7 +18505,71 @@ class NodeServerInstance{
17747
18505
 
17748
18506
 
17749
18507
 
18508
+ window.saveClassesInformation=(obj, visitedObjects=[])=>{
18509
+
18510
+ const attributeClassName=CLASSNAME_ATTRIBUTE_NAME;
18511
+
18512
+ if(contains(visitedObjects, obj)) return obj;
18513
+ visitedObjects.push(obj);
18514
+
18515
+ const className=getClassName(obj);
18516
+ if(className!="Object"){
18517
+ // We do the save :
18518
+ obj[attributeClassName]=className;
18519
+
18520
+ // DBG
18521
+ lognow("0 saving class name : :",obj);
18522
+
18523
+ }
18524
+
18525
+ foreach(obj, (attr, attrName)=>{
18526
+ const className=getClassName(attr);
18527
+
18528
+ // DBG
18529
+ if(attrName=="position") lognow("1 attr:",attr);
18530
+
18531
+ if(className!="Object"){
18532
+ saveClassesInformation(attr, visitedObjects);
18533
+ }
18534
+
18535
+ // DBG
18536
+ if(attrName=="position") lognow("2 attr:",attr);
18537
+
18538
+ },(attr, attrName)=>
18539
+ attr &&
18540
+ // DEBUG ONLY
18541
+ (attrName=="position" || (
18542
+ isObject(attr) && !attr[attributeClassName]
18543
+ ))
18544
+ );
18545
+
18546
+ return obj;
18547
+ };
18548
+
18549
+
18550
+ window.restoreClassesInformation=(obj, visitedObjects=[])=>{
18551
+
18552
+ const attributeClassName=CLASSNAME_ATTRIBUTE_NAME;
18553
+
18554
+ if(contains(visitedObjects, obj)) return obj;
18555
+ visitedObjects.push(obj);
18556
+
18557
+ const className=obj[attributeClassName];
18558
+ if(className){
18559
+ // We do the restore :
18560
+ const blankInstance=instantiate(className);
18561
+ obj=Object.assign(blankInstance, obj);
18562
+ }
18563
+
18564
+ foreach(obj, (attr, attrName)=>{
18565
+ const className=attr[attributeClassName];
18566
+ if(className!="Object"){
18567
+ obj[attrName]=restoreClassesInformation(attr, visitedObjects);
18568
+ }
18569
+ },attr=>attr && ((isObject(attr) && attr[attributeClassName]) || isArray(attr)) );
17750
18570
 
18571
+ return obj;
18572
+ };
17751
18573
 
17752
18574
 
17753
18575