@@ -706,6 +706,106 @@ else if (!interactable && targetBlock == null)
706
706
}
707
707
});
708
708
709
+ expression .addContextFunction ("run_async" , 1 , (c , t , lv ) ->
710
+ {
711
+ CommandSourceStack source = ((CarpetContext ) c ).source ();
712
+ FunctionArgument callback = FunctionArgument .findIn (c , expression .module , lv , 1 , true , false );
713
+ String command = lv .get (0 ).getString ();
714
+ if (callback .function == null )
715
+ {
716
+ try
717
+ {
718
+ Component [] errorOutput = {null };
719
+ List <Component > output = new ArrayList <>();
720
+ OptionalLong [] returnValue = {OptionalLong .empty ()};
721
+ source .getServer ().getCommands ().performPrefixedCommand (
722
+ new SnoopyCommandSource (source , errorOutput , output , returnValue ),
723
+ command
724
+ );
725
+ }
726
+ catch (Exception ignored )
727
+ {
728
+ }
729
+ return Value .NULL ;
730
+ }
731
+ else if (callback .function .getArguments ().size () < 1 )
732
+ {
733
+ throw new InternalExpressionException ("callback requires 1 argument for command output" );
734
+ }
735
+ boolean [] isCallbackConsumed = {false };
736
+ try
737
+ {
738
+ boolean [] hasCommandError = {false };
739
+ Integer [] commandExitCode = {null };
740
+ Component [] errorOutput = {null };
741
+ List <Component > output = new ArrayList <>();
742
+ OptionalLong [] ignoredReturnedValue = {OptionalLong .empty ()};
743
+
744
+ SnoopyCommandSource snoopyCommandSource = (SnoopyCommandSource ) new SnoopyCommandSource (source , errorOutput , output , ignoredReturnedValue )
745
+ .withCallback ((success , exitCode ) -> {
746
+ if (isCallbackConsumed [0 ])
747
+ {
748
+ return ;
749
+ }
750
+ isCallbackConsumed [0 ] = true ;
751
+ // The CommandSourceStack.sendFailure are sent after the resultConsumer, so the error output is always empty in
752
+ // this resultConsumer. This workaround is using the resultConsumer in tandem with an errorCallback.
753
+ if (!success )
754
+ {
755
+ hasCommandError [0 ] = true ;
756
+ commandExitCode [0 ] = exitCode ;
757
+ return ;
758
+ }
759
+ List <Value > args = List .of (ListValue .of (
760
+ NumericValue .of (exitCode ),
761
+ ListValue .wrap (output .stream ().map (FormattedTextValue ::new )),
762
+ FormattedTextValue .of (errorOutput [0 ])
763
+ ));
764
+ callback .function .callInContext (c , t , args );
765
+ });
766
+ // To catch errors that don't call the resultConsumer and make sure the error output is set for those that do.
767
+ snoopyCommandSource .setErrorCallback ((ignored ) -> {
768
+ if (isCallbackConsumed [0 ])
769
+ {
770
+ if (!hasCommandError [0 ])
771
+ {
772
+ return ;
773
+ }
774
+ List <Value > args = List .of (ListValue .of (
775
+ NumericValue .of (commandExitCode [0 ]),
776
+ ListValue .wrap (output .stream ().map (FormattedTextValue ::new )),
777
+ FormattedTextValue .of (errorOutput [0 ])
778
+ ));
779
+ callback .function .callInContext (c , t , args );
780
+ return ;
781
+ }
782
+ isCallbackConsumed [0 ] = true ;
783
+ List <Value > args = List .of (ListValue .of (
784
+ Value .NULL ,
785
+ ListValue .of (),
786
+ FormattedTextValue .of (errorOutput [0 ])
787
+ ));
788
+ callback .function .callInContext (c , t , args );
789
+ });
790
+
791
+ source .getServer ().getCommands ().performPrefixedCommand (snoopyCommandSource , command );
792
+ }
793
+ catch (Exception exc )
794
+ {
795
+ if (!isCallbackConsumed [0 ])
796
+ {
797
+ isCallbackConsumed [0 ] = true ;
798
+ List <Value > args = List .of (ListValue .of (
799
+ Value .NULL ,
800
+ ListValue .of (),
801
+ new FormattedTextValue (Component .literal (exc .getMessage ()))
802
+ ));
803
+ callback .function .callInContext (c , t , args );
804
+ }
805
+ }
806
+ return Value .NULL ;
807
+ });
808
+
709
809
expression .addContextFunction ("save" , 0 , (c , t , lv ) ->
710
810
{
711
811
CommandSourceStack s = ((CarpetContext ) c ).source ();
0 commit comments