TIC-80 icon indicating copy to clipboard operation
TIC-80 copied to clipboard

Allow integration of custom editors.

Open jahodfra opened this issue 5 years ago • 24 comments

Extending editors requires knowledge of C. It is much more error prone to write in C then in Lua (due to the necessity to manage memory).

Users cannot use their own editor without recompiling TIC. I think it is possible to have editors implemented as specific cartridges. Every time an editor would be triggered the part of tic memory would be copied to the editor. The part of memory would be copied back upon exit from the editor.

Editor cartridges could be referenced in settings so the users would get seamless switching between editors as in TIC.

E.g. for Map editor the sprite backrounds and maps would be copied into an editor cartridge and upon the exit the maps would be copied back.

I see only problem with specific functionality bound to C libraries - e.g. ability to copy / paste from clipboard or recording music. This part would need an interface in TIC.

This is just suggestion for implementation and should be break down into smaller issues before implementation.

jahodfra avatar Sep 23 '18 19:09 jahodfra

It could be implemented like a plugin system. The plugins would look like usual .lua scripts with system events access (clipboard, history changes...) You can assign these scripts in the config, maybe just a list with plugins to load and also, you can define a pixel icon in the script to show it on the main toolbar...

nesbox avatar Sep 24 '18 10:09 nesbox

How would custom editors have their own graphic resources (sprites, etc) if the only memory they had access to was all from the currently loaded user cartridge?

I wonder if you could overload the bankswitching system for this...

joshgoebel avatar Jan 29 '21 01:01 joshgoebel

Added a few external tools to the wiki https://github.com/nesbox/TIC-80/wiki/tools

RobLoach avatar Jan 29 '21 01:01 RobLoach

Don't know atm, we'll invent something :)

nesbox avatar Jan 29 '21 10:01 nesbox

Maybe TIC-80 needs a notion of RAM as in "memory for data of running program"? So far there is none and TIC-80 "magicly" uses host RAM (allowing funny things like retro games having gigs of data on heap). Conveniently there is ~12k of unmapped address space at the end of RAM layout right now 🤔

So custom editors could store their sprites in current sprites location and have user edited data in that RAM area. Custom editors will need to implement their rendering tho.

Anrock avatar Jan 29 '21 12:01 Anrock

Maybe TIC-80 needs a notion of RAM as in "memory for data of running program"? So

It already has 96kb of RAM. But variables inside the language runtime and such things aren't the problem here... it's the RAM that's the problem - the fact that the game being edited has sprites AND the editor likely ALSO has sprites. so now spr(12) becomes ambiguous.

Is it sprite 12 of the editor or sprite 12 of the user program being edited?

Custom editors will need to implement their rendering tho.

Well it'd be very nice if that could be avoided... Optimally you'd want to be able to use the full existing API for drawing or else editors instantly become crippled.

joshgoebel avatar Jan 29 '21 14:01 joshgoebel

Just adding another 8 banks and saying banks 0-7 are user cartridge and banks 8-15 are editor could work, but I'm not sure how annoying (or not) that would be in practice to have to switch banks constantly.

joshgoebel avatar Jan 29 '21 14:01 joshgoebel

It already has 96kb of RAM

Erm, no. It has 96kb of address space, ~84kb of which is memory mapped for audio\video\input "hardware". There is no region in that adress space that holds program code, it's variables, callstack, data, etc - it's all in (unbounded) memory of interpreter outside of TIC-80 "hardware".

My point is that in real hardware you will have RAM/ROM with program data mapped somewhere to adress space, so you can store however much sprites (while it fits the memory) you want and copy that data to video memory before you can draw it.

