playcanvas / editor

Issue tracker for the PlayCanvas Editor
https://playcanvas.com/
156 stars 28 forks source link

Have a 'Resources' folder where users can just upload files as-is without converting to an asset #895

Open yaustar opened 2 years ago

yaustar commented 2 years ago

This is similar to Unity's streaming assets: https://docs.unity3d.com/Manual/StreamingAssets.html

There are a number of times where users want to upload a file as-is and retain the folder hierarchy because there is dependency on relative paths.

Examples include:

On publish, I would expect to have a resources folder alongside assets where these file would be and we could use relative path URLs to access them.

LeXXik commented 2 months ago

Would be a great addition to ESM scripts.

marklundin commented 2 months ago

This is a valid issue, however a separate Resources folder does not feel like the correct approach. As a user I'd expect to be able fetch/import assets regardless of where they're located. This already works for ESM Scripts, but it's understandably confusing why it doesn't apply to other assets.

The main restriction is that the registry can contain multiple assets with the same name and path. So a fetch(/to/some/image.jpg) is ambiguous and could resolve to multiple assets.

One solution is to resolve fetch/import requests to the first matching asset available, and on export, replicate the asset registry folder structure, renaming any duplicate assets with unique filename that don't collide

Maksims commented 2 months ago

There is no need for complications and complex loading, import mapping etc.

We already have binary assets that provides a direct way to load/unload assets and be able to do anything you want with it. It benefits from being an asset as it is on the registry and it simplifies the loading process and handling events when the asset is loaded. Also, it can be in bundles if needed.

So all it is, is to allow upload files that are not auto-detected what they are, but simply imported as binary. Through UX, this easily can be solved by providing the "Upload Raw" option in the assets panel + menu options.

Providing multiple droptargets for drag'n'drop - is an option too, but it has a risk of not being visually clear to users that are used to simply drag'n'drop, that might accidentally drop it to a new "raw" area. For such users a single popup for acknowledgment on first drop - will be sufficient to introduce them to new UX.

marklundin commented 2 months ago

Just to be clear we're on the same page, I'm talking about resolving file paths for arbitrary assets in a way thats analagous to the asset registry.

// Supporting this
fetch('/path/to/image.png')
import('./relative/path/to/utils.mjs')

// over this
fetch(pc.app.assets.find('image.png').getFileUrl())
import(pc.app.assets.find('module.mjs').getFileUrl())

I'm not totally clear on what you mean by the binary type? Do you mean just leveraging that for a specific folder, or allow certain files to be uploaded as raw?

Maksims commented 2 months ago

People don't use fetch with assets, if you use assets it is this:

asset.ready(() => {
    // loaded asset.resource - is your binary
});
app.assets.load(asset);

If you need binary, same story: fetch with file paths - is not needed, when asset contains all needed paths and everything, asset registry will figure our itself where to load things from.

marklundin commented 2 months ago

Right, but I think this is what @yaustar is talking about in this issue (correct me If Im wrong though). The ability to for files/assets to resolve relative paths.

Examples include:

Font files to be used in HTML or CanvasFont GLB files that they want to add their own post processing on (eg Bitmoji Animations) Images for the loading screen

Maksims commented 2 months ago

Re-read the topic starter, and basically, it touches two problems, Unity is technically unrelated because Unity does auto-bundling resources into single files, while PlayCanvas does not do that, it preserves files as they are. But two problems are:

  1. How to reference assets within CSS/HTML.
  2. Be able to upload a particular file that is supported by PlayCanvas assets pipeline, but not process it by it.

CSS use case:

@font-face {
    font-family: "SomeFont";
    src: url("path/to/some-font.otf") format("opentype")
}

If a font file is uploaded to PlayCanvas, depending on its file type, it can be converted to bitmap font, which in this case is undesirable, so "Upload Raw" mentioned before - is useful here. The second problem, is the path to reference such assets from the CSS, as the current path will be something like: /files/42/some-font.otf, where 42 - is an asset ID. And on forking of projects changes, so it is never recommended to use that.

The current approach to solving it is to hardcode the relative path to it, based on knowledge of how asset URL is constructed. Or to do post-processing of CSS, which requires extra engineering, something like:

@font-face {
    font-family: "SomeFont";
    src: url("${asset:42.getFileUrl}") format("opentype")
}

Then write a script that using regular expressions looks for such things, and replaces them with a resolved file URL, which is a stable way of doing it regardless of context (launcher/published).

HTML use case

Very similar to CSS. HTML would reference images, or CSS files like so:

<head>
    <link href="/path/to/style.css" rel="stylesheet" />
</head>
<body>
    <img src="/path/to/image.png" />
</body>

In this case, we have the same two problems:

  1. Assets are not path referencable.
  2. Images are pre-processed by the Editor's assets pipeline, which is not always desirable, in this case, Texture - is not needed.

GLB container with post-processing

Currently, PlayCanvas supports a lot of extensions of GLB specs and does a great job at that. But GLB is third-party extensible, a good example would be VRM avatar files, which are based on GLB, but adds a lot of custom extensions. It is possible to load such files with custom handlers using ContainerHandler. Currently to do so, you would need to create an asset and provide custom handler callback functions. This complicates things. In such case, it makes Editor's auto-pipeline undesirable during the upload. So "Upload Raw" - is a need here again. Also for this particular case we would need an additional API to be able to add custom GLB container handlers to existing assets.

Summary

The current assets folder structure is implemented for the benefit of simplicity to manage and avoid of file collisions, but it suffers from not preserving folder structure. Also worth mentioning: folder assets were not implemented at the time when folder structure were decided. And folder assets were implemented as a purely organisational tool in Editor.

Investigate an option of matching folder structure of assets and actual file locations. This should be 1 to 1 in Launcher and Published projects. Launcher atm already "almost" does that, but in reality it uses ?id= to reference a file, instead of a path. So there is a need for resolving paths based on actual path. Published/exported projects should re-create folder structure.

This consistency between Launcher and Published/Exported projects - will result in proper relative paths, and referencable files. And something like base folder called "assets" instead of "files" - probably will make it easier to remember also.

There are few implications of "folder preserving" feature:

  1. Cache invalidations due to re-organisation of files. When files are not changed but only moved within folders, currently they preserve cache, with full paths - they will invalidate cache. But that is common within normal work with files, so not a huge thing, and developer aware of web cache mechanics would expect that.
  2. File name collisions, ensure Editor does not allow any of that. Editor does allow to create files of the same name as other file, and rename to something that collides with other files also. And many files are created as "asset friendly" names, e.g. "New Text" - when creating .txt files. So Editor would need to change its behaviour, and provide extra UX to avoid collisions.
  3. Not all assets have files, and this should be communicated, e.g. Render or Material - they are not files, but simply asset with data/references to containers.
  4. Launcher and Published/Exported builds should be 1:1 in behaviour always.

The second thing is simple: implement option in assets panel + menu, to "Upload Raw" - without auto-detecting file type, and just keeping it as binary. It will be up to developer to interpret asset.resource in whatever data they need.