5454import java .nio .file .Path ;
5555import java .nio .file .StandardCopyOption ;
5656import java .util .ArrayList ;
57+ import java .util .Collections ;
5758import java .util .HashMap ;
59+ import java .util .HashSet ;
5860import java .util .LinkedHashMap ;
5961import java .util .List ;
6062import java .util .Map ;
63+ import java .util .Set ;
64+ import java .util .concurrent .atomic .AtomicReference ;
6165import java .util .function .BiConsumer ;
66+ import java .util .function .Consumer ;
6267import java .util .regex .Pattern ;
6368
6469@ RequiredArgsConstructor
@@ -167,6 +172,8 @@ protected void loadAllExtensions(@NonNull ExtensionManager extensionManager) {
167172
168173 Map <String , Path > extensions = new LinkedHashMap <>();
169174 Map <String , GeyserExtensionContainer > loadedExtensions = new LinkedHashMap <>();
175+ Map <String , GeyserExtensionDescription > descriptions = new LinkedHashMap <>();
176+ Map <String , Path > extensionPaths = new LinkedHashMap <>();
170177
171178 Path updateDirectory = extensionsDirectory .resolve ("update" );
172179 if (Files .isDirectory (updateDirectory )) {
@@ -195,10 +202,126 @@ protected void loadAllExtensions(@NonNull ExtensionManager extensionManager) {
195202 });
196203 }
197204
198- // Step 3: Load the extensions
205+ // Step 3: Order the extensions to allow dependencies to load in the correct order
199206 this .processExtensionsFolder (extensionsDirectory , (path , description ) -> {
200- String name = description .name ();
201207 String id = description .id ();
208+ descriptions .put (id , description );
209+ extensionPaths .put (id , path );
210+
211+ }, (path , e ) -> {
212+ logger .error (GeyserLocale .getLocaleStringLog ("geyser.extensions.load.failed_with_name" , path .getFileName (), path .toAbsolutePath ()), e );
213+ });
214+
215+ // The graph to back out loading order (Funny I just learnt these too)
216+ Map <String , List <String >> loadOrderGraph = new HashMap <>();
217+
218+ // Looks like the graph needs to be prepopulated otherwise issues happen
219+ for (String id : descriptions .keySet ()) {
220+ loadOrderGraph .putIfAbsent (id , new ArrayList <>());
221+ }
222+
223+ for (GeyserExtensionDescription description : descriptions .values ()) {
224+ for (Map .Entry <String , GeyserExtensionDescription .Dependency > dependency : description .dependencies ().entrySet ()) {
225+ String from = null ;
226+ String to = null ; // Java complains if this isn't initialised, but not from, so, both null.
227+
228+ // Check if the extension is even loaded
229+ if (!descriptions .containsKey (dependency .getKey ())) {
230+ if (dependency .getValue ().isRequired ()) { // Only disable the extension if this dependency is required
231+ // The extension we are checking is missing 1 or more dependencies
232+ logger .error (
233+ GeyserLocale .getLocaleStringLog (
234+ "geyser.extensions.load.failed_dependency_missing" ,
235+ description .id (),
236+ dependency .getKey ()
237+ )
238+ );
239+
240+ descriptions .remove (description .id ()); // Prevents it from being loaded later
241+ }
242+
243+ continue ;
244+ }
245+
246+ if (
247+ !(description .humanApiVersion () >= 2 &&
248+ description .majorApiVersion () >= 9 &&
249+ description .minorApiVersion () >= 0 )
250+ ) {
251+ logger .error (
252+ GeyserLocale .getLocaleStringLog (
253+ "geyser.extensions.load.failed_cannot_use_dependencies" ,
254+ description .id (),
255+ description .apiVersion ()
256+ )
257+ );
258+
259+ descriptions .remove (description .id ()); // Prevents it from being loaded later
260+
261+ continue ;
262+ }
263+
264+ // Determine which way they should go in the graph
265+ switch (dependency .getValue ().getLoad ()) {
266+ case BEFORE -> {
267+ from = dependency .getKey ();
268+ to = description .id ();
269+ }
270+ case AFTER -> {
271+ from = description .id ();
272+ to = dependency .getKey ();
273+ }
274+ }
275+
276+ loadOrderGraph .get (from ).add (to );
277+ }
278+ }
279+
280+ Set <String > visited = new HashSet <>();
281+ List <String > visiting = new ArrayList <>();
282+ List <String > loadOrder = new ArrayList <>();
283+
284+ AtomicReference <Consumer <String >> sortMethod = new AtomicReference <>(); // yay, lambdas. This doesn't feel to suited to be a method
285+ sortMethod .set ((node ) -> {
286+ if (visiting .contains (node )) {
287+ logger .error (
288+ GeyserLocale .getLocaleStringLog (
289+ "geyser.extensions.load.failed_cyclical_dependencies" ,
290+ node ,
291+ visiting .get (visiting .indexOf (node ) - 1 )
292+ )
293+ );
294+
295+ visiting .remove (node );
296+ return ;
297+ }
298+
299+ if (visited .contains (node )) return ;
300+
301+ visiting .add (node );
302+ for (String neighbor : loadOrderGraph .get (node )) {
303+ sortMethod .get ().accept (neighbor );
304+ }
305+ visiting .remove (node );
306+ visited .add (node );
307+ loadOrder .add (node );
308+ });
309+
310+ for (String ext : descriptions .keySet ()) {
311+ if (!visited .contains (ext )) {
312+ // Time to sort the graph to get a load order, this reveals any cycles we may have
313+ sortMethod .get ().accept (ext );
314+ }
315+ }
316+ Collections .reverse (loadOrder ); // This is inverted due to how the graph is created
317+
318+ // Step 4: Load the extensions
319+ for (String id : loadOrder ) {
320+ // Grab path and description found from before, since we want a custom load order now
321+ Path path = extensionPaths .get (id );
322+ GeyserExtensionDescription description = descriptions .get (id );
323+
324+ String name = description .name ();
202325 if (extensions .containsKey (id ) || extensionManager .extension (id ) != null ) {
203326 logger .warning (GeyserLocale .getLocaleStringLog ("geyser.extensions.load.duplicate" , name , path .toString ()));
204327 return ;
@@ -222,20 +345,22 @@ protected void loadAllExtensions(@NonNull ExtensionManager extensionManager) {
222345 }
223346 }
224347
225- GeyserExtensionContainer container = this .loadExtension (path , description );
226- extensions .put (id , path );
227- loadedExtensions .put (id , container );
228- }, (path , e ) -> {
229- logger .error (GeyserLocale .getLocaleStringLog ("geyser.extensions.load.failed_with_name" , path .getFileName (), path .toAbsolutePath ()), e );
230- });
348+ try {
349+ GeyserExtensionContainer container = this .loadExtension (path , description );
350+ extensions .put (id , path );
351+ loadedExtensions .put (id , container );
352+ } catch (Throwable e ) {
353+ logger .error (GeyserLocale .getLocaleStringLog ("geyser.extensions.load.failed_with_name" , path .getFileName (), path .toAbsolutePath ()), e );
354+ }
355+ }
231356
232- // Step 4 : Register the extensions
357+ // Step 5 : Register the extensions
233358 for (GeyserExtensionContainer container : loadedExtensions .values ()) {
234359 this .extensionContainers .put (container .extension (), container );
235360 this .register (container .extension (), extensionManager );
236361 }
237362 } catch (IOException ex ) {
238- ex . printStackTrace ( );
363+ logger . error ( "Unable to read extensions." , ex );
239364 }
240365 }
241366
0 commit comments