aotrautils 0.0.1841 → 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:44:04)»*/
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,14 +4152,27 @@ 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.`);
4134
- return newObj;
4155
+ lognow(`ERROR : CAUTION : «${className}» class does not seem to be registered in the global object. Cannot instantiate it.`);
4156
+
4157
+ // DEBUG
4158
+ // TODO : FIXME : I don't like that at all, to use eval(...), but on today I know of no other solution... :
4159
+ newObj=eval("new "+className+"();");
4160
+
4135
4161
  }
4136
4162
  }else{
4137
- // TRACE
4138
- 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.`);
4139
- return newObj;
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.`);
4165
+
4166
+ // DEBUG
4167
+ // TODO : FIXME : I don't like that at all, to use eval(...), but on today I know of no other solution... :
4168
+ newObj=eval("new "+className+"();");
4169
+
4140
4170
  }
4171
+
4172
+ // WORKAROUND
4173
+ newObj[CLASSNAME_ATTRIBUTE_NAME]=className;
4174
+
4175
+ return newObj;
4141
4176
  }
4142
4177
  }
4143
4178
 
@@ -4147,21 +4182,21 @@ window.instanciate=function(className=null){
4147
4182
  // newObj=Reflect.construct(classInWindow);
4148
4183
  // }catch(e1){
4149
4184
  //// // TRACE
4150
- //// 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 !)");
4151
4186
  // // TODO : FIXME : I don't like that at all, to use eval(...), but on today I know of no other solution... :
4152
4187
  // newObj=eval("new "+className+"();");
4153
4188
  // }
4154
4189
  // }catch(e2){
4155
4190
  //// // TRACE
4156
- //// 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 !)");
4157
4192
  // try{
4158
4193
  //// // TODO : FIXME : I don't like that at all, to use eval(...), but on today I know of no other solution... :
4159
4194
  //// newObj=eval("new window."+className+"();");
4160
4195
  // newObj=Reflect.construct(classInWindow);
4161
4196
  // }catch(e3){
4162
4197
  //// // TRACE
4163
- //// console.log("ERROR : Could not instanciate class «"+className+"» with eval and «window.». (Maybe class is undefined or default constructor doesn't exist !)");
4164
- //// 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 !)");
4165
4200
  // // TODO : FIXME : This is the most performance-costing fallback :
4166
4201
  // try{
4167
4202
  // if(classInWindow){
@@ -4173,7 +4208,7 @@ window.instanciate=function(className=null){
4173
4208
  // }
4174
4209
  // }catch(e4){
4175
4210
  //// // TRACE
4176
- //// console.log("ERROR : Could not instanciate class «"+className+"» with prototype affectation.");
4211
+ //// console.log("ERROR : Could not instantiate class «"+className+"» with prototype affectation.");
4177
4212
  //// console.log("ERROR : Returning plain object since all instantiation methods have failed for class «"+className+"».");
4178
4213
  // }
4179
4214
  // }
@@ -4181,17 +4216,17 @@ window.instanciate=function(className=null){
4181
4216
 
4182
4217
  // NEW :
4183
4218
  newObj=new classInWindow();
4219
+
4220
+ // WORKAROUND
4221
+ newObj[CLASSNAME_ATTRIBUTE_NAME]=className;
4222
+
4184
4223
  return newObj;
4185
4224
  };
4186
4225
 
4187
4226
 
4188
4227
 
4189
-
4190
-
4191
-
4192
-
4193
4228
  JSON.stringifyDecycle=function(obj){
4194
- let decycled=JSON.decycle(obj,null,"$className");
4229
+ let decycled=JSON.decycle(obj,null,CLASSNAME_ATTRIBUTE_NAME);
4195
4230
  // CANNOT USE stringifyObject(...) function because we are in a common, lower-level library !
4196
4231
  return stringifyObject(decycled);
4197
4232
  }
@@ -4199,6 +4234,8 @@ JSON.stringifyDecycle=function(obj){
4199
4234
 
4200
4235
  JSON.parseRecycle=function(str){
4201
4236
 
4237
+ const attributeClassName=CLASSNAME_ATTRIBUTE_NAME;
4238
+
4202
4239
  const parsedDecycled=parseJSON(str);
4203
4240
  let restoreClass=function(objParam){
4204
4241
 
@@ -4208,11 +4245,11 @@ JSON.parseRecycle=function(str){
4208
4245
  if(isArray(objParam)){
4209
4246
  newObj=[];
4210
4247
  }else{
4211
- let className=objParam["$className"];
4248
+ let className=objParam[attributeClassName];
4212
4249
  if(!className || isNativeClassName(className)){
4213
4250
  newObj= {};
4214
4251
  }else{
4215
- newObj=instanciate(className);
4252
+ newObj=instantiate(className);
4216
4253
  }
4217
4254
  }
4218
4255
 
@@ -4242,8 +4279,11 @@ JSON.parseRecycle=function(str){
4242
4279
  ,(itemOrAttr)=>{ return !isFunction(itemOrAttr); }
4243
4280
  );
4244
4281
 
4245
- if(destination["$className"]){
4246
- 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];
4247
4287
  }
4248
4288
 
4249
4289
  };
@@ -4261,6 +4301,7 @@ JSON.parseRecycle=function(str){
4261
4301
 
4262
4302
 
4263
4303
  window.clone=function(obj){
4304
+ if(obj==null) return null;
4264
4305
  // OLD :
4265
4306
  const str=JSON.stringifyDecycle(obj);
4266
4307
  const newObj=JSON.parseRecycle(str);
@@ -4733,7 +4774,7 @@ window.isFlatMap=function(obj){
4733
4774
 
4734
4775
  window.getAsFlatStructure=function(rawObject
4735
4776
  ,UUID_ATTR_NAME=DEFAULT_UUID_ATTR_NAME/* Yet Another UUID */
4736
- ,CLASSNAME_ATTR_NAME=DEFAULT_CLASSNAME_ATTR_NAME/* Yet Another Classname */
4777
+ ,CLASSNAME_ATTR_NAME=DEFAULT_CLASSNAME_ATTR_NAME/* Yet Another ClassName */
4737
4778
  ,POINTER_TO_ATTR_NAME=DEFAULT_POINTER_TO_ATTR_NAME/* Yet Another PointerTo */
4738
4779
  ){
4739
4780
  // Flattening :
@@ -4784,7 +4825,7 @@ function flattenGraph(root, UUID_ATTR_NAME, CLASSNAME_ATTR_NAME, POINTER_TO_ATTR
4784
4825
 
4785
4826
  window.getAsTreeStructure=function(oldMap, keepClassName=false
4786
4827
  ,UUID_ATTR_NAME=DEFAULT_UUID_ATTR_NAME/* Yet Another UUID */
4787
- ,CLASSNAME_ATTR_NAME=DEFAULT_CLASSNAME_ATTR_NAME/* Yet Another Classname */
4828
+ ,CLASSNAME_ATTR_NAME=DEFAULT_CLASSNAME_ATTR_NAME/* Yet Another ClassName */
4788
4829
  ,POINTER_TO_ATTR_NAME=DEFAULT_POINTER_TO_ATTR_NAME/* Yet Another PointerTo */
4789
4830
  ){
4790
4831
 
@@ -4806,7 +4847,7 @@ window.restoreGraph=(flatData, keepClassName=false, UUID_ATTR_NAME, CLASSNAME_AT
4806
4847
 
4807
4848
  const objData = flatData[id];
4808
4849
  const className = objData[CLASSNAME_ATTR_NAME];
4809
- const newInstance = instanciate(className);
4850
+ const newInstance = instantiate(className);
4810
4851
 
4811
4852
  restoredObjects[id]=newInstance; // Assign ID early to handle circular references
4812
4853
 
@@ -5171,7 +5212,7 @@ window.getMonoThreadedTimeout=function(actuator=null){
5171
5212
 
5172
5213
  // ======================== Routine ========================
5173
5214
 
5174
- window.MonoThreadedRoutine=class {
5215
+ window.MonoThreadedRoutine=class MonoThreadedRoutine{
5175
5216
  constructor(actuator,refreshMillis=null,startDependsOnParentOnly=false){
5176
5217
 
5177
5218
  this.actuator=actuator;
@@ -5181,15 +5222,21 @@ window.MonoThreadedRoutine=class {
5181
5222
  this.time=getNow();
5182
5223
  this.refreshMillis=refreshMillis;
5183
5224
 
5184
- this.durationTimeFactorHolder={durationTimeFactor:1};
5225
+ this.managedTimeFactor=1;
5185
5226
  // Three-states : started, paused, stopped.
5186
5227
 
5187
5228
  }
5188
5229
 
5189
- setDurationTimeFactorHolder(durationTimeFactorHolder){
5190
- this.durationTimeFactorHolder=durationTimeFactorHolder;
5230
+ registerToTimeFactorManager(timeFactorManager){
5231
+ timeFactorManager.registerTimeFactorManagee(this);
5232
+ return this;
5233
+ }
5234
+
5235
+ changeManagedTimeFactor(newManagedTimeFactor){
5236
+ this.managedTimeFactor=newManagedTimeFactor;
5191
5237
  return this;
5192
5238
  }
5239
+
5193
5240
  isStarted(){
5194
5241
  return this.started || this.startDependsOnParentOnly;
5195
5242
  }
@@ -5226,7 +5273,7 @@ window.MonoThreadedRoutine=class {
5226
5273
  if(!this.isStarted() || this.paused) return;
5227
5274
  // Looping index with a delay :
5228
5275
 
5229
- const delayHasPassed=hasDelayPassed(this.time, this.refreshMillis*this.durationTimeFactorHolder.getDurationTimeFactor());
5276
+ const delayHasPassed=hasDelayPassed(this.time, this.refreshMillis*this.managedTimeFactor);
5230
5277
  if(!this.refreshMillis || delayHasPassed){
5231
5278
  if(this.refreshMillis && delayHasPassed){
5232
5279
  this.time=getNow();
@@ -5236,7 +5283,8 @@ window.MonoThreadedRoutine=class {
5236
5283
  this.stop(args);
5237
5284
  return;
5238
5285
  }
5239
- if(this.actuator.doOnEachStep) this.actuator.doOnEachStep(args);
5286
+ if(this.actuator.doOnEachStep)
5287
+ this.actuator.doOnEachStep(args);
5240
5288
  }
5241
5289
 
5242
5290
  }
@@ -5265,19 +5313,27 @@ window.MonoThreadedGoToGoal=class {
5265
5313
  this.time=getNow();
5266
5314
  this.refreshMillis=refreshMillis;
5267
5315
 
5268
- this.durationTimeFactorHolder={durationTimeFactor:1};
5316
+ this.managedTimeFactor=1;
5317
+
5269
5318
 
5270
5319
  // Three-states : started, paused, stopped.
5271
5320
  }
5272
- setDurationTimeFactorHolder(durationTimeFactorHolder){
5273
- this.durationTimeFactorHolder=durationTimeFactorHolder;
5321
+
5322
+ registerToTimeFactorManager(timeFactorManager){
5323
+ timeFactorManager.registerTimeFactorManagee(this);
5324
+ return this;
5325
+ }
5326
+
5327
+ changeManagedTimeFactor(newManagedTimeFactor){
5328
+ this.managedTimeFactor=newManagedTimeFactor;
5274
5329
 
5275
5330
  // We recalculate total time-dependent values :
5276
- this.totalTimeMillis=this.totalTimeMillis*this.durationTimeFactorHolder.getDurationTimeFactor();
5331
+ this.totalTimeMillis=this.totalTimeMillis*this.managedTimeFactor;
5277
5332
  this.stepValue=((this.refreshMillis*(this.goalValue-this.actualValue))/this.totalTimeMillis);
5278
5333
 
5279
5334
  return this;
5280
5335
  }
5336
+
5281
5337
  isStarted(){
5282
5338
  return this.started || this.startDependsOnParentOnly;
5283
5339
  }
@@ -5289,7 +5345,7 @@ window.MonoThreadedGoToGoal=class {
5289
5345
  setNewGoal(goalValue, refreshMillisParam=null, totalTimeMillis=null){
5290
5346
  if(Math.round(this.value)===Math.round(goalValue)) return;
5291
5347
  if(refreshMillisParam) this.refreshMillis=refreshMillisParam;
5292
- if(totalTimeMillis) this.totalTimeMillis=totalTimeMillis*this.durationTimeFactorHolder.getDurationTimeFactor();
5348
+ if(totalTimeMillis) this.totalTimeMillis=totalTimeMillis*this.managedTimeFactor;
5293
5349
 
5294
5350
  this.goalValue=goalValue;
5295
5351
  this.stepValue=((this.refreshMillis*(this.goalValue-this.value))/this.totalTimeMillis);
@@ -5376,7 +5432,7 @@ window.MonoThreadedGoToGoal=class {
5376
5432
  }
5377
5433
 
5378
5434
  hasDelayPassed(){
5379
- return (!this.time || hasDelayPassed(this.time, this.refreshMillis*this.durationTimeFactorHolder.getDurationTimeFactor()));
5435
+ return (!this.time || hasDelayPassed(this.time, this.refreshMillis*this.managedTimeFactor));
5380
5436
  }
5381
5437
 
5382
5438
 
@@ -5402,7 +5458,7 @@ AOTRAUTILS_LIB_IS_LOADED=true;
5402
5458
 
5403
5459
 
5404
5460
 
5405
- /*utils CLIENT library associated with aotra version : «1_29072022-2359 (09/04/2026-01:44:04)»*/
5461
+ /*utils CLIENT library associated with aotra version : «1_29072022-2359 (02/06/2026-01:05:39)»*/
5406
5462
  /*-----------------------------------------------------------------------------*/
5407
5463
  /* ## Utility global methods in a browser (htmljs) client environment.
5408
5464
  *
@@ -6117,7 +6173,7 @@ function getTextWordsExtract(textStrParam, wordsNumber,/* OPTIONAL */wordPositio
6117
6173
 
6118
6174
  //==================== CLIENT Geometry ====================
6119
6175
 
6120
- 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){
6121
6177
 
6122
6178
  if(center==null) center={x:"center",y:"center"};
6123
6179
 
@@ -6141,8 +6197,6 @@ Math.isInScreen=function(coords,size=null,center=null,totalWidth,totalHeight,cam
6141
6197
  }
6142
6198
 
6143
6199
 
6144
-
6145
-
6146
6200
  let isInScreenX=false;
6147
6201
  {
6148
6202
  let min;
@@ -8321,7 +8375,7 @@ function filterPoints(allPoints ,ctx/*DBG*/){
8321
8375
 
8322
8376
  // This temporary, off-screen video is always needed to get the image data for further use :
8323
8377
  videoTag=document.createElement("video");
8324
- videoTag.id="tmpVideoElement";
8378
+ videoTag.id="tmpVideoElement"+getUUID("short");
8325
8379
  videoTag.autoplay=true;
8326
8380
  videoTag.width=canvasTag.width;
8327
8381
  videoTag.height=canvasTag.height;
@@ -8661,12 +8715,6 @@ drawVideoImage=function(canvas,videoImage,
8661
8715
  var x=nonull(xParam,0);
8662
8716
  var y=nonull(yParam,0);
8663
8717
 
8664
- // // DOES NOT WORK WELL :
8665
- // if(Object.getPrototypeOf(videoImage).constructor.name==="VideoFrame"){
8666
- // ctx.drawImage(videoImage, x, y, newWidthParam, newHeightParam);
8667
- // return;
8668
- // }
8669
-
8670
8718
  var oldWidth=videoImage.width;
8671
8719
  var oldHeight=videoImage.height;
8672
8720
 
@@ -9500,14 +9548,18 @@ class SpriteMonoThreaded{
9500
9548
  // Time measurement :
9501
9549
  this.time=getNow();
9502
9550
  this.refreshMillis=refreshMillis;
9503
- this.durationTimeFactorHolder={durationTimeFactor:1};
9504
9551
 
9552
+ this.managedTimeFactor=1;
9505
9553
  // Two-states : started, stopped.
9506
-
9554
+ }
9555
+
9556
+ registerToTimeFactorManager(timeFactorManager){
9557
+ timeFactorManager.registerTimeFactorManagee(this);
9558
+ return this;
9507
9559
  }
9508
9560
 
9509
- setDurationTimeFactorHolder(durationTimeFactorHolder){
9510
- this.durationTimeFactorHolder=durationTimeFactorHolder;
9561
+ changeManagedTimeFactor(newManagedTimeFactor){
9562
+ this.managedTimeFactor=newManagedTimeFactor;
9511
9563
  return this;
9512
9564
  }
9513
9565
 
@@ -9577,7 +9629,7 @@ class SpriteMonoThreaded{
9577
9629
  }
9578
9630
 
9579
9631
  hasDelayPassed(){
9580
- return (!this.time || hasDelayPassed(this.time, this.refreshMillis*this.durationTimeFactorHolder.getDurationTimeFactor()));
9632
+ return (!this.time || hasDelayPassed(this.time, this.refreshMillis*this.managedTimeFactor));
9581
9633
  }
9582
9634
 
9583
9635
  }
@@ -13228,7 +13280,7 @@ createOritaMicroClient=function(url, port, isNode=false, staticMicroClientIdPara
13228
13280
 
13229
13281
 
13230
13282
 
13231
- /*utils GEOMETRY library associated with aotra version : «1_29072022-2359 (09/04/2026-01:44:04)»*/
13283
+ /*utils GEOMETRY library associated with aotra version : «1_29072022-2359 (02/06/2026-01:05:39)»*/
13232
13284
  /*-----------------------------------------------------------------------------*/
13233
13285
 
13234
13286
 
@@ -13983,171 +14035,6 @@ window.getSunShadowProjectedPoint=function(originalPoint,angleDegreesParam,groun
13983
14035
  }
13984
14036
 
13985
14037
 
13986
- window.isInZone=function(point, zone, center={x:"center",y:"center",z:"center"}, zoneOffsets=null, invertYAxis=false, zooms=null){
13987
-
13988
- if(!center){
13989
- center={x:"center",y:"center",z:"center"}
13990
- }
13991
- if(invertYAxis==null){
13992
- invertYAxis=false;
13993
- }
13994
- if(zooms){
13995
- point=getZoomedPosition(point, zooms);
13996
- zone=getZoomedZone(zone, zooms);
13997
- }
13998
-
13999
- let pointX=point.x;
14000
- let pointY=point.y;
14001
- let pointZ=point.z;
14002
-
14003
- let zoneX=zone.x;
14004
- let zoneY=zone.y;
14005
- let zoneZ=zone.z;
14006
- if(zoneOffsets){
14007
- if(zoneX!=null) zoneX+=nonull(zoneOffsets.x,0);
14008
- if(zoneY!=null) zoneY+=nonull(zoneOffsets.y,0);
14009
- if(zoneZ!=null) zoneZ+=nonull(zoneOffsets.z,0);
14010
- }
14011
-
14012
- // We ignore zone parameters if they are null or 0 :
14013
- let isXInZone=zone.w?false:true;
14014
- let isYInZone=zone.h?false:true;
14015
- let isZInZone=zone.d?false:true;
14016
-
14017
-
14018
- if(zone.w || zone.h || zone.d){
14019
-
14020
- if(zone.w){
14021
- if(pointX!=null && zoneX!=null){
14022
- if(center.x==="center"){
14023
- let demiSize=Math.floor(zone.w*.5);
14024
- if((zoneX - demiSize) <= pointX && pointX <= (zoneX + demiSize) )
14025
- isXInZone=true;
14026
- }else if(center.x==="right"){
14027
- if((zoneX - zone.w) <= pointX && pointX <= zoneX )
14028
- isXInZone=true;
14029
- }else if(center.x==="left"){
14030
- if(zoneX <= pointX && pointX <= (zoneX + zone.w) )
14031
- isXInZone=true;
14032
- }
14033
- }else{
14034
- isXInZone=true;
14035
- }
14036
- }
14037
-
14038
- if(zone.h){
14039
- if(pointY!=null && zoneY!=null){
14040
- if(center.y==="center"){
14041
- let demiSize=Math.floor(zone.h*.5);
14042
- if((zoneY - demiSize) <= pointY && pointY <= (zoneY + demiSize) )
14043
- isYInZone=true;
14044
- }else if(center.y==="top"){
14045
- if(!invertYAxis){
14046
- if((zoneY - zone.h) <= pointY && pointY <= zoneY )
14047
- isYInZone=true;
14048
- }else{
14049
- if(zoneY <= pointY && pointY <= (zoneY + zone.h) )
14050
- isYInZone=true;
14051
- }
14052
- }else if(center.y==="bottom"){
14053
- if(!invertYAxis){
14054
- if(zoneY <= pointY && pointY <= (zoneY + zone.h) )
14055
- isYInZone=true;
14056
- }else{
14057
- if((zoneY - zone.h) <= pointY && pointY <= zoneY )
14058
- isYInZone=true;
14059
- }
14060
- }
14061
- }else{
14062
- isYInZone=true;
14063
- }
14064
- }
14065
-
14066
- if(zone.d){
14067
- if(pointZ!=null && zoneZ!=null){
14068
- if(center.z==="center"){
14069
- let demiSize=Math.floor(zone.d*.5);
14070
- if((zoneZ - demiSize) <= pointZ && pointZ <= (zoneZ + demiSize) )
14071
- isZInZone=true;
14072
- }else if(center.z==="front"){
14073
- if((zoneZ - zone.d) <= pointZ && pointZ <= zoneZ )
14074
- isZInZone=true;
14075
- }else if(center.z==="back"){
14076
- if(zoneZ <= pointZ && pointZ <= (zoneZ + zone.d) )
14077
- isZInZone=true;
14078
- }
14079
- }else{
14080
- isZInZone=true;
14081
- }
14082
- }
14083
-
14084
-
14085
- }else{
14086
-
14087
- if(pointX!=null && zoneX!=null){
14088
- if(center.x==="center"){
14089
- let demiW=Math.floor(zone.w*.5);
14090
- if((zoneX - demiW) <= pointX && pointX <= (zoneX + demiW))
14091
- isXInZone=true;
14092
- }else if(center.x==="right"){
14093
- if((zoneX - zone.w) <= pointX && pointX <= zoneX )
14094
- isXInZone=true;
14095
- }else if(center.x==="left"){
14096
- if( zoneX <= pointX && pointX <= (zoneX + zone.w ) )
14097
- isXInZone=true;
14098
- }
14099
- }else{
14100
- isXInZone=true;
14101
- }
14102
-
14103
- if(pointY!=null && zoneY!=null){
14104
- if(center.y==="center"){
14105
- let demiH=Math.floor(zone.h*.5);
14106
- if((zoneY - demiH) <= pointY && pointY <= (zoneY + demiH))
14107
- isYInZone=true;
14108
- }else if(center.y==="top"){
14109
- if(!invertYAxis){
14110
- if((zoneY - zone.h) <= pointY && pointY <= zoneY )
14111
- isYInZone=true;
14112
- }else{
14113
- if( zoneY <= pointY && pointY <= (zoneY + zone.h ) )
14114
- isYInZone=true;
14115
- }
14116
- }else if(center.y==="bottom"){
14117
- if(!invertYAxis){
14118
- if( zoneY <= pointY && pointY <= (zoneY + zone.h ) )
14119
- isYInZone=true;
14120
- }else{
14121
- if((zoneY - zone.h) <= pointY && pointY <= zoneY )
14122
- isYInZone=true;
14123
- }
14124
- }
14125
- }else{
14126
- isYInZone=true;
14127
- }
14128
-
14129
- if(pointZ!=null && zoneZ!=null){
14130
- if(center.z==="center"){
14131
- let demiW=Math.floor(zone.d*.5);
14132
- if((zoneZ - demiW) <= pointZ && pointZ <= (zoneZ + demiW))
14133
- isZInZone=true;
14134
- }else if(center.z==="front"){
14135
- if((zoneZ - zone.d) <= pointZ && pointZ <= zoneZ )
14136
- isZInZone=true;
14137
- }else if(center.z==="back"){
14138
- if( zoneZ <= pointZ && pointZ <= (zoneZ + zone.d ) )
14139
- isZInZone=true;
14140
- }
14141
- }else{
14142
- isZInZone=true;
14143
- }
14144
-
14145
-
14146
- }
14147
-
14148
- return isXInZone && isYInZone && isZInZone;
14149
- }
14150
-
14151
14038
 
14152
14039
  Math.getQuadrant=function(centerPoint,point){
14153
14040
  let deltaX=point.x-centerPoint.x;
@@ -14284,85 +14171,6 @@ Math.getBidimensional8Direction=(currentPosition,destination,invertYAxis=false)=
14284
14171
 
14285
14172
 
14286
14173
 
14287
- /*public static*/window.getRandomPositionInZone=function(positionZone, xOffset=0, yOffset=0, zOffset=0, avoidOverlap=null, size=null, randomizeAngles=false){
14288
-
14289
- let w=nonull(positionZone.w,0);
14290
- let h=nonull(positionZone.h,0);
14291
- let d=nonull(positionZone.d,0);
14292
-
14293
- let demiW=w*.5;
14294
- let demiH=h*.5;
14295
- let demiD=d*.5;
14296
-
14297
- let xOrZero=nonull(positionZone.x,0);
14298
- let yOrZero=nonull(positionZone.y,0);
14299
- let zOrZero=nonull(positionZone.z,0);
14300
-
14301
- // If respectively width or height is 0, then there is no need to use a randomization !
14302
- let x= ( w<=0 ? xOrZero : (xOrZero + Math.getRandomInt(Math.floor(demiW), -Math.ceil(demiW)))) + xOffset;
14303
- let y= ( h<=0 ? yOrZero : (yOrZero + Math.getRandomInt(Math.floor(demiH), -Math.ceil(demiH)))) + yOffset;
14304
- let z= ( d<=0 ? zOrZero : (zOrZero + Math.getRandomInt(Math.floor(demiD), -Math.ceil(demiD)))) + zOffset;
14305
-
14306
- if(avoidOverlap && size){
14307
-
14308
- let itemW=nonull(size.w,0);
14309
- let itemH=nonull(size.h,0);
14310
- let itemD=nonull(size.d,0);
14311
-
14312
- if(xOrZero/*includes case==0*/ && 0<itemW && contains(avoidOverlap,"x")) x=Math.floor(x/itemW)*itemW;
14313
- if(yOrZero/*includes case==0*/ && 0<itemH && contains(avoidOverlap,"y")) y=Math.floor(y/itemH)*itemH;
14314
- if(zOrZero/*includes case==0*/ && 0<itemD && contains(avoidOverlap,"z")) z=Math.floor(z/itemD)*itemD;
14315
-
14316
- }
14317
-
14318
- let b=positionZone.b;
14319
- let g=positionZone.g;
14320
- let a=positionZone.a;
14321
- if(randomizeAngles){
14322
- b=Math.random()*Math.PI*2;
14323
- g=Math.random()*Math.PI*2;
14324
- a=Math.random()*Math.PI*2;
14325
- }
14326
-
14327
- return {x:x, y:y, z:z, b:b, g:g, a:a, parallax:positionZone.parallax};
14328
- }
14329
-
14330
-
14331
- // CAUTION : ONLY HANDLES «center-positionned» ZONES !
14332
- /*public static*/window.isZoneCollidingWithZone=function(zone1, zone2){
14333
-
14334
- let demiWidth1=zone1.w*.5;
14335
- let demiWidth2=zone2.w*.5;
14336
-
14337
- let xMin1=zone1.x-demiWidth1;
14338
- let xMax1=zone1.x+demiWidth1;
14339
- let xMin2=zone2.x-demiWidth2;
14340
- let xMax2=zone2.x+demiWidth2;
14341
-
14342
- //OLD : let widthsOverlap=((xMin2<xMin1 && xMin1<xMax2) || (xMin2<xMax1 && xMax1<xMax2)
14343
- // || (xMin1<xMin2 && xMin2<xMax1) || (xMin1<xMax2 && xMax2<xMax1));
14344
-
14345
- let widthsOverlap=(xMin1<xMax2 && xMin2<xMax1);
14346
-
14347
-
14348
- let demiHeight1=zone1.h*.5;
14349
- let demiHeight2=zone2.h*.5;
14350
-
14351
- let yMin1=zone1.y-demiHeight1;
14352
- let yMax1=zone1.y+demiHeight1;
14353
- let yMin2=zone2.y-demiHeight2;
14354
- let yMax2=zone2.y+demiHeight2;
14355
-
14356
- //OLD : let heightsOverlap=((yMin1<yMin2 && yMin2<yMax1) || (yMin1<yMax2 && yMax2<yMax1)
14357
- // || (yMin2<yMin1 && yMin1<yMax2) || (yMin2<yMax1 && yMax1<yMax2));
14358
-
14359
- let heightsOverlap=(yMin1<yMax2 && yMin2<yMax1);
14360
-
14361
- return widthsOverlap && heightsOverlap;
14362
- }
14363
-
14364
-
14365
-
14366
14174
  // CAUTION : ONLY HANDLES «center-positionned» ZONES !
14367
14175
  /*public static*/window.getIntersectionZone=function(zone1, zone2){
14368
14176
  if(!isZoneCollidingWithZone(zone1, zone2)) return null;
@@ -14557,10 +14365,10 @@ function rayVsUnitSphereClosestPoint(p, r) {
14557
14365
  // MUST REMAIN AT THE END OF THIS LIBRARY FILE !
14558
14366
 
14559
14367
  AOTRAUTILS_GEOMETRY_LIB_IS_LOADED=true;
14560
- /*utils 3D library associated with aotra version : «1_29072022-2359 (09/04/2026-01:44:04)»*/
14368
+ /*utils 3D library associated with aotra version : «1_29072022-2359 (02/06/2026-01:05:39)»*/
14561
14369
  /*-----------------------------------------------------------------------------*/
14562
14370
 
14563
- /*utils AI library associated with aotra version : «1_29072022-2359 (09/04/2026-01:44:04)»*/
14371
+ /*utils AI library associated with aotra version : «1_29072022-2359 (02/06/2026-01:05:39)»*/
14564
14372
  /*-----------------------------------------------------------------------------*/
14565
14373
 
14566
14374
 
@@ -14706,11 +14514,11 @@ getOpenAIAPIClient=(modelName, apiURL, agentRole, defaultPrompt)=>{
14706
14514
 
14707
14515
 
14708
14516
 
14709
- /*utils CONSOLE library associated with aotra version : «1_29072022-2359 (09/04/2026-01:44:04)»*/
14517
+ /*utils CONSOLE library associated with aotra version : «1_29072022-2359 (02/06/2026-01:05:39)»*/
14710
14518
  /*-----------------------------------------------------------------------------*/
14711
14519
 
14712
14520
 
14713
- /* ## Utility methods in a javascript, for AORTAC subsystem (server & client)
14521
+ /* ## Utility methods in a javascript, for AORTAC subsystem (client)
14714
14522
  *
14715
14523
  * This set of methods gathers utility generic-purpose methods usable in any JS project.
14716
14524
  * Several authors of snippets published freely on the Internet contributed to this library.
@@ -14733,163 +14541,584 @@ getOpenAIAPIClient=(modelName, apiURL, agentRole, defaultPrompt)=>{
14733
14541
  if(typeof(window)==="undefined") window=global;
14734
14542
 
14735
14543
 
14544
+
14545
+
14736
14546
  // ==================================================================================================================
14737
14547
 
14738
14548
 
14739
- // AORTAC SERVER
14549
+ // AORTAC CLIENT
14550
+
14740
14551
 
14741
14552
 
14742
14553
  //*********************************** AUTO-ORGANIZING REAL-TIME AORTAC CLUSTERIZATION (AORTAC) *********************************** */
14743
14554
 
14744
- AORTAC_OUTCOMING_SERVERS_CONNECTION_GLOBAL_TIMEOUT=30000;
14555
+ const AORTAC_CLIENT_FORCE_SSL_USAGE=true;
14745
14556
 
14746
14557
  // New implementation :
14747
- AORTAC_SERVER_CELL_PERIODICAL_CONNECTIVITY_CHECK_MILLIS=2000;
14748
- class AORTACServerCell{
14558
+
14559
+ class AORTACClientCell{
14749
14560
 
14750
- 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();
14751
14564
 
14752
14565
  this.model=model;
14753
- this.controller=controller;
14754
-
14755
- this.quorumNumber=quorumNumber;
14756
- this.selfOrigin=selfOrigin;
14757
- const infos=splitURL(this.selfOrigin);
14758
-
14759
- //DBG
14760
- lognow("infos:::::::::::::",infos);
14566
+ this.view=view;
14567
+ this.isNodeContext=isNodeContext;
14761
14568
 
14569
+ const infos=splitURL(serverCellOrigin);
14570
+
14762
14571
  this.protocol=nonull(infos.protocol,"ws");
14763
14572
  this.host=nonull(infos.host,"localhost");
14764
14573
  this.port=nonull(infos.port,"30000");
14765
- this.sslConfig=sslConfig;
14766
-
14767
- this.server=null;
14768
- this.incomingCells={};
14574
+ this.usesSSL=AORTAC_CLIENT_FORCE_SSL_USAGE;
14769
14575
 
14770
- this.outcomingCellsOrigins=outcomingCellsOrigins;
14771
- this.outcomingCells={};
14772
- const self=this;
14773
- foreach(this.outcomingCellsOrigins,outcomingCellOrigin=>{
14774
- self.outcomingCells[outcomingCellOrigin]={connected:false,client:null};
14775
- });
14776
- this.outcomingNetworkProber=null;
14576
+ this.startTime=null;
14777
14577
 
14778
- this.onReceiveMessageListeners={
14779
- "incoming":{"protocol":{}},
14780
- "outcoming":{"protocol":{}},
14781
- "both":{"protocol":{}},
14782
- };
14783
-
14784
- this.cellsOverview={};
14578
+ this.initialClientInstance=null;
14579
+ this.clientInstances={};
14785
14580
 
14786
- this.startTime=null;
14787
14581
 
14788
- this.quorumIsReachedSequenceIsInitiated=false;
14789
- // this.quorumCells=null; // (CAUTION : only the latest ready cell when quorum is reached has this attribute populated)
14790
-
14791
- this.liveModelObjects={};
14582
+
14583
+ this.ready=false;
14584
+
14585
+ this.lobuleZone=new ClientLobule();
14792
14586
 
14587
+
14793
14588
  }
14589
+
14590
+
14591
+ start(manifestationZone){
14592
+
14593
+ const self=this;
14794
14594
 
14795
- start(){
14796
14595
 
14596
+ this.manifestationZone=manifestationZone;
14797
14597
  this.startTime=getNow();
14798
- this.server=this.launchServerForIncomingConnections();
14799
-
14800
- if(empty(this.outcomingCellsOrigins) || (getArraySize(this.outcomingCellsOrigins)==1 && contains(this.outcomingCellsOrigins, this.selfOrigin))){
14801
- // TRACE
14802
- lognow("ERROR : Server cell must have an outcoming link to at least one other server cell of the blob. Aborting outcoming network probing.");
14803
- }else{
14804
- this.outcomingNetworkProber=new PeriodicalExecuter(this.probeOutcomingNetwork, AORTAC_SERVER_CELL_PERIODICAL_CONNECTIVITY_CHECK_MILLIS, this);
14805
- }
14806
-
14807
- this.handleCommonListeners();
14808
-
14809
- // TRACE
14810
- lognow("AORTAC Server cell started on URL : ",this.selfOrigin);
14811
14598
 
14812
- return this;
14813
- }
14814
-
14815
- /*private*/launchServerForIncomingConnections(){
14816
- const self=this;
14817
14599
 
14818
- const server=initNodeServerInfrastructureWrapper(
14819
- // On each client connection :
14820
- null,
14821
- // On client finalization :
14822
- function(server){
14823
14600
 
14824
- // On-receive message listeners handling ;
14825
- foreach(Object.keys(self.onReceiveMessageListeners["incoming"]), channelName=>{
14826
- server.receive(channelName, (message, clientSocket)=>{
14827
- self.executeListeners("incoming",channelName, message, clientSocket);
14828
- });
14829
- });
14830
- foreach(Object.keys(self.onReceiveMessageListeners["both"]), channelName=>{
14831
- server.receive(channelName, (message, clientSocket)=>{
14832
- self.executeListeners("both",channelName, message, clientSocket);
14833
- });
14834
- });
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...");
14835
14609
 
14610
+ initialClientInstanceLocal.socketToServerClientInstance=socketToServerClientInstance;
14836
14611
 
14837
- // HANDLE HELLO SEQUENCE
14838
- self.handleHelloSequence();
14612
+ // INITIATE CLIENT HELLO SEQUENCE
14613
+ self.initiateClientHelloSequence();
14839
14614
 
14840
- }, this.port, this.sslConfig.certPath, this.sslConfig.keyPath);
14841
- server.serverManager.start();
14842
- return server;
14615
+ // HANDLE CLIENT HELLO SEQUENCE
14616
+ self.handleClientHelloSequence(resolve);
14617
+
14618
+
14619
+ }, self.protocol+"://"+self.host, self.port, self.usesSSL);
14620
+ self.initialClientInstance.client.start();
14621
+
14622
+ });
14843
14623
  }
14844
14624
 
14845
- /*private*/probeOutcomingNetwork(){
14846
- const numberOfTotalServers=getArraySize(this.outcomingCells);
14847
- let numberOfConnectedOutcomingServers=0;
14848
- foreach(this.outcomingCells, outcomingCell=>{
14849
- numberOfConnectedOutcomingServers++
14850
- },outcomingCell=>outcomingCell.connected);
14851
-
14852
- // TRACE
14853
- lognow(`INFO : Number of connected outcoming servers : ${numberOfConnectedOutcomingServers}/${numberOfTotalServers}`);
14854
-
14855
- if(numberOfTotalServers<=numberOfConnectedOutcomingServers){
14856
- this.outcomingNetworkProber.stop();
14857
-
14858
- // TRACE
14859
- lognow(`INFO : All outcoming cells connected. Stopping outcoming network probing.`);
14860
14625
 
14861
- // INITIATE CELL IS READY SEQUENCE
14862
- this.initiateCellIsReadySequence();
14626
+ // ===================================================================================================
14627
+ // SEQUENCES
14628
+ // ===================================================================================================
14863
14629
 
14864
- return;
14865
- }
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
+ }
14866
14652
 
14867
- const self=this;
14653
+ /*private*/handleClientHelloSequence(resolve){
14868
14654
 
14869
- // We try to connect to all outcoming cells :
14870
- foreach(this.outcomingCells, (outcomingCell, outcomingCellOrigin)=>{
14655
+ const self=this;
14656
+ this.initialClientInstance.socketToServerClientInstance.receive("protocol", (messageParam, clientSocket)=>{
14871
14657
 
14872
- const infos=splitURL(outcomingCellOrigin);
14658
+ const message=JSON.recycle(messageParam);
14659
+
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
+
15021
+ // On-receive message listeners handling ;
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);
15034
+ });
15035
+ });
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);
14873
15093
  const outcomingCellProtocol=nonull(infos.protocol,"ws");
14874
15094
  const outcomingCellHost=nonull(infos.host,"localhost");
14875
15095
  const outcomingCellPort=nonull(infos.port,"40000");
14876
15096
 
14877
15097
  const clientInstance=initClient(true, false, (socketToServerClientInstance)=>{
14878
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
+
14879
15105
  // On-receive message listeners handling ;
14880
- foreach(Object.keys(self.onReceiveMessageListeners["outcoming"]), channelName=>{
15106
+ foreach(channelsNames, channelName=>{
14881
15107
  socketToServerClientInstance.receive(channelName, (message, clientSocket)=>{
15108
+
15109
+ // DBG
15110
+ lognow("DEBUG : Server cell receives message from outcoming...");
15111
+
14882
15112
  self.executeListeners("outcoming",channelName, message, clientSocket);
14883
- });
14884
- });
14885
- foreach(Object.keys(self.onReceiveMessageListeners["both"]), channelName=>{
14886
- socketToServerClientInstance.receive(channelName, (message, clientSocket)=>{
15113
+
15114
+ // DBG
15115
+ lognow("DEBUG : Server cell receives message from both (out)...");
15116
+
14887
15117
  self.executeListeners("both",channelName, message, clientSocket);
14888
15118
  });
14889
15119
  });
14890
-
14891
15120
 
14892
- // INITIATE HELLO SEQUENCE
15121
+ // INITIATE SERVER CELL HELLO SEQUENCE
14893
15122
  self.initiateHelloSequence(socketToServerClientInstance);
14894
15123
 
14895
15124
  // We are connected:
@@ -14913,47 +15142,68 @@ class AORTACServerCell{
14913
15142
 
14914
15143
  }
14915
15144
 
14916
-
14917
15145
  /*private*/handleCommonListeners(){
14918
-
14919
15146
  // HANDLE CELL IS READY SEQUENCE
14920
15147
  this.handleCellIsReadySequence();
14921
-
14922
15148
  // HANDLE PARTITION SEQUENCE
14923
15149
  this.handlePartitionSequence();
14924
15150
 
14925
- }
15151
+
15152
+ // ******************************************
15153
+ // CLIENT
15154
+ // HANDLE CLIENT MODEL INITIAL POPULATION SEQUENCE
15155
+ this.handleGetServersForZoneRequest();
15156
+
15157
+ // HANDLE RECONNECTED CLIENT HELLO SEQUENCE
15158
+ this.handleReconnectedClientHelloSequence();
14926
15159
 
15160
+ }
15161
+
14927
15162
 
14928
15163
  /*private*/executeListeners(outletName, channelName, message, clientSocket){
14929
15164
  const onReceiveMessageListeners=this.onReceiveMessageListeners[outletName][channelName];
14930
15165
  if(!onReceiveMessageListeners) return;
14931
15166
 
14932
- // OUTCOMING NODES LISTENERS HOOK :
14933
- // // TRACE
14934
- // lognow("INFO : SERVER : Received a "+channelName+" message: ", message);
14935
-
14936
- foreach(onReceiveMessageListeners, (listeners,listenerMessageType)=>{
14937
- foreach(listeners, (listener)=>{
14938
- // // TRACE
14939
- // lognow(`INFO : SERVER : Executing a onReceiveMessageListeners for messageType ${listenerMessageType}.`);
14940
-
14941
- listener.execute(this, message, this.server, clientSocket);
14942
-
14943
- },(listener)=>(message.type===listener.listenerMessageType));
14944
- });
14945
-
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");
14946
15190
  }
14947
15191
 
14948
15192
 
14949
15193
  /*private*/sendMessageToBlob(channelName, message,
14950
- 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){
14951
15202
 
14952
- const self=this;
14953
-
14954
15203
  if(broadcastConfig){
14955
15204
  if(broadcastConfig.isOriginatingCell){
14956
15205
  message.originatingCellOrigin=this.selfOrigin;
15206
+ message.originatingPartitionId=this.partitionId;
14957
15207
  }
14958
15208
  if(broadcastConfig.isRequest){
14959
15209
  message.isRequest=true;
@@ -14966,10 +15216,36 @@ class AORTACServerCell{
14966
15216
  if(!message.visitedCells)
14967
15217
  message.visitedCells=[];
14968
15218
  else if(contains(message.visitedCells,this.selfOrigin))
14969
- return;
15219
+ return null;
14970
15220
  message.visitedCells.push(this.selfOrigin);
14971
15221
 
14972
- 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){
14973
15249
  foreach(this.incomingCells,(incomingCell)=>{
14974
15250
  // As a server, we send (forward) the message to the currently iterated upon client that is connected to us :
14975
15251
  incomingCell.server.send(channelName, message, null, incomingCell.clientSocket);
@@ -14977,7 +15253,6 @@ class AORTACServerCell{
14977
15253
  (incomingCell.connected && !contains(message.visitedCells, incomingCellOrigin))
14978
15254
  );
14979
15255
  }
14980
-
14981
15256
  foreach(this.outcomingCells,(outcomingCell)=>{
14982
15257
  // As a client, we send (forward) the message to the currently iterated upon server we are connected to :
14983
15258
  outcomingCell.socketToServerClientInstance.send(channelName, message);
@@ -14985,49 +15260,84 @@ class AORTACServerCell{
14985
15260
  (outcomingCell.connected && !contains(message.visitedCells, outcomingCellOrigin))
14986
15261
  );
14987
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
+ }
14988
15279
 
14989
- // We only add a result listener for the cell which sent the request message :
14990
- if(message.isRequest && message.originatingCellOrigin==this.selfOrigin
14991
- //&& (typeof(message.result)=="undefined" || message.result==null)
14992
- ){
14993
-
14994
- const outletName="both";
14995
-
14996
- // To emulate a promise-like behavior :
14997
- let blobResponseListeners=this.onReceiveMessageListeners[outletName][channelName][message.type];
14998
- if(blobResponseListeners && blobResponseListeners["forRequestsIssuedBySelf"]){
14999
- // TRACE
15000
- lognow(`INFO : Blob response «forRequestsIssuedBySelf» listener for cell ${this.selfOrigin} on channel ${channelName} for «${message.type}» already exists.`);
15001
- return blobResponseListeners["forRequestsIssuedBySelf"];
15002
- }else{
15003
- blobResponseListeners=getOrCreateEmptyAttribute(this.onReceiveMessageListeners[outletName][channelName],message.type);
15004
- }
15005
-
15006
- const blobResponseListener={
15007
- thenCallback:null,
15008
- execute:(self, message, server, clientSocket)=>{
15009
- // When we have received the message :
15010
- 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){
15011
15300
 
15012
- // We need to remove the result listener once it is completed though.
15013
- delete blobResponseListeners["forRequestsIssuedBySelf"];
15014
- },
15015
- then:(thenCallback)=>{
15016
- 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
+ }
15017
15309
  }
15018
- };
15019
- blobResponseListeners["forRequestsIssuedBySelf"]=blobResponseListener;
15020
-
15021
- return blobResponseListener;
15022
- }else{
15023
- return null;
15024
- }
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
+ };
15327
+
15025
15328
 
15329
+
15330
+ return blobResponseListener;
15026
15331
  }
15332
+
15333
+
15027
15334
 
15335
+ // ===================================================================================================
15336
+ // SERVER CELLS
15337
+ // ===================================================================================================
15028
15338
 
15029
15339
  // ==========================================================
15030
- // HELLO SEQUENCE
15340
+ // SERVER CELL HELLO SEQUENCE
15031
15341
 
15032
15342
  /*private*/initiateHelloSequence(socketToServerClientInstance){
15033
15343
 
@@ -15040,28 +15350,30 @@ class AORTACServerCell{
15040
15350
  }
15041
15351
 
15042
15352
 
15043
- /*private*/handleHelloSequence(){
15353
+ /*private*/handleServerCellHelloSequence(){
15044
15354
 
15045
15355
  // 2- We wait to receive the hello request of the incoming cell connection :
15046
- getOrCreateEmptyAttribute(
15047
- this.onReceiveMessageListeners["both"]["protocol"],"helloRequest")["forRequestsIssuedByOthers"]={
15048
- execute:(selfParam, message, server, clientSocket)=>{
15356
+ this.onReceiveMessageListeners["both"]["protocol"]["helloRequest"]={
15357
+ execute:(selfParam, messageParam, serverWrapper, clientSocket)=>{
15049
15358
 
15050
- const cellOriginToCheck=message.originatingCellOrigin;
15359
+ const cellOriginToCheck=messageParam.originatingCellOrigin;
15051
15360
 
15052
15361
  // TRACE
15053
- 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...`);
15054
15363
 
