-
-
Notifications
You must be signed in to change notification settings - Fork 140
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
Dynamic asset / build system #358
Comments
I think this is a huge need and I agree with a lot of the motivation. In my head I sorta split this up into 2+ problems:
Thoughts on build.lua / lovr.build
Maybe the virtual filesystem can help? It can be used to shadow files. Imagine if something like this ran: if not lf.isFile('shader.spv') or lf.getLastModified('shader.spv') < lf.getLastModified('shader.glsl') then
local glsl = lf.read('shader.glsl')
local spv = require('compiler').compile(glsl)
lf.write('shader.spv', spv)
end If that could somehow run, I would always be able to do The other piece of this I was thinking about was using the new plugin system as a way of including optional functionality. There could be a plugin called
There could maybe be a piece of lovr.dev that handles the One thing I like about this is that all of the development stuff can be contained in the plugin. LÖVR doesn't know anything about how this stuff works, aside from doing like Still thinking, will post more later |
An attempted proposal on something I've been thinking about awhile…
Motivation
Imagine the idea of resources which are generated from other resources. Examples might be:
If one were building a system to support these kinds of resources, a couple things to consider might be:
git status
es or you'll have to set up a complicated.gitignore
.A naive way to get these goals might be to have a "build" script. You could have a script that combines your project directory with an assets directory to create a "built" project directory. But this would be too bad, because this disrupts the normal way you use Lovr. Lovr doesn't normally have a "build step", and that is nice. So a couple more considerations:
A solution: build.lua, Generators
My proposal has three parts:
lovr.build(t, c)
This is a function like lovr.conf. It passes in two arguments,
t
, which works like the t argument to lovr.conf, andc
, which describes some things about platform and configuration (like, "you're building for oculus quest" "this is a debug copy").t
has two members to be assigned to bylovr.build
,shun
andgenerators
.shun
is a list of filenames or fileglobs which should be screened out when doing a build; by default, it contains "build.lua".t.generators
is the main purpose oflovr.build
, and it is a list of…Generators
These could also maybe be called "converters". I dunno. A generator is a table of the form
build.lua
Similar to conf.lua, build.lua is a file expected to contain just the definition for
function lovr.build
.What these parts do would depend on which mode you're in:
In regular / just-in-time mode, Lovr runs like Lovr normally runs, but filesystem operations are overloaded. Any file which is either opened or checked for presence is checked against (1) the shun list; if the file is on the shun list, lovr pretends the file doesn't exist and returns an error if you try to open it (2) the generator list; if the file is on the generator list
filesystem.isFile
advertises its existence, and when you try toread()
it, it calls the appropriategenerate
function with the filename and returns thegenerate
result as the file contents. In addition, we change the execution order: Instead of (conf.lua,lovr.conf
, load modules, main.lua,lovr.load
), it runs (conf.lua,lovr.conf
, load modules, build.lua, main.lua,lovr.build
,lovr.load
).In "build" mode,
lovr --build destdir
or something, It runs conf.lua then build.lua (but not main.lua). The modules requested in conf.lua are run but the window is not created. Oncelovr.build
has run and it has the generator and shun lists, it copies the project directory into destdir, and on the way it omits any files on the shunlist and generates all the files on the generator list.lovr.load
, etc are not run.The result is a directory with the generators "pre-run". No shunned files are present, all dynamically generated files are already generated, and generators are not registered at runtime (because they're registered in build.lua-- which we shunned).
But wait Andi, how does
--build
mode know what the list of files in the generator list is if the generator list can contain fileglobsUh... okay. So I have a couple ideas about that. One is that lovr.build
t
can contain an explicit list of filenames to generate-- I'm not crazy about this because it could lead to broken builds if your explicit list is missing things. My other two ideas are below.Stretch goals
There are a couple of more-advanced versions of this idea we could consider.
Sample generators / Generator generators
Most projects will probably not have very special generators and will do a small number of obvious things (like: downsample images, build SPIRV). We could offer a small library that generates the most obvious generators.
Cacheing / Dependencies
So the idea is every time you read from a file, its contents are generated on the fly. Every time? That's a little wasteful.
The generator
generate
function returns a value to be added to the shunlist. We could treat this as a "dependency". When running in normal lovr run mode, it could save a list of all shun-requested files and a lookaside directory with all generated files. Then when it comes time to run the generator, it could check the lookaside and only generate the files if the dependency's datestamp is newer than the last-generated version.We could also use a previously recorded list of generated files to generate the list of "all files" for build mode. But there are a lot of ways that could go wrong so I don't like it.
Functional interface
The interface for the generators feels pretty complicated and it honestly doesn't seem very "lovr-like". One possibility would be that instead of building up a table, it could call a series of functions-- like,
buildSystem.shun("notes/*.txt")
orbuildSystem.generateForEach({"shaders/*.frag", "shaders/*.vert"}, buildSystem.buildSpv)
.It would be probably easier to make a system like this expressive than the "complicated table of tables of tables" approach so this interface could along the way gather enough information about the files being generated to drive the "make a list of all files" in the
--build
mode. For example instead of the direction of file autogeneration being "describe a fileglob of files to be built on demand" it could be "inlovr.build
scan the directory tree for dynamic-generate sources and register an explicit list of all dynamic-generate files". This would be potentially slower than the purely lazy approach but surely not much since we wouldn't have to run the generators themselves immediately.Could this be a third-party library?
Historically bjorn has resisted the addition of complicated features to core lovr; this is a complicated feature with a complicated interface. I think lovr is eventually going to need something like this if for no other reason than that when we move to Vulkan you will have to make the .spvs somehow.
However, this entire Thing could in principle be done as a third party library in pure lua, like lovr-lodr.
There are two things that might require this to be an extension to lovr itself rather than a third party library:
All lovr functions that load files must lie.
lovr.filesystem
, as well as any function outsidelovr.filesystem
that accepts a path as an argument, has to know about the shun and generate lists.Ideally the magic dynamic file loading behavior would work across all threads. (I do the bulk of my file loading off thread.)
I can solve problem (1) by "wrapping" the Lovr API functions; I think ?? my lodr-like tool could just alter the lovr.filesystem table and replace all functions that hit disk with dummy versions that do the generator magic and then call the original functions. However this would be a somewhat laborious process, it wouldn't be forward-compatible (ie every time you released a new lovr version it would break because the function API would change) and it wouldn't propagate to threads because "lodr-type" tools can only impact the main thread scope.
I can solve problems (1) and (2) by introducing my own filesystem functions, like buildSystem.isFile() and buildSystem.loadFile(), and expecting anyone using my third party build system library to call those wrapper functions instead of
lovr.filesystem
directly. However this will stop working if you use ANY third party lovr libraries (because the third party lovr libraries won't be using my wrappers), and it will be annoying to make work with functions likelovr.graphics.newTexture
.It may be we could find a compromise, like lovr exposes some sort of interface for a dynamic file system to put in hooks and then the build/dynamic file system is a third party library using those hooks.
The text was updated successfully, but these errors were encountered: