simon816 / Command-Block-Assembly

Compile high-level code into Minecraft commands
https://www.simon816.com/minecraft/assembler
MIT License
268 stars 31 forks source link

Are there plans to update to Minecraft Version 1.15.2 ? #12

Open CorneliaXaos opened 4 years ago

CorneliaXaos commented 4 years ago

I know it just came out, and all, but I was wondering if there were plans to update this to Minecraft 1.15.2. I also wanted to gauge how "alive" the project is, which seems decently so given the last commit was about four months ago. (There's no in-progress branches for me to peek at. :stuck_out_tongue: )

Also, truly awesome work. I was just wondering if something like this would be possible.. and stumbled across MCC. Great stuff!

simon816 commented 4 years ago

I haven't worked on this in a while but I do intend to continue at some point. I think I hit a small roadblock with the design of the CBL type system, so will need to address that.

I will test it on 1.15 next time I work on it, I expect it will work on 1.15 now since I don't think there are many breaking changes.

CorneliaXaos commented 4 years ago

Thanks for the fast reply. The whole MCC is awesome and I just wanted to let you know. I've got lots of little questions, musings, and ideas, though they can all come in different issues or whatnot at some point in the future. (I am trying to see how.. "feature complete" CBL is before I decide to use it to make something.) I'll be sure to ask my questions after I've fiddled around a bit more. (Also.. here or new issue? I'm inclined to 1 question per issue, but that could result in a flood pretty fast. :stuck_out_tongue: )

chorman0773 commented 4 years ago

I certainly kept one thread for asking questions, so I’d assume there’s no problem with that

On Wed, Mar 4, 2020 at 21:21 Cornelia Xaos notifications@github.com wrote:

Thanks for the fast reply. The whole MCC is awesome and I just wanted to let you know. I've got lots of little questions, musings, and ideas, though they can all come in different issues or whatnot at some point in the future. (I am trying to see how.. "feature complete" CBL is before I decide to use it to make something.) I'll be sure to ask my questions after I've fiddled around a bit more. (Also.. here or new issue? I'm inclined to 1 question per issue, but that could result in a flood pretty fast. 😛 )

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/simon816/Command-Block-Assembly/issues/12?email_source=notifications&email_token=ABGLD22TSMSC3ZHXLXKXFNDRF4EEPA5CNFSM4LAY2HUKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEN3OFEI#issuecomment-594993809, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABGLD272KNVA2WM35OPYKIDRF4EEPANCNFSM4LAY2HUA .

simon816 commented 4 years ago

Feel free to ask wherever. Probably easier to ask questions in a single thread. I can split out a feature request into it's own issue if need be.

The core language and backend design are pretty much there. The main thing lacking is APIs. The main thing I need is to have some practical applications, then the APIs can be developed to suite the needed features.

CorneliaXaos commented 4 years ago

Cool.. so.. I composed a bunch of things offline.. here they are:


Why "cmdl" for CBL?

The assembly files have "asm"; the c-like files have "c"; the intermediate representation files have "ir". Why do CBL files have "cmdl" instead of "cbl"?

Why is there a "place location" for data pack defintion files?

I assume the command blocks are used for some kind of tracking mechanism for the "virtual machine." If there's really no other way for this to work without creating invisible armor stands and command block files at specific locations, would it be worth it to create some kind of "base" datapack that all data packs built with MCC would depend on. (Might require "Interoperation" below.)

This base data pack could assume that the bedrock layers in a world are undisturbed and manage doling out space to data packs that are built using MCC. A data pack could use a function to request storage in a specific size and be given coordinates for a block space that's available. Furthermore, these chunks could be /forceload 'd in order to keep the datapacks running in the background. The forceload may depend on the function used to request data or may simply be forceloaded for all MCC packs (and space kept as tight as possible).

Compartmentalization of Generated mcfunction's

Currently, judging from simply perusing output, when a file contains multiple subroutines each one gets a top-level "mcfunction" file in the datapack underneath the specified namespace (determined by the file name). I.e. "fib/functions/main.mcfunction" Supporting functions for a subroutine, like main, get put in another namespace nested underneath that matches the name of the function.

This results in the "/function" command displaying something like this:

/function fib:main /function fib:main/0ret /function fib:main/after_cmp /function fib:main/cmp_false ...

A means to hide the non-target functions from end users would be nice. This could be something as simple as prefacing all "nested" functions with "internal" or some other scheme:

/function fib:main/internal/0ret

/function fib:internal/main/0ret

/function fib:_/main/0ret

Another means might be to promote top-level functions similar to the third option above. Hopefully, Minecraft will eventually allow "hiding" functions from the /function command that the player can see or execute directly.

This isn't super important, but I wonder if it would potentially help with user experience.

Interoperation between Data Packs

One thing that would be potentially very interesting would be interoperation between different data packs compiled using MCC or from other means of authorship. This is sort of possible with the 'C' compiler due to the "CMD" command, but when it comes to CBL, I couldn't find anything matching this. Some sort of means of defining a function whose implementation is "/function namespace:path/to/function.mcfunction" would allow creating libraries of a sort. This might require a lot more work in order to wait for the function to complete or yield (yielding, by the way, is another question I have below).

Something like this:

[Function: namespace/path/to/function.mcfunction]
void call_specific_function();

[Subroutine: namespace:routine] // maps to: [Function: namespace/routine.mcfunction]
void call_mcc_function();

This could even potentially be used by functions in separate files but the same data pack, allowing a single file to represent a namespace and subroutines within that file as "top-level" functions. The ultimate goal, though, would be allowing CBL programs to call functions that weren't apart of the original translation unit, such as those written by hand by other users. (Would provide a work-around for accessing new functions that aren't available through CBL includes yet.. also another question way down below.)

Of course, this then potentially requires some kind of tie-in to "Compartmentalization" above whereby functions could be marked as exported or not, resulting in them being "hidden" from the public.

Autoruns or Looped Functions

Currently, all functions must be manually invoked (probably to determine where to place the armor stand). If that problem could be resolved it would be useful to specify that a function is "Auto-ran" or "Looped", placing a call to the routine in the appropriate "minecraft/tags/functions/*.json" file during datapack creation. As of right now, this would have to be done manually and I'm not quite sure of the effect of doing so..

Yielding

Is it currently possible to have a function be called, do some execution, yield back to the caller, who finishes executing, and the next time that function is called it picks up where it left off for the yield? Long run on sentence, but I think you get what I mean. I know you can sort of do this for syncing on game ticks.. but this would be more like stateful functions.. I imagine if you wrote your function the right way it would be possible, but would require you make sure your data is stored safely.

Minecraft Command Coverage for CBL

The biggest limitation I've found so far is the lack of "Command Coverage" for CBL. That is to say, there's no includes to do things like applying effects, generating particles, querying a biome, manipulating NBT etc. I'd recommend allowing all minecraft commands to be called directly through some sort of "low level" include library. Higher-level libraries could then build on those libraries to provide functions for making custom items or spawning mobs in.

Wall Clock Time

Any possible way to get the wallclock time so that functions could potentially self-modulate and use as much time as possible while targeting a frame rate? (This might require work on Mojang's end.)


I also compiled "fib" (using CBL) and "tetris" (using C) and ran on 1.5.2. Fib worked perfectly. Tetris required some modification.. mainly a limiter to slow down how fast the row advanced.. I had to lower it to only advancing a row between 0.5 and 1 seconds to be playable.. otherwise blocks just zoomed in and filled up quickly.

I think there are some bugs when compiling for 1.15.2, though.. Namely, rows didn't disappear, and when blocks backed up it just sort of.. rainbow clobbered on top of itself and changed blocks every frame. It looks pretty cool, though.


But yea, I was hoping to use CBL to make a data pack, because writing raw commands is so tedious, but it doesn't look like it's far along enough yet to be super useful. Still, very awesome work! I got giddy when tetris actually started running. :stuck_out_tongue:

simon816 commented 4 years ago

Why "cmdl" for CBL?

I found that .cbl was used for Cobol files so I went for cmdl aka "command language". Probably doesn't matter so maybe I'll change this to be consistent.

Why is there a "place location" for data pack defintion files?

If command blocks are used, they need to go somewhere. MCC does not define any constraints where they go.

Command blocks are only needed for certain instructions (In CBL they are used for the Game.tick() and async features), if these are not used then command blocks are not generated.

The invisible armor stand is required to store state (local/global variables etc), so it is required if state is used. (See https://github.com/simon816/Command-Block-Assembly/wiki/Command-IR#global-entity for what is stored on this entity).

would it be worth it to create some kind of "base" datapack that all data packs built with MCC would depend on.

I've been considering integrating "Minecraft Phi" https://github.com/MinecraftPhi/MinecraftPhi-modules which defines a common standard like how you describe.

Compartmentalization of Generated mcfunction's

I expect that most of the time players wouldn't use /function directly. Instead, functions would be invoked from events (such as tick event, pressure plate triggering a command block). I have looked into a few datapacks made by other people and they too sometimes have internal functions at the same level as end-user functions. I think adding a configuration option to the datapack definition file would suffice.

Interoperation between Data Packs

Something the assembler has had since its inception is #include_h ("include headers"). This has been somewhat neglected from Command IR, it would be good to re-vitalise this. Command IR effectively compiles everything "statically" i.e. no dynamic libraries. One difficulty is crossing data back and forth from one datapack to another - in the case of two separate datapacks compiled with MCC, a separate global entity will exist for both datapacks, making data sharing challenging.

For external interfaces that just need to call an mcfunction file, that'd be easy enough. I want to discourage this from within CBL, because you loose type safety and guarantees of state consistency. In the lower-level Command IR it's possible like so:

$cmd = command "/function my_function"
run_cmd $cmd

Autoruns or Looped Functions

This is actually already possible, in Command IR at least. The initialisation takes place in a minecraft:load tag hook, and it's possible to add custom hooks with setupfn. The minecraft:tick event is also supported in IR. I don't think they are exposed in CBL but I intend on adding some load and tick hooks.

Yielding

Not currently possible, but there is async/await support, which has some similarities. There'd need to be a way of preserving the current state of execution and later resuming. I think this is doable, but does add some complexity to the implementation.

Minecraft Command Coverage for CBL

Most of my time working on CBL so far has been language design and figuring out how to transform high level constructs into Command IR. I've added a selection of supported commands to Command IR, which can be found on the reference page - https://github.com/simon816/Command-Block-Assembly/wiki/Command-IR-Instruction-Reference

I am thinking of adding a command IR "statement" to CBL, something like:

cmd_ir {
$x = 0
...
}

From there, the raw command and run_cmd can be used to get access to things not expose in CBL directly.

Most of the commands I have added support for so far have been for things requiring additional support (e.g. the /execute family of commands). For "basic" commands, it should be easy enough just to add the necessary APIs without much backend work.

Wall Clock Time

I've seen a technique using /worldborder that does this. The world border command allows the wall clock time to be obtained (though it does require exclusive access to the world border - multiple datapacks may conflict).

I first came across this technique from this video - https://www.youtube.com/watch?v=lhJM9LmD2Gg the tickbuster datapack takes this idea and packages it up into a datapack - https://github.com/Arcensoth/tickbuster-datapack

There is no support for this in MCC, however it may be possible to integrate with tickbuster.


Tetris runs too fast on my desktop too. It was originally written on my laptop which was getting ~15 fps so I didn't notice at the time. I never finished the implementation; there's no end game state - tetriminos will just overwrite the top of the screen as you've observed. If rows don't clear when they are complete then yes that is a bug.


Thanks for these questions :slightly_smiling_face: It's helpful to know what features are lacking so they can be prioritised. If you think CBL/MCC may be beneficial for your project I'm happy to help make it more usable. Perhaps just drafting code (pseudocode even) could help guide the APIs needed. If you're familiar with Python you could attempt to discover what madness lies within :stuck_out_tongue: There's a lot of work still left to do and only one of me to do it.

simon816 commented 4 years ago

I've just gotten around to looking at what's new in 1.15. There are two interesting new features:

I don't see any changes that would break the current implementation, so all existing features are forward-compatible with 1.15.

CorneliaXaos commented 4 years ago

Late responding to this, as I've been a bit busy, but I have been keeping an eye on the project as a whole.

If command blocks are used, they need to go somewhere.

I've been considering integrating "Minecraft Phi"

Part of my reasoning for a base data pack would be to have a uniform way of placing Command Blocks such that multiple datapacks made using CBL wouldn't require the person setting up the server to make sure things stayed loaded in certain chunks and that individual packs wouldn't try to place at the same location.

This data pack could be where these system-wide functionality be stored.

Minecraft Phi, itself, looks pretty neat, but also brings along the same kinds of issues with the entity that shouldn't be killed. It would be nice if Mojang exposed some actual memory to data pack crafters instead of requiring weird hacks with NBT data.

I expect that most of the time players wouldn't use /function directly.

True.. but I've recently discovered "/trigger" and, apparently, it is expected that players use that for some data pack functionality. I've seen one where it turns on / off hud elements for coordinates and compass-like directions.

One difficulty is crossing data back and forth from one datapack to another - in the case of two separate datapacks compiled with MCC, a separate global entity will exist for both datapacks, making data sharing challenging.

This seems solvable with the "base data pack" that gets shipped out with user code. This pack could do all the middle management.

I am thinking of adding a command IR "statement" to CBL

That would work.. but I think it would also be useful to just be able to write pure Minecraft commands without having to do some kind of double-nesting:

cmd_ir {
CMD( /* do thing */ )
}

// maybe this?
cmd {
    /* do thing */
    /* do other thing */
    /* basically, this would just be dropped in wherever it would occur in the compiled output */
}

And, still, on top of this, it would probably be useful to have CBL api's for every builtin command.


Another thing that might be useful with a base data pack would be support for compiled user code to tell the base data pack the longest uninterrupted string of commands. That way, the base datapack could go ahead and set the max commands to the appropriate value to allow all called code to run correctly without a user manually setting the number to some ridiculously high value.

Alternatively, the base data pack would just set it to max and hope that's enough.

simon816 commented 4 years ago

Interoperation between Data Packs

This is now possible from this commit: c99ed910415e367f17c9897d1f56e5382fbacc74

I'll write some documentation for it once I finish some refactoring.

Functions and global variables can now be assigned a namespace in which they reside. Further, mcc can link with datapacks from other namespaces. Rather than copying code (static linking) it will only refer to variables and functions from the other datapack. This is a simple mechanism for dynamic linking. This works in a similar way to how .so files are linked.

Here is an example of switching namespace:

namespace ns1;

int x;

void foo() {
    x = 10;
}

namespace ns2;

int y;

void bar() {
    x = 20;
    y = 30;
}

// Annotation will force a namespace regardless of context
[Namespace ext]
void ext() {
    x += y;
}

no namespace;

void main() {
    foo();
    bar();
    ext();
}

Using a datapack definition where namespace=ns:

data/ns1/functions/foo.mcfunction

scoreboard players set @e[tag=ns1_global,limit=1] g_x 10

data/ns2/functions/bar.mcfunction

scoreboard players set @e[tag=ns1_global,limit=1] g_x 20
scoreboard players set @e[tag=ns2_global,limit=1] g_y 30

data/ext/functions/ext.mcfunction

scoreboard players operation @e[tag=ext_global,limit=1] reg_0 = @e[tag=ns1_global,limit=1] g_x
scoreboard players operation @e[tag=ext_global,limit=1] reg_0 += @e[tag=ns2_global,limit=1] g_y
scoreboard players operation @e[tag=ns1_global,limit=1] g_x = @e[tag=ext_global,limit=1] reg_0

data/ns/functions/main.mcfunction

function ns1:foo
function ns2:bar
function ext:ext

Notice how the tag=*_global selector chooses the tag based on which namespace the variable is defined in.

An example of dynamic linking:

link1.cmdl defines a function main which depends on do_something and a global, x (both of which are not defined here, they are known by name only).

extern int x;

void do_something();

void main() {
    x = 1;
    do_something();
}

link2.cmdl defines int x and do_something:

int x;

void do_something() {
    x = 2;
}

(Also there's link1.dpd and link2.dpd which define the respective namespace of the datapack)

I then compile link2 with the -shared flag:

./mcc.py -shared link2.cmdl link2.dpd

This generates link2.zip:

data/link2/functions/do_something.mcfunction

scoreboard players set @e[tag=link2_global,limit=1] g_x 2

Now I compile link1 with reference to link2.zip:

./mcc.py link2.zip link1.cmdl link1.dpd

This creates link1.zip:

data/link1/functions/main.mcfunction

scoreboard players set @e[tag=link2_global,limit=1] g_x 1
function link2:do_something

link1 is able to reference variables and functions from link2 without including link2 in it's own datapack.

(For brevity I've omitted the setup functions that create the global entity and scoreboard objectives).

CorneliaXaos commented 4 years ago

This will definitely be useful and will allow for modularizing code. I could see this being used for reserving space in the world via the "link2" datapack... though I would have to figure out how to pass data around. (My guess is with using NBT trickery.. having data packs reference an entity in the shared datapack for storing arguments and whatnot.. might not be easy to do currently, unless I'm missing something.)

I imagine the reason you need to compile link1 with link2.zip as a reference is for type safety and whatnot? Could this be expanded to call into datapacks and functions not authored using CBL?

simon816 commented 4 years ago

though I would have to figure out how to pass data around. (My guess is with using NBT trickery.. having data packs reference an entity in the shared datapack for storing arguments and whatnot.. might not be easy to do currently, unless I'm missing something.)

MCC creates a setup function in every datapack it generates that spawns entities with tags based on the namespaces used within the datapack.

execute unless entity @e[tag=ns1_global,limit=1] kill @e[tag=ns1_global,limit=1]
execute unless entity @e[tag=ns1_global,limit=1] summon armor_stand ~ ~ ~ {Tags:["ns1_global"],ArmorItems:[{id:"minecraft:stone",Count:1b,tag:{stack:[],globals:{},working:{int:0}}}],NoAI:1b,Invisible:1b,Small:0b,NoGravity:1b,Marker:1b,Invulnerable:1b,NoBasePlate:1b}
execute unless entity @e[tag=ext_global,limit=1] kill @e[tag=ext_global,limit=1]
execute unless entity @e[tag=ext_global,limit=1] summon armor_stand ~ ~ ~ {Tags:["ext_global"],ArmorItems:[{id:"minecraft:stone",Count:1b,tag:{stack:[],globals:{},working:{int:0}}}],NoAI:1b,Invisible:1b,Small:0b,NoGravity:1b,Marker:1b,Invulnerable:1b,NoBasePlate:1b}
execute unless entity @e[tag=ns2_global,limit=1] kill @e[tag=ns2_global,limit=1]
execute unless entity @e[tag=ns2_global,limit=1] summon armor_stand ~ ~ ~ {Tags:["ns2_global"],ArmorItems:[{id:"minecraft:stone",Count:1b,tag:{stack:[],globals:{},working:{int:0}}}],NoAI:1b,Invisible:1b,Small:0b,NoGravity:1b,Marker:1b,Invulnerable:1b,NoBasePlate:1b}

I realise the kill is redundant now (as the entity would never exist if the execute unless passed)

Each function uses the entity belonging to it's namespace for local variables and parameters.

Parameter passing is handled transparently; If ns1:foo called ns2:bar with a parameter, the parameter would be copied from the ns1 stack into the ns2 stack. Likewise return variables are pulled out of ns2's stack back to ns1's stack.

Similarly, global variables are attached to the entity representing the namespace of the variable.

I decided to make namespaces not play a role in variable and function lookup resolution. This makes everything much easier as global variables therefore only exist in one place and a call to a function foo() will not be ambiguous. This does mean that having two functions called ns1:foo and ns2:foo is not possible due to conflicts on the name foo. (They can co-exist just fine at runtime, just that both cannot be referred to by a single datapack).

I imagine the reason you need to compile link1 with link2.zip as a reference is for type safety and whatnot? Could this be expanded to call into datapacks and functions not authored using CBL?

It's needed as MCC requires metadata about what functions and variables are accessible, along with their types. When the -shared flag is used, this metadata is written to __mcc__.json inside the datapack zip. For link2.zip it's:

{
    "symbol_table": {
        "functions": {
            "do_something": {
                "_name": "link2:do_something",
                "params": [],
                "returns": [],
                "flags": {
                    "pure": false
                }
            }
        },
        "variables": {
            "pos_util__ptr_ptr": {
                "score": "g_pos_util__ptr_ptr",
                "type": "i32",
                "namespace": "link2"
            },
            "x": {
                "score": "g_x",
                "type": "i32",
                "namespace": "link2"
            }
        }
    },
    "reserved_funcs": ["link2:setup"]
}

It's similar in operation to linking to .so files in gcc, e.g. -lm to link against libm.so.

For linking to datapacks not created by MCC I see two options:

  1. Relax the requirement that a strong definition is required for successful compilation. What I mean by strong definition is that MCC knows where a variable or function exists ("is it in my datapack, another datapack, or missing?"). Instead, the programmer could say "function foo exists at ns:foo, just trust me on this" and MCC would accept that, rather than requiring the definition to be seen.
  2. Create a stub/compat datapack that defines the desired functions / variables for use with linking only. Essentially just to generate the __mcc__.json file (Related stackoverflow question).
simon816 commented 4 years ago

Why "cmdl" for CBL?

I've decided to rename them to .cbl files f4c01bdc404bf0ad09138f52b2455a4dbd421274. Also discovered that you can force a specific syntax highlighter on GitHub, so have made them highlight as C++ files.

Compartmentalization of Generated mcfunction's

I've moved internally generated commands to namespace:_internal/... fdc68cd722f8c0d723026eade20585bc0cd6d509.
Although the underscore makes them bubble to the top of the function list, you can browse the function suggestion list in reverse which is nicer than it being interspersed throughout.

simon816 commented 4 years ago

Autoruns or Looped Functions

I've improved the event handler API in a256feac679b5583d9b8fcedb618fe52a98455d2.

There are two types of events: tag events and advancement events (the wiki describes them here).

In CBL, the Events built in API defines base vanilla events. Events.tick is fired every tick, Events.load is fired on datapack load.

It's also possible to define custom events that will invoke the handlers of all functions listening to the event. The mechanism for this is with function tags.

Here is an example:

include "Events"
include "Text"

TagEvent customTick = TagEvent("custom_tick");

// Listen to the built in tick event
[Event Events.tick]
void on_tick() {
    // fire the custom event
    customTick.fire();
}

// Listen to the custom event
[Event customTick]
void on_custom_tick() {
     // print message
    Text t;
    t += "Custom tick fired";
    t.send_to_all();
}

The following datapack is created:

data/ns/functions/on_tick.mcfunction

function #ns:custom_tick

data/ns/functions/on_custom_tick.mcfunction

tellraw @a [{"text":"Custom tick fired"}]

data/minecraft/tags/functions/tick.json

{"values": ["ns:on_tick"], "replace": false}

data/ns/tags/functions/custom_tick.json

{"values": ["ns:on_custom_tick"], "replace": false}

The on_tick function forwards the event to listeners of ns:custom_tick. As expected, when loaded in game the "Custom tick fired" message is printed in chat every tick.


Regarding the suggestion to embed commands directly, I created a built in macro raw_command that takes a constant string - the command. This string does have to be quoted for it to parse properly.

Use it like so:

void main() {
    raw_command("say hello");
}
FinnT730 commented 4 years ago

Wait..... so I know I am a bit late to this issue.... but it is / was not for 1.15.2 yet? Only for 1.14? That might be why some of the things did not work then... Oh well, good to know! Keep it up!

simon816 commented 4 years ago

@FinnT730 it works on both 1.15 and 1.14. I have been testing changes on 1.15 and will likely drop support for 1.14 at some point. If something doesn't work, there may be a bug in the compiler. It would be helpful to know if you've encountered any issues.

FinnT730 commented 4 years ago

Nah, I don't think it is a compiler issue haha. The things I try to make are the things I expect to break. A good example of this would be me creating a float number or something like that, because you can only use int(s) and floats don't work with Minecraft.