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

gvasm v3 ideas #43

Open
velipso opened this issue Jul 22, 2023 · 4 comments
Open

gvasm v3 ideas #43

velipso opened this issue Jul 22, 2023 · 4 comments

Comments

@velipso
Copy link
Owner

velipso commented Jul 22, 2023

The big feature for v3 should be a system for distributing libraries (sharing code).

I should be able to pull in a music library, or a saving library, and everything should be able to work easily.

One big element of that is iwram / ewram allocation. Specifically:

  1. Structs should be able to allocate static memory, with layering.
  2. Code blocks should be able to say they belong in memory, with layering. This might mean getting rid of .base entirely.

I would also like to have bug-free dependency management. That could mean a few specific things:

  1. Deterministic random numbers. Scripts should have a default random seed, not based on the timer.
  2. Pools might have to be flushed at the end of every file? or every module?
  3. Pools will probably have to allocate space prior to knowing the values stored in the pool, which means there could be some small wasted space due to duplicate values.

Looking at the Inky source code, only main.gvasm has .include statements. This might need to be required going forward. Specifically, I'm imagining everything is a "library" except the main file. The main file has global config, GBA header, and include order. Then, all libraries can access the global config, or something like that.

So maybe main cannot import, and can only include. Kind of like main is package.json, and wires up packages and configuration, along with the ability to output some code so that short single-file ROMs can be made.

Maybe .gvmain (entry point) and .gvasm (libraries) or something.

// ./inky.gvmain

.begin header
  .arm
  b main
  ...
  main:
.end

.lib '@src'
  .include './src'
.end

.lib '@mem-1'
  .include './lib/mem-1.0'
.end

.lib '@mem-2'
  .include './lib/mem-2.0'
.end

.lib '@gvsong'
  .def MAX_CHANNEL = 6
  .def MAX_SFX = 3
  .maplib '@mem' = '@mem-1'
  .include './lib/gvsong-1.0'
.end

.lib '@gvsave'
  .maplib '@mem' = '@mem-2'
  .include './lib/gvsave-1.0'
.end

// ./lib/gvsong-1.0/gvsong.gvasm

.dep '@another'
  .repo 'https://github.com/velipso/another'
  .tag 'branch, tag, or hash'
  // this gets constructed into a git submodule command:
  //   git submodule add -b <tag> <repo> lib/<repo_name>-<tag>
.end

.import './relative/import.gvasm' { asdf }
.import '@/this/library/root.gvasm' { asdf }
.import '@another/library/import.gvasm' { asfd }

This doesn't seem entirely correct... need to think through what it means when a library depends on another library, and how to wire up those versions correctly.

@velipso
Copy link
Owner Author

velipso commented Jul 23, 2023

Thinking more deeply about package management.

I don't like how Rust differentiates between libraries and binaries. I like the idea of a package consisting of exported symbols, available for import by another package, and multiple entry points. These entry points are what become individual ROM files. This allows for multiple ROMs to be built from the same package (good for tests, demos, debug builds, etc).

The package manager solves a lot of UX problems, but one core problem I think it solves is flattening the dependencies. If A depends on B and C, and both B and C depend on D, then only one copy of D is needed. It would be stupid to install two copies of D for each B and C.

Here's what I would like to do:

$ gvasm new MyGame

This creates a MyGame directory with:

MyGame
|- MyGame.toml
|- src
   |- main.gvasm

Inside MyGame.toml, it should have:

[package]
name = "MyGame"
gvasm = "3.0.0"

[header]
title = "MyGame"
initials = "My"
maker = "77"
version = 0x00
region = "E"
code = "C"

[lib]
name = "somename"
path = "src/lib.gvasm"

# default dependencies
[dependencies]
gvsong = "https://raw.githubusercontent.com/velipso/gvsong/v1.2.0/gvsong.toml"

# default defines
[def]
A = 1
B = 2

[[bin]]
name = "main"
path = "src/main.gvasm"

# example
[[bin]]
name = "debug"
path = "src/debug.gvasm"
[bin.header] # overrides default header
title = "Debug"
[bin.def] # overrides default defines
A = 3
[bin.dependencies] # overrides default dependencies
gvsong = false # remove a dependency

@velipso
Copy link
Owner Author

velipso commented Aug 3, 2023

I don't like the toml idea because:

  1. It complicates small programs
  2. It introduces a new syntax to learn
  3. It obfuscates how everything is put together (header is forced at start?)

That being said, the .lib syntax is pretty ugly too.

There is something nice and simple about gvasm v1. It's a single pass. It just goes from top to bottom. It's easy to understand.

For gvasm v2, things had to get complicated to support incremental building. That extra complication is probably required..?

For gvasm v3, I feel like I'm making it even more complicated. It's annoying. I don't like it.

Installing a library should just be extracting a zip file in a directory in your repo. The library should be checked into the repo. The install instructions should literally be: download this zip, unzip it somewhere, and .include/.import as needed.

I feel annoyed at trying to add all these "features". Every feature is a concession.

Really, the only problem that I would like to solve is flattening dependencies, if possible. And I'm not entirely sure I need to solve that.

Deno/node solves the issue by having complicated library resolution logic. I don't want that.

C solves issues by having a global namespace. I might want the same, for some things. Incremental building is done one "translation unit" (pretty much one file) at a time, into a .lib file.

I like not having a global namespace because then you have to be explicit about your dependencies, instead of implicit.

I think just supporting some sort of import mapping is good enough... like:

.def '@' = '~/lib/'
.begin
  .def '@gvmem' = '~/lib/gvmem'
  .include '~/lib/gvsong'
.end

Then when gvsong needs gvmem, it can .import '@gvmem/lib.gvasm' { memcpy32 } or something.

There might even be a simpler way to do that.

I guess the overall problem is that importing is pulling from a shared resource. So there needs to be a contract about how a package is resolved. And once you do that, you define how a hypothetical package manager would work.


I think it needs to be slightly more complicated than downloading zip files.

It should be capable of being automated.

I like how nimble relies on git/hg repos, and specifies a format for tags so they can be parsed as semver.

I think the "lock file" should be implemented as submodules.

More to think about...

@velipso
Copy link
Owner Author

velipso commented Aug 3, 2023

.dep '@gvsong' = git('>=1.2.0 <1.3.0', 'https://github.com/velipso/gvsong')
.dep '@gvmem' = hg('>=1.2.0 <1.3.0', 'https://github.com/velipso/gvmem')

.import '@gvsong/lib.gvasm' { FOO }

.include '@gvsong/lib.gvasm'

I think I should just pick a directory for all libraries, and be done with it (like node_modules, but called deps instead).

I think deps should be optionally checked into the repo for binaries, and not checked into the repo for libraries. The point is to reduce the amount of data transferred during a clone, since the folder will be ignored for libraries.

Then there should probably be a deps/lock.json which is checked in for reproducible builds.

I think that can basically be it? There will need to be some complex wiring during import to make sure everything grabs the right file, but I think that's doable.

The rest of the build can be almost entirely ignorant of the package manager. It just needs a single function that translates an import/include path into a full path, and then base everything else off of that.

@velipso
Copy link
Owner Author

velipso commented Aug 6, 2023

I think I will pull this back even further.

Let's do the bare minimum for third-party support, which is iwram/ewram logic, and anything else I can come up with.

As for package management, let's just simply not do that. Get the iwram/ewram logic working, get the watch mode bullet proof, and that's it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant