Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scripting RFC #1

Merged
merged 2 commits into from Apr 5, 2019

Conversation

@Moxinilian
Copy link
Member

commented Oct 28, 2018

This RFC introduces our scripting proposal.
Rendered

@kabergstrom

@Moxinilian Moxinilian changed the title Create 0001-scripting.md Scripting RFC Oct 28, 2018

@Xaeroxe

This comment has been minimized.

Copy link
Member

commented Oct 31, 2018

I really like all of this. My only suggestion is a name change, rather than augmented FFI we should call it Automatically Idiomatic Foreign Function Interface. AI-FFI.

@fhaynes

This comment has been minimized.

Copy link
Member

commented Oct 31, 2018

The first one is to ban scripts from handling their own threading.

What about languages that use green threads?

@Moxinilian

This comment has been minimized.

Copy link
Member Author

commented Oct 31, 2018

Feel free to use GitHub's review features so we can have threads.
@Xaeroxe Sure, why not, I called it augmented FFI just for the context of the RFC, I wasn't planning on keeping the name.
@fhaynes I think this could fall under the "limited managed threading" kind of deal. But maybe it's been too long since I've read about green threads. I need to look at it again.
But like for example it would be perfectly fine to have Rust have threads, because Rust is smart. The constraints here are language-specific.

@fhaynes

This comment has been minimized.

Copy link
Member

commented Oct 31, 2018

Feel free to use GitHub's review features so we can have threads.
@Xaeroxe Sure, why not, I called it augmented FFI just for the context of the RFC, I wasn't planning on keeping the name.
@fhaynes I think this could fall under the "limited managed threading" kind of deal. But maybe it's been too long since I've read about green threads. I need to look at it again.
But like for example it would be perfectly fine to have Rust have threads, because Rust is smart. The constraints here are language-specific.

I mean more like threads that are managed by a language runtime, goroutines or Erlang processes for example, but the OS views as just one OS thread.

@Moxinilian

This comment has been minimized.

Copy link
Member Author

commented Oct 31, 2018

It would depend on specific cases then. I haven't thought about it yet. If you want to make an addition, please write a paragraph and PR it to my fork.

@fhaynes

This comment has been minimized.

Copy link
Member

commented Oct 31, 2018

In order to expose that API in an idiomatic way to the language they drive, the drivers are also provided with additional high level metadata, along with the FFI. For example, if a type is an iterator, the language driver will be noticed of this additional information so that it can, for example, do the necessary language-specific work for this iterator to be iterated over in a for loop (see the Lua example for more details).

Something about this bugs me, but I'm not sure what. Will have to think about it more. At a minimum, I think this will need a strict schema. I just have a suspicion that the driver code responsible for the the translation will end up messy. But we won't know until we try it.

Show resolved Hide resolved 0001-scripting.md Outdated
Show resolved Hide resolved 0001-scripting.md
@fhaynes

This comment has been minimized.

Copy link
Member

commented Oct 31, 2018

It looks pretty solid to me. I'd vote move forward with a minimal PoC using LuaJIT to get an idea of the difficulties associated with this level of genercism.

@torkleyy
Copy link
Member

left a comment

I think this is pretty similar to what was said before. I'm going to go through the details later.


Anyway, keep in mind that what approach is chosen can be different for each language, as different languages give us more or less flexibility (Rust as a scripting language for example already enforces all of this out of the box).

## Hotswapping

This comment has been minimized.

Copy link
@Marwes

Marwes Nov 5, 2018

Does this mean that script system(s) are run in a separate World(?). I don't remember where I read it but since there is no way to add or remove systems after constructing a World it was recommended to keep dynamic systems in a separate world to avoid needing to reconstruct everything everytime something is reloaded.

https://github.com/Marwes/shred-example

https://github.com/Marwes/shred-example/blob/9f6d6a62f0b4cf7f55c3e5e9ca851c76c8ec9dea/src/gluon_system.rs#L798-L800

This comment has been minimized.

Copy link
@torkleyy

torkleyy Nov 5, 2018

Member

The architecture Specs will have in the future will differ from the current quite a bit. It's already in design ;)

This comment has been minimized.

Copy link
@torkleyy

torkleyy Nov 5, 2018

Member

Regarding your comment, I think you're mixing up Dispatcher and World. Both are very likely going to change (the latter one also renamed to SystemGraph which will not need any of those workarounds).

We will quote here what I said in slide-rs/specs#462 regarding this issue:

* Creating a new component type at runtime could be handled by having a special non-generic storage type that would store `Vec<u8>`. Those `Vec<u8>` would contain the raw data of the new components. Then, those components would be exposed to the scripting runtime through newly generated C headers corresponding to the component schema the user provided. That however will mean that TypeId can no longer be the only thing determining ResourceId, as those storages would share the same type but a different ID. But it does not seem unreasonable to have ResourceId become a `(TypeId, InstanceId)`, at least from my uneducated point of view. Also, at the end of the execution, the dynamic component should be collapsed into a statically generated component to no longer need to store the data in a `Vec<u8>`. The typical wokflow would be: create your components on the fly, do your testing with it, and if it's too slow then reboot the game. It seems reasonable to me, and besides I don't really expect it to be that slow, especially if you're only using it for logic creation.
* Modifying existing component types on the fly would only work on component types that were created from the scripting environment, but I think it's a reasonable constraint. Modifying a component in dynamic state would simply be a matter of iterating over the `Vec<u8>` storage and moving the data inside around. Modifying a component in statically-collapsed state however is a bit trickier, but not that hard either. You need to replace all static instances of that component into a dynamic instance of the new component.