So in case of current TIC-80 implementation it would be something like holding sprites of editor and user-edited sprites somewhere in memory of interpreter and copying it back and forth between interpreter memory and SPRITES in RAM (btw, why isn't it in VRAM?). I.e. during SCR, SPRITES contain sprites of editor and you draw editor interface without drawing area of user edited sprites, then during OVR you copy user-edited sprites to SPRITES region, draw them and restore editor sprites back. This way spr stays unambiguous. Or, alternatively, bank switching like you proposed - instead of copying sprites back-forth you just switch banks of SPRITES memory.

Anrock avatar Jan 30 '21 11:01 Anrock

Erm, no. It has 96kb of address space,

We're arguing over semantics. I understand there is the "fantasy device" RAM and then there is the JS/Lua/etc runtime memory. I understand the "code" can't be found in the RAM of the fantasy device at any address. Many parts of the cartridge "ROM" are indeed loaded into fixed addresses though, just not the code.

My point is that in real hardware you will have RAM/ROM with program data mapped somewhere to adress space, so you can store however much sprites (while it fits the memory) you want and copy that data to video memory before you can draw it.

I understand this. Worth noting: You can still persist sprites anywhere in RAM and then copy them in and out of the SPRITES/TILES area as needed... or even render directly them from other areas (with custom functions). You can also treat the "code" as program data with via arrays or some type of encoding (that then feed RAM - there is at least 12kb entirely unused), etc... or use extra music/sfx area for additional tiles, etc... you sort of mention this with the idea of copying large segments of RAM in and out of the scripting runtime on the fly.

something like holding sprites of editor and user-edited sprites somewhere in memory of interpreter and copying it back and forth between interpreter memory ... Or, alternatively, bank switching like you proposed

As far as I can see you really need new "hardware" support to allow either. At this point I'm thinking perhaps a separate bit somewhere you flip to address "editor" 96kb RAM vs "cartridge" RAM/ROM. (it's worth noting that addressing cartridge RAM/ROM outside of what is persistent to the cartridge makes little sense). Expanding the address space to allow both the editor RAM and cartridge "ROM" to exist in "memory" simultaneously might also be an idea... though I feel like that pollutes the device concept somewhat - so it seems easier to believe this might be done internally but exposed to the user via banking.

and SPRITES in RAM (btw, why isn't it in VRAM?).

I think this is just how things were originally setup and if you tried to enlarge VRAM now VRAM also has a bunch of other random things in it (input state, etc), etc. Really you could argue perhaps FONT should also be in VRAM. But I don't see these things changing since it would break compatibility with like everything.

I.e. during SCR, SPRITES contain sprites of editor and you draw editor interface without drawing area of user edited sprites, then during OVR you copy user-edited sprites to SPRITES region, draw them and restore editor sprites back. This way spr stays unambiguous.

I don't think it's "ambiguous" if it allowed banked access... as long as it's well defined and documented it's not ambiguous. We already have BLIT Segment after-all.

joshgoebel avatar Feb 09 '21 13:02 joshgoebel

Pixel Vision 8 allows custom tool development. So it would be good to learn from that.

As of now, in TIC80 (and Pico8) if you edit a sprite and then hop over to the map editor without saving, the sprite retains your edits. At least when I tried PV8 a little while ago, if you use one tool and then exit it to load another tool without saving, your work is lost. There are probably good technical reasons for this, allowing tools to be completely decoupled from one another or something, but as a user experience, it's definitely an unpleasant surprise.

If people want to support custom tools, my hope would be that they would be consistent with the user expectations set by the existing TIC80 tools, where it is not necessary to save your cart before switching to another tool.

jpfed avatar Apr 27 '21 14:04 jpfed

Generally I think the TIC needs to: Capture special characters and upon pressing e.g. F4 load editor cartridge. For the sprite editor it needs to load sprites and tiles into the editor cartridge. As there is limited space the editor: a) wouldn't be able to use it's own sprites (this is quite serious limitation) b) it would have to store it's own sprites outside (e.g. in code or sound data) and replace it with the user sprites each frame. c) it can store tileset in a own bank. But that needs possibility to change a bank at least 2 per frame or it would produce blinking. d) As extra e.g. 9th bank e) Instead of the existing bank e.g 8th f) The user can specify edited bank in overlay UI specified by TIC-80 engine. So only one bank would be put inside the editor.

After finishing of the editor the TIC-80 needs to unload editor cartridge parts - e.g. code.

jahodfra avatar May 21 '21 22:05 jahodfra

I think there is no sense of having code editor - as the TIC doesn't pass keyboard events to cartridge. I see space for music, sound, custom graphic editors. (e.g. editor for trees, 3d images, animations etc..).

jahodfra avatar Jul 10 '21 18:07 jahodfra

as the TIC doesn't pass keyboard events to cartridge.

Sure it does. key and keyp. Funny I'm more interested in a code editor than the others.

joshgoebel avatar Jan 08 '22 20:01 joshgoebel

