Rtl.UnitLoader.pas 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. unit Rtl.UnitLoader;
  2. interface
  3. { $define DEBUGUNITLOADER}
  4. uses SysUtils, JS, Types;
  5. Type
  6. EUnitLoader = Class(Exception);
  7. TLoadedProcedure = Reference to Procedure(const aUnitNames : Array of String; aData : TObject);
  8. { TLoadTask }
  9. TLoadTask = Class(TObject)
  10. Private
  11. FUnitNames : TStringDynArray; // unit names case sensitive!
  12. FInitUnitNames : TStringDynArray; // unit names case sensitive!
  13. FOnLoaded : TLoadedProcedure;
  14. FData : TObject;
  15. function GetAllLoaded : Boolean;
  16. Protected
  17. Procedure CallLoaded;
  18. Public
  19. Constructor Create(Const aUnitNames : Array of string; aOnLoaded : TLoadedProcedure; aData : TObject);
  20. Procedure UnitLoaded(Const aUnitName : String);
  21. Property AllLoaded : Boolean Read GetAllLoaded;
  22. Property LoadUnitNames : TStringDynArray Read FUnitNames;
  23. Property OnLoaded : TLoadedProcedure Read FOnLoaded;
  24. Property Data : TObject Read FData;
  25. end;
  26. { TUnitLoader }
  27. TUnitLoader = Class(TObject)
  28. Private
  29. Class var FInstance : TUnitLoader;
  30. procedure DoDependenciesLoaded(const aUnitName: array of string;
  31. aData: TObject);
  32. Private
  33. FBaseURL : String;
  34. FLoadList : TStringDynArray; // unitnames case sensitive!
  35. function IndexOfLoadUnit(aUnitName : String): integer;
  36. protected
  37. Procedure AddToLoadList(aUnitName : String);
  38. Procedure RemoveFromLoadList(aUnitName : String);
  39. function IsInLoadList(aUnitName: String): Boolean;
  40. function GetUnitURL(const aUnitName: string): String; virtual;
  41. procedure InitModule(aTask: TLoadTask; const aName: String; aModule : JSValue); virtual;
  42. procedure DoLoadUnits(const aUnitNames: array of String; aOnLoaded: TLoadedProcedure; aData: TObject); virtual;
  43. function AreAllDependenciesLoaded(aTask: TLoadTask; const aName: String; AModule: JSValue): Boolean; virtual;
  44. function GetNeededDependencies(const aName: String; AModule: JSValue): TStringDynArray;
  45. procedure UnitSourcesLoaded(aData : TObject); virtual;
  46. Public
  47. Class Function Instance : TUnitLoader;
  48. function FindModule(aModuleName: string): JSValue;
  49. function HaveModule(aModuleName: string): Boolean;
  50. procedure LoadUnit(Const aUnitName : string; aOnLoaded : TLoadedProcedure = Nil; aData : TObject = Nil);
  51. procedure LoadUnits(Const aUnitNames : Array of String; aOnLoaded : TLoadedProcedure = Nil; aData : TObject = Nil);
  52. Property BaseURL : String Read FBaseUrl Write FBaseURL;
  53. end;
  54. Implementation
  55. uses Rtl.ScriptLoader;
  56. function IndexOfI(arr: TStringDynArray; Name: string): integer;
  57. begin
  58. Result:=length(arr)-1;
  59. while (Result>=0) and not SameText(arr[Result],Name) do
  60. dec(Result);
  61. end;
  62. function TLoadTask.GetAllLoaded: Boolean;
  63. begin
  64. Result:=Length(FInitunitNames)=0;
  65. end;
  66. procedure TLoadTask.CallLoaded;
  67. begin
  68. if Assigned(OnLoaded) then
  69. OnLoaded(LoadUnitNames,Data);
  70. end;
  71. constructor TLoadTask.Create(const aUnitNames: array of string;
  72. aOnLoaded: TLoadedProcedure; aData: TObject);
  73. Var
  74. I : Integer;
  75. begin
  76. SetLength(FunitNames,Length(aUnitNames));
  77. SetLength(FInitUnitNames,Length(aUnitNames));
  78. for I:=Low(aUnitNames) to High(aUnitNames) do
  79. begin
  80. FUnitNames[i]:=aUnitNames[i];
  81. FInitUnitNames[i]:=aUnitNames[i];
  82. end;
  83. FOnLoaded:=aOnLoaded;
  84. FData:=aData;
  85. end;
  86. procedure TLoadTask.UnitLoaded(const aUnitName: String);
  87. var
  88. Idx : integer;
  89. begin
  90. {$IFDEF DEBUGUNITLOADER}Writeln('Unit ',aUnitName,' loaded, removing from list');{$ENDIF}
  91. Idx:=IndexOfI(FInitUnitNames,aUnitName);
  92. if Idx>-1 then
  93. TJSArray(FInitUnitNames).splice(Idx,1);
  94. end;
  95. class function TUnitLoader.Instance: TUnitLoader;
  96. begin
  97. if (FInstance=Nil) then
  98. FInstance:=TUnitLoader.Create;
  99. Result:=FInstance;
  100. end;
  101. Procedure LoadIntf(aModule : JSValue); external name 'rtl.loadintf';
  102. Procedure LoadImpl(aModule : JSValue); external name 'rtl.loadimpl';
  103. var pas : TJSOBject; external name 'pas';
  104. function TUnitLoader.FindModule(aModuleName: string): JSValue;
  105. var
  106. Key: string;
  107. begin
  108. Result:=pas[aModuleName];
  109. if isModule(Result) then exit;
  110. for Key in pas do
  111. begin
  112. if not SameText(Key,aModuleName) then continue;
  113. Result:=pas[Key];
  114. if isModule(Result) then exit;
  115. end;
  116. Result:=nil;
  117. end;
  118. function TUnitLoader.HaveModule(aModuleName: string): Boolean;
  119. begin
  120. Result:=FindModule(aModuleName)<>nil;
  121. end;
  122. procedure TUnitLoader.InitModule(aTask: TLoadTask; const aName: String;
  123. aModule: JSValue);
  124. begin
  125. {$IFDEF DEBUGUNITLOADER} Writeln('Unit ',aName,' dependencies loaded. Initialising "',TJSObject(aModule)['$name'],'" ...');{$ENDIF}
  126. RemoveFromLoadList(aName);
  127. LoadIntf(aModule);
  128. LoadImpl(aModule);
  129. aTask.UnitLoaded(aName);
  130. end;
  131. function TUnitLoader.GetNeededDependencies(const aName: String; AModule: JSValue
  132. ): TStringDynArray;
  133. var
  134. l,u : TStringDynArray;
  135. m : String;
  136. begin
  137. SetLength(l,0);
  138. u:=TStringDynArray(TJSOBject(aModule)['$intfuseslist']);
  139. for m in u do
  140. if not (HaveModule(m) or IsInLoadList(m)) then
  141. TJSArray(l).push(m);
  142. u:=TStringDynArray(TJSOBject(aModule)['$impluseslist']);
  143. for m in u do
  144. if not (HaveModule(m) or IsInLoadList(m)) then
  145. TJSArray(l).push(m);
  146. Result:=l;
  147. end;
  148. function TUnitLoader.AreAllDependenciesLoaded(aTask: TLoadTask;
  149. const aName: String; AModule: JSValue): Boolean;
  150. begin
  151. Result:=Length(GetNeededDependencies(aName,aModule))=0;
  152. end;
  153. procedure TUnitLoader.DoDependenciesLoaded(const aUnitName : array of string; aData : TObject);
  154. begin
  155. UnitSourcesLoaded(aData);
  156. end;
  157. function TUnitLoader.IndexOfLoadUnit(aUnitName: String): integer;
  158. begin
  159. Result:=IndexOfI(FLoadList,aUnitName);
  160. end;
  161. procedure TUnitLoader.AddToLoadList(aUnitName: String);
  162. begin
  163. if IndexOfLoadUnit(aUnitName)<0 then
  164. TJSArray(FLoadList).Push(aUnitName);
  165. end;
  166. procedure TUnitLoader.RemoveFromLoadList(aUnitName: String);
  167. var
  168. idx : Integer;
  169. begin
  170. Idx:=IndexOfLoadUnit(aUnitName);
  171. if Idx>-1 then
  172. TJSArray(FLoadList).splice(Idx,1);
  173. end;
  174. function TUnitLoader.IsInLoadList(aUnitName: String): Boolean;
  175. begin
  176. Result:=IndexOfLoadUnit(aUnitName)>=0;
  177. end;
  178. procedure TUnitLoader.UnitSourcesLoaded(aData : TObject);
  179. Var
  180. aTask : TLoadTask;
  181. aModule : JSValue;
  182. aModuleName : String;
  183. Deps : TStringDynArray;
  184. begin
  185. {$IFDEF DEBUGUNITLOADER} Writeln('Succesfully loaded sources');{$ENDIF}
  186. aTask:=TLoadTask(aData);
  187. For aModuleName in aTask.LoadUnitNames do
  188. begin
  189. aModule:=FindModule(aModuleName);
  190. if aModule<>nil then
  191. begin
  192. {$IFDEF DEBUGUNITLOADER} Writeln(aModuleName+' is module. Loading interface');{$ENDIF}
  193. Deps:=GetNeededDependencies(aModuleName,aModule);
  194. if length(Deps)=0 then
  195. InitModule(aTask,aModuleName,aModule)
  196. else
  197. DoLoadUnits(Deps,@DoDependenciesLoaded,aData);
  198. end;
  199. end;
  200. if (aTask.AllLoaded) then
  201. aTask.CallLoaded;
  202. end;
  203. function TUnitLoader.GetUnitURL(const aUnitName: string): String;
  204. begin
  205. Result:=BaseURL;
  206. if (Result<>'') then
  207. Result:=Result+'/';
  208. Result:=Result+aUnitname+'.js';
  209. end;
  210. procedure TUnitLoader.LoadUnit(const aUnitName : string; aOnLoaded : TLoadedProcedure = Nil; aData : TObject = Nil);
  211. begin
  212. LoadUnits([aUnitName],aOnLoaded,aData);
  213. end;
  214. procedure TUnitLoader.LoadUnits(const aUnitNames: array of String; aOnLoaded: TLoadedProcedure; aData: TObject);
  215. begin
  216. if Length(FLoadList)>0 then
  217. Raise EUnitLoader.Create('Load operation in progress. Cannot load.');
  218. DoLoadUnits(aUnitNames,aOnLoaded,aData);
  219. end;
  220. procedure TUnitLoader.DoLoadUnits(const aUnitNames: array of String; aOnLoaded: TLoadedProcedure; aData: TObject);
  221. Var
  222. Scripts : TStringDynArray;
  223. aCount : Integer;
  224. S : String;
  225. begin
  226. aCount:=0;
  227. Setlength(Scripts,Length(aUnitNames));
  228. for s in aUnitNames do
  229. if Not HaveModule(S) then
  230. begin
  231. {$IFDEF DEBUGUNITLOADER} Writeln('Need to load unit: ',S);{$ENDIF}
  232. Scripts[aCount]:=GetUnitURl(S);
  233. AddToLoadList(S);
  234. inc(aCount);
  235. end;
  236. SetLength(S,aCount);
  237. if aCount=0 then
  238. begin
  239. // All is already loaded
  240. if Assigned(aOnLoaded) then
  241. aOnLoaded(aUnitNames,aData);
  242. end
  243. else
  244. LoadScripts(Scripts,@UnitSourcesLoaded,TLoadTask.Create(aUnitNames,aOnLoaded,aData));
  245. end;
  246. end.