This comment has been minimized.

Copy link
@jchitel

jchitel Feb 19, 2019

Modifying existing component types on the fly would only work on component types that were created from the scripting environment, but I think it's a reasonable constraint.

Does this mean that scripts would not be able to modify core components like Transform? That seems like it would reduce the power of scripts quite a bit. Is there any reason that component methods couldn't be exposed? Or is there another way that scripts might access static components that I'm missing?

This comment has been minimized.

Copy link
@Moxinilian

Moxinilian Feb 19, 2019

Author Member

What I meant is that scripts can't change the type signature of components defined outside of the scripting environment, as in they can't modify them. In other words, if your project creates a Rust struct as a component, you won't be able to hot-reload its structure definition.

This comment has been minimized.

Copy link
@jchitel

jchitel Feb 19, 2019

Ah, you meant the component types, not component instances. So scripted systems will be able to read arbitrary storages from the world and modify the components in those storages, correct?

This comment has been minimized.

Copy link
@Moxinilian

Moxinilian Feb 19, 2019

Author Member

Not the systems. Scripted systems can do exactly as much as Rust systems.
But from the scripting ecosystem, you can't edit static Rust types.

@jmqualls

This comment has been minimized.

Copy link

commented Feb 28, 2019

My 2c as someone who is not (yet) a contributor but a prospective user:

A bird in the hand is worth 2 in the bush. Don't design an API for all languages up front. Just the fact that there exists an API separating the engine and the language is sufficient abstraction for a first pass.

Make the first language a mature one. Don't adopt someone else's technical debt. LuaJIT seems like a fine choice.

Once people can actually write and run some kind of script at all, then start thinking about what kinds of accommodations would have to be made to support MUMPS or Befunge or whatever. Don't be afraid to say no if the language concepts just don't map well to the engine data structures.

@Moxinilian

This comment has been minimized.

Copy link
Member Author

commented Mar 1, 2019

Could you give more details on the reasoning behind that conclusion?

@jmqualls

This comment has been minimized.

Copy link

commented Mar 1, 2019

Could you give more details on the reasoning behind that conclusion?

http://wiki.c2.com/?PrematureAbstraction
http://wiki.c2.com/?TracerBullets

@Moxinilian

This comment has been minimized.

Copy link
Member Author

commented Mar 2, 2019

I published a post on the Amethyst forums containing more details on the LuaJIT implementation.

@Moxinilian

This comment has been minimized.

Copy link
Member Author

commented Mar 2, 2019

@jmqualls I wouldn't call premature a solution to a problem all major game engines have failed at solving because of their legacy code and userbase. By only thinking about a single language, we get ourselves trapped in that language and might eventually be stuck with it. Unity is so stuck with C# that they ended up creating a custom compiler for it when they realized the language requirements had changed.

Besides, so far, this general purpose solution has not been in the way at all of the LuaJIT integration. If you want to consider this + LuaJIT as focusing on a single language first, then it's all good! It isn't very complicated either, we are just describing the essence of an ECS and the requirements for it to fit to a language.

@DianaNites

This comment has been minimized.

Copy link

commented Mar 2, 2019

One possibility is to have something that other languages can compile to easily.

Even Lua can fit this purpose, with several languages that can compile to it, including C#. Typescript to Lua compilers are particularly interesting to me, basically free types and other nice things, and much better IDE support than plain Lua.

@Moxinilian

This comment has been minimized.

Copy link
Member Author

commented Mar 3, 2019

@DianaNites My issue with this is that it would introduce one more potentially costly abstraction layer. For example, say we want to integrate Go with this method, we cannot take advantage of its pretty performant static builds. Also, this does limit what languages we want to integrate, as we wouldn’t have the opportunity to maintain different compilers to Lua.

But this approach is not incompatible with the one suggested in this RFC, so if it makes sense in some casss it can definitely be used.

@khionu

khionu approved these changes Apr 5, 2019

@fhaynes

This comment has been minimized.

Copy link
Member

commented Apr 5, 2019

Merging this as @khionu is satisfied.

@fhaynes fhaynes merged commit 0fad6d0 into amethyst:master Apr 5, 2019

@zicklag

This comment has been minimized.

Copy link

commented Jun 7, 2019

When does the builder phase happen in this design? The build is taken care of automatically by Amethyst or other Amethyst tooling without you having to re-compile Amethyst itself, right?

I want to understand whether or not I can build Amethyst itself one time, distribute that to users, and then allow them to write games with just the scripting languages and the Amethyst binary ( even if that includes more tooling like an Amethyst CLI ).

@Moxinilian

This comment has been minimized.

Copy link
Member Author

commented Jun 7, 2019

You are correct. The build is, at least during development, a dynamic process. If the language is a dynamic one (like Lua for example), then it would work very simarly to how it is commonly done. If the language is a more static one (like Rust for example), it’s a module of the engine that builds your code and dynamically links it without even needing to shut down the engine.

@erlend-sh

This comment has been minimized.

Copy link

commented Jul 26, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
You can’t perform that action at this time.