15055
15364
 
15056
15365
  selfParam.incomingCells[cellOriginToCheck]={
15057
15366
  connected:true,
15058
- server:server,
15367
+ server:serverWrapper.server,
15059
15368
  // DO NOT USE TO SEND/RECEIVE ANYTHINIG !
15060
15369
  // For this, use the server attribute (+ the clientSocket as argument) instead.
15061
15370
  clientSocket:clientSocket,
15062
15371
  };
15063
15372
 
15064
- },
15373
+
15374
+ // NO SERVER CELL HELLO CONFIRMATION SENT BACK TO THE OTHER SERVER CELL (to save time)
15375
+
15376
+ },
15065
15377
  listenerMessageType:"helloRequest"
15066
15378
  };
15067
15379
 
@@ -15086,18 +15398,14 @@ class AORTACServerCell{
15086
15398
  }
15087
15399
 
15088
15400
  /*private*/handleCellIsReadySequence(){
15089
- getOrCreateEmptyAttribute(
15090
- this.onReceiveMessageListeners["both"]["protocol"],"cellIsReady")["forRequestsIssuedByOthers"]={
15091
- execute:(selfParam, message, server, clientSocket)=>{
15092
15401
 
15093
- selfParam.cellsOverview[message.originatingCellOrigin]={ready:true,startTime:message.startTime};
15094
-
15095
- 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};
15096
15407
 
15097
- // // TRACE
15098
- // lognow("INFO : Updated cells overview for this cell ("+selfParam.selfOrigin+") :",selfParam.cellsOverview);
15099
- // lognow("DEBUG : selfParam.outcomingCells :",Object.keys(selfParam.outcomingCells));
15100
- // lognow("DEBUG : selfParam.incomingCells :",Object.keys(selfParam.incomingCells));
15408
+ selfParam.sendMessageToBlob("protocol",messageParam);
15101
15409
 
15102
15410
  // INITIATE QUORUM IS REACHED SEQUENCE
15103
15411
  selfParam.initiateQuorumIsReachedSequenceIfNecessary();
@@ -15123,11 +15431,11 @@ class AORTACServerCell{
15123
15431
  const quorumCells=copy(this.cellsOverview);
15124
15432
 
15125
15433
  // TRACE
15126
- lognow("INFO : Quorum is reached and this is the latest started cell. quorumCells :",quorumCells);
15127
-
15128
- this.initializeModel(quorumCells);
15129
-
15130
-
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);
15131
15439
 
15132
15440
  }
15133
15441
 
@@ -15138,249 +15446,694 @@ class AORTACServerCell{
15138
15446
  // ==========================================================
15139
15447
  // MODEL MANAGEMENT
15140
15448
 
15141
- /*private*/initializeModel(quorumCells){
15449
+ /*private*/async launchModelPartitionBlobSequence(quorumCells){
15142
15450
 
15143
15451
  const self=this;
15144
15452
 
15145
- 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});
15146
15681
 
15147
- const controller=this.controller;
15148
- const model=this.model;
15149
-
15150
-
15151
- // We initialize the whole model :
15152
- // (controller will handle if a persisted model exists)
15153
- controller.initializeModelForAORTACNode(model);
15154
-
15155
- // This must return the asked number of partitions, indexed by partition id :
15156
- // CAUTION : The partition function MUST return ALL the objects in a partition, WHATEVER THEIR NESTING LEVEL !!!
15157
- 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);
15158
15688
 
15159
- // We exclude this cell from the partition sending, because we already have the model partition at hand.
15160
- this.removeLinks(modelPartitions[0].objects);
15161
- this.doOnModelPartitionReception(modelPartitions[0]);
15162
-
15163
- // We send the models objects to the required cells :
15164
- let i=1;
15165
- foreach(quorumCells, (quorumCell,quorumCellOrigin)=>{
15689
+ selfParam2.lobuleZone.clear(clientIdLocal);
15690
+
15691
+ });
15692
+
15166
15693
 
15167
- const modelPartition=modelPartitions[i];
15168
- self.removeLinks(modelPartition.objects);
15169
-
15170
- const message=JSON.decycle({type:"modelPartition",partition:modelPartition});
15171
-
15172
- // TRACE
15173
- lognow(`INFO : Cell ${self.selfOrigin} is sending a partition to cell ${quorumCellOrigin}...`,message);
15174
-
15175
- self.sendMessageToBlob("protocol", message,
15176
- {isOriginatingCell:true, destinationCellsOrigins:[quorumCellOrigin], includeIncomingConnectionInTransmission:false, isRequest:false});
15694
+ },
15695
+ listenerMessageType:"clientHello"
15696
+ };
15177
15697
 