I've been trying to think about how to even start/approach this (forgetting the more mundane concerns like data access)... currently the idea is we have a single currentVM... a single cart... I feel like if people write editors they should be real cartridges (or that should be a possibility)...

So if I'm editing my Lua game using my Lua editor cartridge... every time I switch does the Lua env get destroyed and a new one created? What about the cartridge? We literally can't do that because we might have unsaved data... so it seems we'd need the ability for TIC-80 to now juggle multiple cartridges... so at a minimum we now have two carts in ram... the user cartridge and the 'editor' cartridge. We actually probably need two full VMs too - unless we want to force it on the user to store state in PMEM or something... Switching between the editor -> game -> editor should not lose your cursor position - which is likely stored in the Lua VM as just a normal variable...

So right away that's a pretty large change... making TIC-80 more of an "OS" that runs multiple cartridge "programs" all at a single time... :-) Sounds cool though.

joshgoebel avatar Jan 08 '22 21:01 joshgoebel

We actually probably need two full VMs too

Actually this is a given/requirement (unless we plan to reinit the VM every time we context switch) I think... you shouldn't have the SAME exact runtime servicing game code and "OS" level code... that's a security nightmare. IE, we have to keep track of which runtime is allowed to call api_save_to_disk and which isn't... and that shouldn't be buried inside Lua - where perhaps a malicious game could break out of it - it should be at our C level.

IE, perhaps there is a privileged Lua VM and an unprivileged one or privileged and unprivileged cartridges... perhaps you "gain" privileges by being installed/configured inside someone's TIC_80 config file... so cartridges spawned from config (as editors) have increased API access levels. I still think file system access should be very restricted - ie opening a cartridge is probably still an "OS"/console only function... but once is was open a user editor could request that it be saved (but not renamed, etc).

Related: #1749

joshgoebel avatar Jan 10 '22 17:01 joshgoebel

I don't think the path forward here is easy (I think it'll require a bit of effort and willpower). But I think I'm starting to imagine how the entire architecture would fit together after a deep dive into the Studio code. One problem is studio is trying to do way too many things... it's acting as shell, task switcher, I/O handler... there isn't really even a deep concept of whether TIC-80 is running your game or the studio... it's always the studio and your studio just runs your game "inside itself". In fact the studio is even processing shell arguments like --vsync...

So right now our "process" hierarchy is really 3 layers:

  • kernel
    • studio
      • game

So:

  • kernel is the device specific layer, SDL, Sokol, retroarch, N3DS etc...
  • game is your Lua, Wren, etc code and it's tick wrapper...
  • studio kind of does EVERYTHING else, serving not only as app but really OS also

I think another layers needs to be added so that that it looks like this:

  • kernel
    • OS
      • game
      • studio
      • gamemenu
      • custom editor 1
      • custom editor 2

So:

  • kernel is the device specific layer, SDL, Sokol, retroarch, N3DS etc...
  • studio is the studio app... with the various editors you know and love.
  • gamemenu would be a simple UI layer we provide for small devices or when BUILD_EDITORS is false.
  • game would be whatever user game cartridge is currently loaded
  • custom editor would be a custom editor, just a tic or project file with extended API access

And OS is the big change here... a layer to hold the pieces that are part of TIC-80 but exist outside of any app. For example you could record a video of the studio, or your game, or your custom editor... so that's not part of studio, it's part of the OS. Same for taking a screenshot, etc. Switching between apps is an OS concern.

There is no reason you couldn't switch out of your game, edit some sprites, and switch right back into the game - all without restarting the game. Restarts would only be necessary for code edits. The studio wouldn't orchestrate switching from studio to game and back, the OS would. So the studio then only need concern itself with it's own RAM. (not making copies, etc)

I think this would simplify a lot of things... many of the current responsibilities of studio.c would move into other files. A lot of the conditional compilation defines could be simplified/removed since we'd separate the idea of "gamemenu" and "studio".


Of course this isn't an all or nothing proposal or anything that would have to be implemented all at once... you could start modestly with:

  • kernel
    • OS
      • existing studio

IE, start with one hard-wired task, the studio... start moving some fo the simpler functionality (video, screen shots, etc) up a layer to the OS... then once you have some basic keyboard stuff working at that level you could add a second hard-wired task, the game... so that instead of studio running it - it's not managed by the OS. And that would be a beginning.

Thoughts?

