Creating local stream

Given a url to a local data file… I want to open the stream if it is there, otherwise create it.
Is there a cleaner way than trying to Download the url and then catch exception to instead URLSaveStream? This stream needs to be read/write so I can dynamically save and load records as needed.

You can use URIExists to check if given URL exists first. It handles all CGE protocols, including castle-data:/.

But, big warning, if you think about writing files under the castle-data:/ protocol: you should not. See Data directory and castle-data:/ URL protocol | Manual | Castle Game Engine : the data should be treated read-only, and while sometimes it is in practice writeable (e.g. if you just distribute application as a zip for Windows and Linux and users unpack and run simply), but sometimes it is really strictly read-only:

  • On Android, data is packed to special “Android assets” concepts, which is per-application strictly read-only.
  • On iOS it is also most likely read-only.
  • On Windows, Linux, macOS it may be read-only if administrator installs the application and user runs it. E.g. if your game will be packaged into Debian package for Linux, or if you package your game using InnoSetup and users will do “system-wide” install (to c:/program files.../).

So I would heavily advise to avoid writing stuff in castle-data:/ , to keep your options open.

For saving stuff, use ApplicationConfig, like Url := ApplicationConfig('my_stuff.txt'); Stream := UrlSaveStream(Url, ...) .

The ApplicationConfig guarantees to return a writeable location on all systems. In practice, right now, it is always just file://.. protocol, though it may be a different protocol (like castle-config:/...) in the future.

Hmm. I want a giant indexed stream to read/write from. So I can have detailed record about millions of ‘people’ (or things) and their connections. It isn’t config data but game data that is too much for memory all at once. ApplicationConfig is still the right call? I don’t care about android/ios. Those people don’t need more distraction from the real world and their screens are too small to build cities.

The general rule is: If this is something that should be written, it is for ApplicationConfig.

Perhaps the names “game data” and “game config” are not clear enough. Their technical meaning is:

  • “game data” (described on Data directory and castle-data:/ URL protocol | Manual | Castle Game Engine , with URLs like castle-data:/xxx) are read-only resources, that contain exactly what you have setup in “data” subdirectory of your project.

    They may be writeable in certain scenarios, but you should not depend on it, ever for desktop applications.

  • “game config” (with URLs obtained from ApplicationConfig(...)) are read-write resources, that you can manipulate at run-time freely. The ApplicationConfig('') starts as empty directory but you can create as many files and as large as necessary. It is “persistent” across program runs. Our UserConfig ( Persistent data (user preferences, savegames) | Manual | Castle Game Engine ) is also saved there. The subdirectories inside ApplicationConfig('') are automatically created, you just save files like UriSaveStream.

    Note that UriSaveStream doesn’t guarantee a read-write stream. Underneath, Download creates a file stream like TFileStream.Create(FileName, fmOpenRead) and UriSaveStream creates a file stream like TFileStream.Create(FileName, fmCreate).

    If you want to go around them, and assume that URLs map to files (as they do on desktops), you can do

FileName := UriToFilenameSafe(ApplicationConfig(...)); 
if FileName = '' then
  raise Exception.Create('URL inside game config did not map to simple file');
MyStream := TFileStream.Create(FilenName, { any flags you want});

Note that ApplicationConfig('') is always empty at start. If you want to initialize something there based on your data, do it explicitly – e.g.

if not UriExists(ApplicationConfig('my_stuff')) then 
begin
  OuputStream := UrlSaveStream(ApplicationConfig('my_stuff'));
  try
    InputStream := Download('castle-data:/my_stuff_initial');
    try
      ReadGrowingStream(InputStream, OuputStream, true);
   ...

Both things are just URLs for CGE, they are kept on disk on all devices. You can store as huge amounts of data as you want – just as you would in any other case when dealing with files :slight_smile: I mean, surely you should be careful to not exhaust the user’s hard disk space :), but go wild otherwise, they are files, they occupy disk space, not e.g. RAM, until you open them.

1 Like

I enhanced our data documentation based on this thread – adding there a section " 4. Data should be considered read-only" with subsection explaining the solution: " 4.1. Use ApplicationConfig instead to write stuff".

2 Likes