15178
- i++;
15179
- },(quorumCell,quorumCellOrigin)=>quorumCellOrigin!=self.selfOrigin);
15180
-
15181
-
15182
- // Each quorum member cell is responsible for a model partition
15183
- // Then the sattelite cells will be handling the duplication
15184
-
15185
15698
  }
15186
-
15187
15699
 
15188
- // HANDLE PARTITION SEQUENCE
15189
15700
 
15190
- /*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(){
15191
15703
 
15192
- // 2- We wait to receive the hello request of the incoming cell connection :
15193
- getOrCreateEmptyAttribute(
15194
- this.onReceiveMessageListeners["both"]["protocol"],"modelPartition")["forRequestsIssuedByOthers"]={
15195
- 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
+
15196
15730
 
15197
- // If this cell is in the destinations of this message, we pass it along and then we do what it says :
15198
- selfParam.sendMessageToBlob("protocol",messageParam);
15731
+ const message={type:"getServersForZone.response", objectsCount:allObjectsCorrespondingToZoneInServerCell.length, answeringServerOrigin:selfParam.selfOrigin};
15199
15732
 
15200
- if(contains(messageParam.destinationCellsOrigins,selfParam.selfOrigin)){
15201
-
15202
- const message=JSON.recycle(messageParam);
15203
-
15204
- selfParam.doOnModelPartitionReception(message.partition);
15205
-
15206
- }
15733
+ // TRACE
15734
+ lognow(`INFO : Server cell ${selfParam.selfOrigin} is sending its origin to server cell ${messageParam.originatingCellOrigin}...: message:`, message);
15207
15735
 
15208
- },
15209
- listenerMessageType:"modelPartition"
15736
+ selfParam.replyToBlobRequest("protocol", messageParam, message);
15737
+ },listenerMessageType:"getServersForZone"
15210
15738
  };
15211
15739
 
15212
15740
  }
15213
15741
 
15214
15742
 
15215
- /*private*/doOnModelPartitionReception(partition){
15216
-
15217
- // TRACE
15218
- lognow(`INFO : Incoming partition for cell ${this.selfOrigin}. Updating local model...`,partition);
15219
- lognow(`>>>>`,stringifyObject(JSON.decycle(partition.objects),1));
15743
+ // ----
15744
+ // HANDLE RECONNECTED CLIENT HELLO SEQUENCE
15745
+ handleReconnectedClientHelloSequence(){
15220
15746
 
15747
+ // Here we only want to collect the servers on which to reconnect to later :
15221
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
+
15222
15785
 
15223
15786
  }
15224
15787
 
15225
15788
 
15226
- /*private*/removeLinks(linkedObjects, currentObject=null){
15227
-
15228
- const self=this;
15229
-
15230
- if(!currentObject){
15231
- foreach(linkedObjects, obj=>{
15232
- self.removeLinksOnSingleObject(linkedObjects, obj);
15233
- });
15234
- }else{
15235
- self.removeLinksOnSingleObject(linkedObjects, currentObject);
15236
- }
15237
-
15238
- return linkedObjects;
15239
- }
15789
+ // ==========================================================
15240
15790
 
15241
- /*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
+ // }
15242
15827
 
15243
- const self=this;
15244
- foreach(currentObject, (attr,attrNameOrIndex)=>{
15245
- // We only remove links to the objects not in the partition
15246
- if(isClassObject(attr)){
15247
- let aortacId=attr.aortacId;
15248
- if(!aortacId){
15249
- aortacId=getUUID();
15250
- attr.aortacId=aortacId;
15251
- }
15252
- if(!contains(linkedObjects,attr)){
15253
- currentObject[attrNameOrIndex]=aortacId+"@aortacId";
15254
- // CAUTION : No need for recursive call here, because the partition function returns ALL the objects in a partition,
15255
- // WHATEVER THEIR NESTING LEVEL !!!
15256
- }
15257
- }else{
15258
- // However, we need a recursive call for any simple object or array that may reference a class object in another partition :
15259
- self.removeLinksOnSingleObject(linkedObjects, attr);
15260
- }
15261
- },obj=>(isObject(obj) || isArray(obj)));
15262
15828
 
15263
- }
15264
15829
 
15265
15830
 
15266
- // ******************************************************************
15267
-
15268
- /*private*/collectDependencies(inputObjects){
15831
+ // **********************************************************************
15832
+
15833
+ /*private*/partiallyPopulatedRootContainer(allObjectsCorrespondingToZoneInServerCell=[]){
15269
15834
 
15270
- ////
15271
- lognow("DEBUG : collectDependencies()...",inputObjects);
15835
+ const rootContainerLocal=this.model.getRootContainer();
15272
15836
 
15837
+ const rootContainer=this.model.populateRootContainerWithObjectsSubset(rootContainerLocal, allObjectsCorrespondingToZoneInServerCell, true);
15838
+ rootContainer.isRootContainer=true;
15273
15839
 
15840
+ return rootContainer;
15274
15841
  }
15275
-
15276
- /*private*/repercutChangesIfNeeded(inputObjects){
15277
-
15278
-
15279
- ////
15280
- lognow("DEBUG : repercutChangesIfNeeded()...",inputObjects);
15281
15842
 
15282
-
15283
- }
15284
-
15285
-
15286
15843
  }
15287
15844
 
15288
15845
 
15289
15846
 
15290
- // ******************************************************************
15291
15847
 
15292
- // Public static hydration method :
15293
- window.ao=(incompleteModelObjectToDecorate)=>{
15294
-
15295
- const localServerCell=window.aortacCServerNodeInstance;
15296
- if(!localServerCell){
15297
- // TRACE
15298
- lognow("ERROR : No AORTACCServerNode singleton instance. Doing nothing on the model object.");
15299
- return incompleteModelObjectToDecorate;
15300
- }
15301
-
15302
- const liveModelObjects=localServerCell.liveModelObjects;
15303
-
15304
- // First we clone the object, for its attriutes values information :
15305
- const clonedObject=clone(incompleteModelObjectToDecorate);
15306
-
15307
- // Then we replace all its methods :
15308
- foreach(clonedObject, (method, methodName)=>{
15309
- clonedObject[methodName]=new Proxy(method,
15310
- {
15311
- apply: function(methodToEnhance, thisArg, argumentsList) {
15312
15848
 
15313
- const inputObjects=[thisArg];
15314
- inputObjects.push(...argumentsList);
15315
- localServerCell.collectDependencies(inputObjects);
15316
-
15317
- // // --- Treatment BEFORE function execution ---
15318
- // console.log(`Calling function "${methodToEnhance.name}" with arguments: ${argumentsList}`);
15319
-
15320
- // Call the original function (target) with its intended 'this' context (thisArg)
15321
- // and arguments (argumentsList) using Reflect.apply
15322
- const result = Reflect.apply(methodToEnhance, thisArg, argumentsList);
15323
-
15324
- // // --- Treatment AFTER function execution ---
15325
- // console.log(`Function "${methodToEnhance.name}" returned: ${result}`);
15326
-
15327
- inputObjects.push(result);
15328
- localServerCell.repercutChangesIfNeeded(inputObjects);
15329
15849
 
15330
- return result;
15331
- },
15332
- }
15333
- );
15334
- },attribute=>isFunction(attribute));
15850
+
15851
+
15852
+
15853
+ class ServerCellLobule{
15335
15854
 
15336
- return clonedObject;
15337
- };
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
+ }
15338
15918
 
15339
15919
 
15340
15920
 
15341
- 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){
15342
15922
  //return new AORTACServerNode("node_"+getUUID("short"), selfOrigin, outcomingCellsOrigins, model, controller);
15343
- if(window.aortacCServerNodeInstance){
15923
+ if(window.aortacCServerCellInstance){
15344
15924
  // TRACE
15345
- 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.");
15346
15926
  }
15347
- window.aortacCServerNodeInstance=new AORTACServerCell(quorumNumber, selfOrigin, outcomingCellsOrigins, model, controller);
15348
- return window.aortacCServerNodeInstance;
15927
+ window.aortacCServerCellInstance=new AORTACServerCell(quorumNumber, selfOrigin, outcomingCellsOrigins, model, controller);
15928
+ return window.aortacCServerCellInstance;
15349
15929
  }
15350
15930
 
15351
15931
 
15352
15932
 
15353
15933
 
15934
+ // ===============================================================================================================================
15935
+ // ===============================================================================================================================
15936
+ // UTILITY METHODS
15354
15937
 
15355
15938
 
15939
+ // USAGE EXAMPLE :
15356
15940
 
15357
- // ==================================================================================================================
15358
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
15359
15968
 
15360
- // AORTAC CLIENT
15361
15969
 
15970
+ window.proxyClass=function(classToProxy,
15971
+ doOnAccess=(className, propertyName, parameter)=>{lognow(`Accessing ${className}.${propertyName}:`,parameter);}){
15362
15972
 
15973
+ const className=classToProxy.name;
15363
15974
 
15364
- //*********************************** AUTO-ORGANIZING REAL-TIME AORTAC CLUSTERIZATION (AORTAC) *********************************** */
15975
+ // Build the Proxy handler
15976
+ const proxyHandler={
15365
15977
 
15366
- // New implementation :
15978
+ // Intercepts: obj.property AND obj.method()
15979
+ get(concernedObject, property, thisArg){
15980
+ const value=Reflect.get(concernedObject, property, thisArg);
15367
15981
 
15368
- 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=()=>{
15369
16065
 
15370
- 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
+ }
15371
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+"».");
15372
16081
  }
15373
16082
 
15374
- }
16083
+ return currentLevelPrototypeName;
16084
+ };
15375
16085
 
15376
16086
 
15377
16087
 
15378
16088
 
15379
- window.getAORTACClient=function(serverNodeOrigin="ws://127.0.0.1:40000", model, view, isNodeContext=false){
15380
- //return new AORTACClient("client_"+getUUID(), serverNodeOrigin, model, view, isNodeContext);
15381
- return new AORTACClientCell(serverNodeOrigin, model, view, isNodeContext);
15382
- }
15383
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
+ //};
15384
16137
 
15385
16138
 
15386
16139
 
@@ -16096,7 +16849,7 @@ WebsocketImplementation={
16096
16849
  },
16097
16850
 
16098
16851
 
16099
- /*private*/getMessageDataBothImplementations:(eventOrMessageParam)=>{
16852
+ /*public*//*static*/getMessageDataBothImplementations:(eventOrMessageParam)=>{
16100
16853
 
16101
16854
  const eventOrMessage=(!WebsocketImplementation.useSocketIOImplementation?eventOrMessageParam.data:eventOrMessageParam);
16102
16855
 
@@ -16293,12 +17046,11 @@ WebsocketImplementation={
16293
17046
 
16294
17047
 
16295
17048
 
16296
- 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){
16297
17050
 
16298
17051
  const EXCLUDED_FILENAMES_PARTS=[".keyHash.",".pem"];
16299
17052
 
16300
17053
 
16301
-
16302
17054
  if(typeof(https)==="undefined"){
16303
17055
  // TRACE
16304
17056
  console.log("«https» SERVER library not called yet, calling it now.");
@@ -16390,8 +17142,6 @@ launchNodeHTTPServer=function(port, doOnConnect=null, doOnFinalizeServer=null, /
16390
17142
 
16391
17143
  const handler=nonull(httpHandlerParam, DEFAULT_HANDLER);
16392
17144
 
16393
-
16394
-
16395
17145
  let listenableServer;
16396
17146
  if(sslOptions){
16397
17147
  let httpsServer=https.createServer(sslOptions, handler).listen(port);
@@ -16412,7 +17162,7 @@ launchNodeHTTPServer=function(port, doOnConnect=null, doOnFinalizeServer=null, /
16412
17162
  // CAUTION : MUST BE CALLED ONLY ONCE !
16413
17163
  server.onConnectionToClient((serverParam, clientSocketParam)=>{
16414
17164
  if(doOnConnect) doOnConnect(serverParam, clientSocketParam);
16415
- });
17165
+ }, doOnConnectionLost);
16416
17166
 
16417
17167
 
16418
17168
  server.onFinalize((serverParam)=>{
@@ -16427,15 +17177,13 @@ launchNodeHTTPServer=function(port, doOnConnect=null, doOnFinalizeServer=null, /
16427
17177
  // TRACE
16428
17178
  console.log("INFO : SERVER : Generic Nodejs server launched and listening on port:" + port + "!");
16429
17179
 
16430
-
16431
-
16432
17180
 
16433
17181
 
16434
17182
  return server;
16435
17183
  }
16436
17184
 
16437
17185
 
16438
- initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFinalizeServer=null,
17186
+ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFinalizeServer=null, doOnConnectionLost=null,
16439
17187
  /*OPTIONAL*/portParam,
16440
17188
  /*OPTIONAL*/certPathParam,
16441
17189
  /*OPTIONAL*/keyPathParam){
@@ -16488,13 +17236,11 @@ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFina
16488
17236
 
16489
17237
 
16490
17238
 
16491
-
16492
-
16493
17239
  const aotraNodeServer={config:serverConfig};
16494
17240
  aotraNodeServer.serverManager={ start:()=>{/*DEFAULT START FUNCTION, WILL BE OVERRIDEN LATER*/}};
16495
17241
 
16496
17242
  if(isHashAsked){
16497
- // 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:
16498
17244
  const persister=getPersister("./");
16499
17245
  let persisterIdSplits=persisterId.split("@");
16500
17246
  if(empty(persisterIdSplits) || persisterIdSplits.length!=2){
@@ -16577,9 +17323,9 @@ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFina
16577
17323
  }
16578
17324
  }
16579
17325
 
16580
- aotraNodeServer.server=launchNodeHTTPServer(port, doOnClientConnection, doOnFinalizeServer, sslOptions);
17326
+ aotraNodeServer.server=launchNodeHTTPServer(port, doOnClientConnection, doOnFinalizeServer, doOnConnectionLost, sslOptions);
17327
+
16581
17328
 
16582
-
16583
17329
  return aotraNodeServer;
16584
17330
  };
16585
17331
 
@@ -16642,7 +17388,10 @@ initNodeServerInfrastructureWrapper=function(doOnClientConnection=null, doOnFina
16642
17388
  // },REFRESH_SCREENSHOTS_MILLIS);
16643
17389
  //
16644
17390
  //
16645
- // },portParam,certPathParam,keyPathParam);
17391
+ // },
17392
+ // // On client connection lost :
17393
+ // (clientId)=>{lognow(`INFO : Connection to client id «${clientId}» was lost.`);},
17394
+ // portParam,certPathParam,keyPathParam);
16646
17395
  //
16647
17396
  //
16648
17397
  // // const doOnConnect=(serverParam, clientSocketParam)=>{
@@ -17110,8 +17859,8 @@ initClient=function(isNodeContext=true, useSocketIOImplementation=/*DEBUG*/false
17110
17859
  // CAUTION : MUST BE CALLED ONLY ONCE !
17111
17860
  socketToServerClientInstance.onConnectionToServer(()=>{
17112
17861
  if(doOnServerConnection){
17113
- if(selfParam) doOnServerConnection.apply(selfParam,[socketToServerClientInstance]);
17114
- else doOnServerConnection(socketToServerClientInstance);
17862
+ if(selfParam) doOnServerConnection.apply(selfParam,[socketToServerClientInstance, aotraClient]);
17863
+ else doOnServerConnection(socketToServerClientInstance, aotraClient);
17115
17864
  }
17116
17865
 
17117
17866
 
@@ -17208,8 +17957,9 @@ class ClientReceptionEntryPoint{
17208
17957
 
17209
17958
 
17210
17959
  // We check if the message matches the required message type :
17211
- if( this.listenerConfig && this.listenerConfig.listenerMessageType && dataWrapped.type
17212
- && 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){
17213
17963
  return;
17214
17964
  }
17215
17965
 
@@ -17233,8 +17983,12 @@ class ClientReceptionEntryPoint{
17233
17983
  if(this.listenerConfig && this.listenerConfig.destroyListenerAfterReceiving)
17234
17984
  remove(clientReceptionEntryPoints, this);
17235
17985
 
17236
- if(this.doOnIncomingMessage)
17237
- this.doOnIncomingMessage(dataLocal, clientSocket);
17986
+ if(this.doOnIncomingMessage){
17987
+
17988
+ const dataRestored=restoreClassesInformation(dataLocal);
17989
+
17990
+ this.doOnIncomingMessage(dataRestored, clientSocket);
17991
+ }
17238
17992
 
17239
17993
  }
17240
17994
 
@@ -17277,7 +18031,9 @@ class ClientInstance{
17277
18031
  // I-
17278
18032
  //
17279
18033
  self.receive(channelNameForResponse, doOnIncomingMessageForResponse,
17280
- clientsRoomsTag, listenerId, {messageId:messageId, destroyListenerAfterReceiving:true});
18034
+ {messageId:messageId, destroyListenerAfterReceiving:true},
18035
+ clientsRoomsTag, listenerId,
18036
+ );
17281
18037
  //
17282
18038
 
17283
18039
  return resultPromise;
@@ -17292,7 +18048,7 @@ class ClientInstance{
17292
18048
  }
17293
18049
 
17294
18050
  // II-
17295
- receive(channelNameParam, doOnIncomingMessage, clientsRoomsTag=null, entryPointId=null, listenerConfig={destroyListenerAfterReceiving:false}){
18051
+ receive(channelNameParam, doOnIncomingMessage, listenerConfig={destroyListenerAfterReceiving:false, listenerMessageType:null}, clientsRoomsTag=null, entryPointId=null){
17296
18052
  const self=this;
17297
18053
 
17298
18054
  // // DBG
@@ -17332,8 +18088,11 @@ class ClientInstance{
17332
18088
  if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
17333
18089
 
17334
18090
  // Channel information is stored in exchanged data :
17335
- let dataWrapped={channelName:channelNameParam, data:data};
17336
-
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!)
17337
18096
  dataWrapped=stringifyObject(dataWrapped);
17338
18097
 
17339
18098
  // TODO : FIXME : Use one single interface !
@@ -17465,8 +18224,9 @@ class ServerReceptionEntryPoint{
17465
18224
  if(!isClientInRoom) return;
17466
18225
 
17467
18226
  // We check if the message matches the required message type :
17468
- if( this.listenerConfig && this.listenerConfig.listenerMessageType && dataWrapped.type
17469
- && 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){
17470
18230
  return;
17471
18231
  }
17472
18232
 
@@ -17474,7 +18234,11 @@ class ServerReceptionEntryPoint{
17474
18234
  // // DBG
17475
18235
  // lognow("(SERVER) this.doOnIncomingMessage:");
17476
18236
 
17477
- this.doOnIncomingMessage(dataWrapped.data, clientSocketParam);
18237
+ const dataLocal=dataWrapped.data;
18238
+
18239
+ const dataRestored=restoreClassesInformation(dataLocal);
18240
+
18241
+ this.doOnIncomingMessage(dataRestored, clientSocketParam);
17478
18242
  }
17479
18243
 
17480
18244
  }
@@ -17522,13 +18286,10 @@ class NodeServerInstance{
17522
18286
  });
17523
18287
  }
17524
18288
 
17525
-
17526
-
17527
18289
  // TODO : DEVELOP...
17528
18290
  //sendChainable(channelNameParam, data, clientsRoomsTag=null){
17529
18291
  //}
17530
18292
 
17531
-
17532
18293
  receive(channelNameParam, doOnIncomingMessage, listenerConfig=null, clientsRoomsTag=null){
17533
18294
 
17534
18295
  // DBG
@@ -17576,14 +18337,16 @@ class NodeServerInstance{
17576
18337
  if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
17577
18338
 
17578
18339
  // Channel information is stored in exchanged data :
17579
- let dataWrapped={channelName:channelName, data:data};
17580
-
17581
-
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!)
17582
18345
  dataWrapped=stringifyObject(dataWrapped);
17583
18346
 
17584
18347
  // TODO : FIXME : Use one single interface !
17585
18348
  if(!WebsocketImplementation.useSocketIOImplementation) clientSocket.send(dataWrapped);
17586
- else clientSocket.emit(channelName,dataWrapped);
18349
+ else clientSocket.emit(channelName, dataWrapped);
17587
18350
 
17588
18351
  });
17589
18352
 
@@ -17603,12 +18366,12 @@ class NodeServerInstance{
17603
18366
  if(!WebsocketImplementation.isInRoom(clientSocket,clientsRoomsTag)) return;
17604
18367
 
17605
18368
  // Channel information is stored in exchanged data :
17606
- let dataWrapped={channelName:channelName, data:data};
17607
- dataWrapped=stringifyObject(dataWrapped);
18369
+ let dataWrapped=saveClassesInformation({channelName:channelName, data:data});
17608
18370
 
17609
-
17610
- // DBG
17611
- 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);
17612
18375
 
17613
18376
 
17614
18377
  // TODO : FIXME : Use one single interface !
@@ -17621,8 +18384,8 @@ class NodeServerInstance{
17621
18384
  return this;
17622
18385
  }
17623
18386
 
17624
-
17625
- onConnectionToClient(doOnConnection){
18387
+
18388
+ onConnectionToClient(doOnConnection, doOnConnectionLost=null){
17626
18389
  const self=this;
17627
18390
 
17628
18391
 
@@ -17631,7 +18394,6 @@ class NodeServerInstance{
17631
18394
 
17632
18395
  // DBG
17633
18396
  console.log("SERVER : ON CONNECTION !");
17634
-
17635
18397
 
17636
18398
  const clientId="autogeneratedid_"+getUUID();
17637
18399
 
@@ -17682,6 +18444,9 @@ class NodeServerInstance{
17682
18444
 
17683
18445
 
17684
18446
  clearInterval(clientSocket.stateCheckInterval);
18447
+
18448
+ if(doOnConnectionLost) doOnConnectionLost(clientSocket.clientId);
18449
+
17685
18450
  return;
17686
18451
  }
17687
18452
 
@@ -17740,7 +18505,71 @@ class NodeServerInstance{
17740
18505
 
17741
18506
 
17742
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)) );
17743
18570
 
18571
+ return obj;
18572
+ };
17744
18573
 
17745
18574
 
17746
18575