joshgoebel avatar Jan 11 '22 19:01 joshgoebel

Thoughts?

Everything you say sounds reasonable, but what the TIC80 architecture looks like now is the result of the evolution of the project, of course, if I knew right away where we would end up, I designed many things differently. Well, let's move forward with baby steps to your ideal architecture :) Not sure about the current version, maybe in the next...

nesbox avatar Jan 12 '22 07:01 nesbox

but what the TIC80 architecture looks like now is the result of the evolution of the project

Sure, don't take any of this as a critique of your amazing progress so far. :-) It was really just more of a before/after not a "before sucks" or anything like that.


Can you help me out with understanding what is happening here:

  • https://github.com/nesbox/TIC-80/blob/main/src/studio/studio.c#L2056 Why are they only "restored" in modes other than RUN? I can't find where they are ever set for run at all... so does run just inherit whatever was set in the other modes? Is it expected that the cartridge will change this by poking and that's why it's not reset in RUN mode?

and here:

  • https://github.com/nesbox/TIC-80/blob/main/src/studio/studio.c#L2132 Ah, this is re/setting which types of input are accepted for which modes, yes?

joshgoebel avatar Jan 13 '22 01:01 joshgoebel

Why are they only "restored" in modes other than RUN?

The same problem we fixed earlier with the keyboard registers, the user can break the system mapping from the game, perhaps we could fix it the same way as we did with the keyboard/gamepad registers.

Ah, this is re/setting which types of input are accepted for which modes, yes?

yes, tic->input.data = -1; means enable all the inputs.

nesbox avatar Jan 13 '22 08:01 nesbox

The same problem we fixed earlier with the keyboard registers, the user can break the system mapping from the game, perhaps we could fix it the same way as we did with the keyboard/gamepad registers.

What is someone mapping exactly? Does it not matter during gameplay if they break it - or is that a feature?

joshgoebel avatar Jan 13 '22 15:01 joshgoebel

Mockup just for fun.

Screen Shot 2022-01-17 at 12 59 24 PM

How one would task switch is an open question though. I presume some might want to ADD editors, not just replace the existing ones, so you'd have a world where you might be running 1 or 2 custom editors, your game, AND the studio...

joshgoebel avatar Jan 17 '22 18:01 joshgoebel

@nesbox Out of curiosity is there a reason you've never explored using Lua for the built-in editors? It would make them a lot more accessible (for contributors)... We already use Lua for the config file... or are you just super comfortable with C yourself so it's not worth the bother? :-)

joshgoebel avatar Jan 19 '22 07:01 joshgoebel

I had such thoughts and it can be done if we extend API for editors (file IO and etc)

nesbox avatar Jan 19 '22 10:01 nesbox

I don't think the editors need any file IO to work... they only need the ability to read/write data from/to the users game cartridge... saving and loading would still be handled by privileged studio code.

What about an "is_editor" boolean somewhere and based on that I'd simply extend the sync API for studio code:

  • remove the frame limits (to editors could swap banks as many times as they need to)
  • add banks 8-15 (that map into the users cartridge)

So a sprite editor would need to:

  • page in it's own banks to render it's UI
  • page in the game cartridge banks to render their sprite date, etc.

The trick (without first completing this task) would be how do you develop that cartridge in "unprivileged" mode... though I don't think it would be terrible if you could only edit your own cartridge while developing the editor TIC itself. Once we had such an editor I imagine we'd keep a project file of it and then build it into the binary the same way we currently do the demo carts...

joshgoebel avatar Jan 19 '22 11:01 joshgoebel

I know there are mixed feelings about file I/O, but a neat "editor" approach for PICO-8 is described in a Lazy Devs video. He wanted a way to edit a big table of enemy positions/types/etc for his game.

His solution was to put his editor in a separate cart, but have both carts load the same data using #include.

Then, the editor cart registers a menu-item "EXPORT" which calls printh(...) to overwrite the #included file. That's a full editing workflow. Moreover, the editor loads sprite data with the reload function (just reading the sprite section of the game cart's ROM).

It'd be sick if something like that would work in TIC-80, but I understand there are security concerns with I/O.

EDIT: and something like printh could be made safe with a confirmation dialogue. E.g. "the cart is trying to overwrite BLAH.DAT with 198 bytes. [allow] [deny]"

ChillyCider avatar Jun 21 '23 04:06 ChillyCider