If you write a file to disk (I’m processing some UGC at runtime and writing the results to disk), fully close the file, etc (the results are written with File.WriteAllBytes, but I’ve tried various permutations including in C++) then have code later call AsyncReadManager.GetFileInfo, you will get a FileInfoResult saying that the file does not exist. Checking the same path with C# by constructing a C# FileInfo or calling System.IO.File.Exists() will correctly return that the file exists. Unity 6000.0.39f1.
Interestingly, AsyncReadManager.Read() calls to the same newly-written file DO WORK CORRECTLY. This came up in a subtle, non-dev-build-only bug when we implemented a variant of File.ReadAllBytes() that returns a NativeArray, by using a combinations of GetFileInfo() to get the file’s size, then an AyncReadManager.Read call for that many bytes.
I appreciate that AsyncReadManager is not especially documented, and is primarily designed for reading Unity assets not your own files, but it’s a nice API to have that is both Async on all platforms (including web) and reads directly into a NativeArray rather than forcing you to alloc C# garbage.
As above, development builds and editor are fine, so presumably asyncReadManager pre-builds a cache of files it knows about in release (I don’t currently have source access to check), which does seem like a valid optimization for a system primarily used for reading pre-baked assets. Not sure how critical this is, but it would be nice if the AsyncReadManager.GetFileInfo docs at least mentioned this behaviour. That way, it won’t take me 2 hours to trace back from what turned out to be an untrapped zero-length GraphicsBuffer alloc that resulted (which wasn’t trapped in the non-dev build because of a badly used assertion), causing chaos with no crash stack
Please by all means post your code, don’t transcribe it. How else are we going to point out the (possibly) obvious mistake or misconception?
Here, I’d say try with VirtualFileSystem.GetLocalFileSystemName after calling WriteAllBytes to see what this results in, or whether it affects reading the file.
Also using OpenFile before read rather than read alone (which sounds like it implicitly calls OpenFile) would also be worth a try to see if it changes the behaviour.
You could also inspect the PlayerLoopSystem. It may contain an update method synchronizing the virtual file system state to the physical file system. Depending on the existance and location of such a player loop system may indicate where you’d have to expect the system to be synchronized.
A possible failure point that I’d suspect is anywhere where you would normally receive the warning message “SendMessage cannot be used during OnValidate, etc.”. Perhaps it’s one of these cases where you won’t have any issues if you ran your code in a coroutine that waits for “end of frame” or yields null once.
PS: If you find anything missing, confusing, incorrect about the docs, you can “report a problem on this page” at the bottom of each manual page, and be sure to provide “more information” after the initial submission.
Assume I’ve filed a bug separately, that I haven’t posted code because the AsyncReadManager APIs result in long code for even minimal examples because there’s a lot of wrapping of unsafe, etc, and that I’ve already tried various permutations of await NextFrameAsync() etc.
If you re-read my post, you’ll see that Read works fine immediately after writing. GetFileInfo doesn’t.
The post is really to quickly determine if anyone at Unity or otherwise with source code can verify what I’m saying more quickly than I’ll get a response from a bug. Reporting bugs typically results in very good responses from knowledgeable unity staff, often with reference to source code, but with a 10+ day timeframe.
I know you’re trying to appear knowledgeable and promote your assets in your sig but could you please stop replying to my posts with your vague waffle? I have no idea what you’re on about with SendMesssage, OnValidate and Coroutines. They appear to be just random Unity words, or perhaps trying to demonstrate the fact that you’ve heard of timing-dependent bugs?
I am also aware of the doc bug process, which I follow after bug reports, because until then you can’t say if something is a doc bug.
It’s kinda hard to help you in any way here if you haven’t actually constructed a minimum reproduction case. Since GetFileInfo of the AsyncReadManager is of course an async method, I assume that you made sure that the task has actually finished, right? You’re dealing with unsafe code here so it’s crucial to get it “right” and not touch the result until it is finished. Otherwise you can get race conditions which could even corrupt the result. That’s why it’s important to know how exactly you used the method.
I have never used this class in Unity and currently I’m not doing anything in Unity, so currently I can’t assemble a test case for myself.
Though I’m not sure you actually isolated the case properly to be sure that the issue comes from GetFileInfo. A reproduction case shouldn’t be too long. If it does work in the trivial case, it may be related to something seemingly unrelated. Messing around with unsafe code generally comes with such problems which are hard to debug as most unsafe operations lack thorough checks and safeguards for efficiency.
The method itself is implemented in native code, so we can’t really see what it does without doing some native code analysis.
Here’s a somewhat minimal example, which you can run to see will write 10 1Mb files to temp, and AsyncReadManager will then say that only the first 2 exist. System.IO will report sizes for all 10.
Not sure why the first few lines of my file didn’t post as part of the code snippet, meh…
using System.Collections.Generic;
using System.IO;
using Unity.IO.LowLevel.Unsafe;
using UnityEngine;
public class FileSizeTest : MonoBehaviour
{
public async static Awaitable GetFileSize(string filePath)
{
FileInfoResult fileInfoResult;
ReadHandle readHandle;
unsafe
{
readHandle = AsyncReadManager.GetFileInfo(filePath, &fileInfoResult);
}
while (!(readHandle.IsValid() && readHandle.Status != ReadStatus.InProgress))
{
await Awaitable.NextFrameAsync();
}
if (readHandle.IsValid())
{
readHandle.Dispose();
}
return fileInfoResult;
}
// Start is called once before the first execution of Update after the MonoBehaviour is created
async void Start()
{
var tempFolder = Path.Combine(Application.temporaryCachePath, "TempSubDir");
System.IO.Directory.CreateDirectory(tempFolder);
List<string> filePaths = new List<string>();
for (int i = 0; i < 10; i++)
{
var filePath = tempFolder + $"/{System.Guid.NewGuid()}.bin";
filePaths.Add(filePath);
byte[] randomData = new byte[1024 * 1024];
File.WriteAllBytes(filePath, randomData);
}
foreach (string filePath in filePaths)
{
System.IO.FileInfo info = new System.IO.FileInfo(filePath);
var fileInfoResult = await GetFileSize(filePath);
Debug.Log($"AsyncReadManager FileInfoResult: State: {fileInfoResult.FileState} Size: {fileInfoResult.FileSize}. C# FileInfo {info.FullName} : {info.Length}");
}
}
The status after this while loop may be canceled, truncated, failed or complete. So check what the actual status is because currently you assume it’s complete when it might not be.
Depending on whether AsyncReadManager works with the AssetDatabase or bypasses it, the behaviour of this file write may depend on actually importing the written file with the AssetDatabase. So try follow that with ImportAsset and see if the GetFileInfo behavior changes:
I appreciate the effort to contribute, but I think there’s been a bit of misunderstanding about the kind of input I’m looking for here.
My original question was about the runtime behaviour, not assetdatabase, and I deliberately didn’t include code because the “minimal” version would still be quite large. I can confirm that my full version does check that status is always “completed” (I suspect the other values are never returned from FileInfo requests, only from reads, after all a truncated read is a common condition, but a truncated filesize response doesn’t exist in any underlying filesystem API I know of…)
The question revolves around what seems to be an unexpected behaviour in the (non-public) implementation of a API, purely out of interest. The discussion has covered a lot of easily searchable or tangential details, and I’m really seeking input from those with C+±level engine knowledge who might be familiar with the low-level implementation. To be clear, I don’t actually need help.
It’s clear that since the information isn’t public, this discussion isn’t in your wheelhouse, which is absolutely fine—no one can know everything, myself included. However, when I post questions that aim for such specific technical expertise, silence is often more constructive than filling the gap. Experts might feel less inclined to share their insights if the thread becomes cluttered with surface-level speculation. For future questions of this nature, I’d kindly ask that you refrain from jumping in unless you have implementation-specific knowledge to share. I hope that doesn’t come across too harshly—it’s just about keeping the space open for those who can directly address the query.
This was confirmed as a bug about a month ago, but there has been no further communication.
Given the low apparent usage of AsyncReadManager in the wild (which often reads like somebody said “we should expose Unity’s internal C++ FS read call to C#, because there isn’t actually a way in the current .net used by Unity to read bytes of data without allocating a managed array”), I would advise just using other APIs for file size/existence checking.
If there’s ever further movement on this issue, I assume it will be noted there.