Skip to content

Commit

Permalink
Add DirAccess interface to file_access_memory
Browse files Browse the repository at this point in the history
Having memory backed res:// folder is useful for hermetic tests that need to mutate contents of the resource directory.

For example in godotengine#98909 we want the res:// folder not to contain a texture imported on previous run to always test the same code path.

It requires changes to core/os/os.h so that we can re-initialize the file/dir handlers during test setup.
  • Loading branch information
demolke committed Dec 17, 2024
1 parent 4364ed6 commit 96f8fff
Show file tree
Hide file tree
Showing 11 changed files with 350 additions and 64 deletions.
199 changes: 154 additions & 45 deletions core/io/file_access_memory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,128 +35,237 @@
#include "core/templates/rb_map.h"

static HashMap<String, Vector<uint8_t>> *files = nullptr;
const static PackedByteArray DIRECTORY = String("<DIRECTORY>").to_utf8_buffer();

void FileAccessMemory::register_file(const String &p_name, const Vector<uint8_t> &p_data) {
if (!files) {
files = memnew((HashMap<String, Vector<uint8_t>>));
}

String name;
if (ProjectSettings::get_singleton()) {
name = ProjectSettings::get_singleton()->globalize_path(p_name);
} else {
name = p_name;
void FileAccessMemory::initialize() {
if (files) {
cleanup();
}
//name = DirAccess::normalize_path(name);

(*files)[name] = p_data;
files = memnew((HashMap<String, Vector<uint8_t>>));
}

void FileAccessMemory::cleanup() {
if (!files) {
return;
}

memdelete(files);
files = nullptr;
}

Ref<FileAccess> FileAccessMemory::create() {
return memnew(FileAccessMemory);
}

bool FileAccessMemory::file_exists(const String &p_name) {
String name = fix_path(p_name);
//name = DirAccess::normalize_path(name);

return files && (files->find(name) != nullptr);
return files->has(p_name);
}

Error FileAccessMemory::open_custom(const uint8_t *p_data, uint64_t p_len) {
data = (uint8_t *)p_data;
length = p_len;
if (!files) {
initialize();
}
current_file = "__temp__";
files->erase(current_file);
open_internal(current_file, FileAccess::WRITE);
store_buffer(p_data, p_len);
pos = 0;
return OK;
}

Error FileAccessMemory::open_internal(const String &p_path, int p_mode_flags) {
ERR_FAIL_NULL_V(files, ERR_FILE_NOT_FOUND);

String name = fix_path(p_path);
//name = DirAccess::normalize_path(name);
String name = p_path.simplify_path();

HashMap<String, Vector<uint8_t>>::Iterator E = files->find(name);
ERR_FAIL_COND_V_MSG(!E, ERR_FILE_NOT_FOUND, vformat("Can't find file '%s'.", p_path));
if (p_mode_flags & WRITE) {
files->insert(name, PackedByteArray());
}

data = E->value.ptrw();
length = E->value.size();
HashMap<String, Vector<uint8_t>>::Iterator E = files->find(name);
ERR_FAIL_COND_V_MSG(!E, ERR_FILE_NOT_FOUND, vformat("Can't find file '%s'.", name));
current_file = name;
pos = 0;

return OK;
}

bool FileAccessMemory::is_open() const {
return data != nullptr;
return !current_file.is_empty();
}

void FileAccessMemory::seek(uint64_t p_position) {
ERR_FAIL_NULL(data);
pos = p_position;
}

void FileAccessMemory::seek_end(int64_t p_position) {
ERR_FAIL_NULL(data);
pos = length + p_position;
pos = get_length() + p_position;
}

uint64_t FileAccessMemory::get_position() const {
ERR_FAIL_NULL_V(data, 0);
ERR_FAIL_COND_V(current_file.is_empty(), 0);
return pos;
}

uint64_t FileAccessMemory::get_length() const {
ERR_FAIL_NULL_V(data, 0);
return length;
ERR_FAIL_COND_V(current_file.is_empty(), 0);
return files->get(current_file).size();
}

bool FileAccessMemory::eof_reached() const {
return pos >= length;
return pos >= get_length();
}

uint64_t FileAccessMemory::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
ERR_FAIL_COND_V(current_file.is_empty(), -1);
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
ERR_FAIL_NULL_V(data, -1);

uint64_t left = length - pos;
uint64_t left = get_length() - pos;
uint64_t read = MIN(p_length, left);

if (read < p_length) {
WARN_PRINT("Reading less data than requested");
}

memcpy(p_dst, &data[pos], read);
memcpy(p_dst, files->get(current_file).ptrw() + pos, read);
pos += read;

return read;
}

Error FileAccessMemory::get_error() const {
return pos >= length ? ERR_FILE_EOF : OK;
return pos >= get_length() ? ERR_FILE_EOF : OK;
}

void FileAccessMemory::flush() {
ERR_FAIL_NULL(data);
ERR_FAIL_COND_MSG(current_file.is_empty(), "No opened file");
}

bool FileAccessMemory::store_buffer(const uint8_t *p_src, uint64_t p_length) {
ERR_FAIL_COND_V(!p_src && p_length > 0, false);

uint64_t left = length - pos;
uint64_t write = MIN(p_length, left);
PackedByteArray &p = files->get(current_file);
uint64_t length = get_length();
if (pos + p_length > length) {
p.resize(pos + p_length);
}
uint8_t *dst = p.ptrw() + pos;
memcpy(dst, p_src, p_length * sizeof(uint8_t));

memcpy(&data[pos], p_src, write);
pos += write;
pos += p_length;
return true;
}

ERR_FAIL_COND_V_MSG(write < p_length, false, "Writing less data than requested.");
Error DirAccessMemory::list_dir_begin() {
list_items.clear();
for (auto f = files->begin(); f != files->end(); ++f) {
if (f->key.begins_with(current_dir)) {
String rest = f->key.substr(current_dir.length());
if (rest.begins_with("/")) {
rest = rest.substr(1);
}
if (!rest.is_empty()) {
list_items.push_back(rest);
}
}
}
return Error();
}

return true;
String DirAccessMemory::get_next() {
if (list_items.size()) {
current_item = list_items.front()->get();
list_items.pop_front();
return current_item;
}
return String();
}

bool DirAccessMemory::current_is_dir() const {
String name = current_dir.path_join(current_item);
return files->has(name) && files->get(name) == DIRECTORY;
}

bool DirAccessMemory::current_is_hidden() const {
return false;
}

void DirAccessMemory::list_dir_end() {
current_item = "";
list_items.clear();
}

int DirAccessMemory::get_drive_count() {
return 0;
}

String DirAccessMemory::get_drive(int p_drive) {
return "";
}

Error DirAccessMemory::change_dir(String p_dir) {
String name = p_dir;
if (name.is_relative_path()) {
name = current_dir.path_join(name);
}
name = name.simplify_path();
if (name == "res://") {
files->insert(name, DIRECTORY);
}

if (dir_exists(name)) {
current_dir = name;
return OK;
}

return ERR_INVALID_PARAMETER;
}

String DirAccessMemory::get_current_dir(bool p_include_drive) const {
return current_dir;
}

String DirAccessMemory::_localize(const String &p_name) const {
String result = p_name;
if (result.is_relative_path()) {
result = current_dir.path_join(result);
}
result = result.simplify_path();
return result;
}

bool DirAccessMemory::file_exists(String p_file) {
String name = _localize(p_file);
return files->has(name) && files->get(name) != DIRECTORY;
}

bool DirAccessMemory::dir_exists(String p_dir) {
String name = _localize(p_dir);
return files->has(name) && files->get(name) == DIRECTORY;
}

Error DirAccessMemory::make_dir(String p_dir) {
String name = _localize(p_dir);
if (!dir_exists(name.get_base_dir())) {
return ERR_CANT_CREATE;
}
files->insert(name, DIRECTORY);
return OK;
}

Error DirAccessMemory::rename(String p_from, String p_to) {
return ERR_UNAVAILABLE;
}

Error DirAccessMemory::remove(String p_name) {
String name = _localize(p_name);
files->erase(name);
return OK;
}

uint64_t DirAccessMemory::get_space_left() {
return 0;
}

String DirAccessMemory::get_filesystem_type() const {
return "MEMORY";
}
45 changes: 42 additions & 3 deletions core/io/file_access_memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,17 @@
#ifndef FILE_ACCESS_MEMORY_H
#define FILE_ACCESS_MEMORY_H

#include "core/io/dir_access.h"
#include "core/io/file_access.h"

class FileAccessMemory : public FileAccess {
uint8_t *data = nullptr;
uint64_t length = 0;
String current_file;
mutable uint64_t pos = 0;

static Ref<FileAccess> create();

public:
static void register_file(const String &p_name, const Vector<uint8_t> &p_data);
static void initialize();
static void cleanup();

virtual Error open_custom(const uint8_t *p_data, uint64_t p_len); ///< open a file
Expand Down Expand Up @@ -79,4 +79,43 @@ class FileAccessMemory : public FileAccess {
FileAccessMemory() {}
};

class DirAccessMemory : public DirAccess {
String current_dir;
List<String> list_items;
String current_item;

String _localize(const String &p_name) const;

public:
virtual Error list_dir_begin() override;
virtual String get_next() override;
virtual bool current_is_dir() const override;
virtual bool current_is_hidden() const override;
virtual void list_dir_end() override;

virtual int get_drive_count() override;
virtual String get_drive(int p_drive) override;

virtual Error change_dir(String p_dir) override;
virtual String get_current_dir(bool p_include_drive = true) const override;

virtual bool file_exists(String p_file) override;
virtual bool dir_exists(String p_dir) override;

virtual Error make_dir(String p_dir) override;

virtual Error rename(String p_from, String p_to) override;
virtual Error remove(String p_name) override;

uint64_t get_space_left() override;

virtual bool is_link(String p_file) override { return false; }
virtual String read_link(String p_file) override { return p_file; }
virtual Error create_link(String p_source, String p_target) override { return FAILED; }

virtual String get_filesystem_type() const override;

DirAccessMemory() {}
};

#endif // FILE_ACCESS_MEMORY_H
3 changes: 3 additions & 0 deletions core/os/os.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ class OS {

protected:
friend class Main;
// Needed to reset default fs access on test start.
friend struct GodotTestCaseListener;
// Needed by tests to setup command-line args.
friend int test_main(int argc, char *argv[]);

Expand All @@ -118,6 +120,7 @@ class OS {

virtual void initialize() = 0;
virtual void initialize_joypads() = 0;
virtual void initialize_default_fs_access() {}

void set_display_driver_id(int p_display_driver_id) { _display_driver_id = p_display_driver_id; }

Expand Down
14 changes: 9 additions & 5 deletions drivers/unix/os_unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,18 +154,22 @@ int OS_Unix::unix_initialize_audio(int p_audio_driver) {
return 0;
}

void OS_Unix::initialize_core() {
#ifdef THREADS_ENABLED
init_thread_posix();
#endif

void OS_Unix::initialize_default_fs_access() {
FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES);
FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_USERDATA);
FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_FILESYSTEM);
FileAccess::make_default<FileAccessUnixPipe>(FileAccess::ACCESS_PIPE);
DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_RESOURCES);
DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_USERDATA);
DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_FILESYSTEM);
}

void OS_Unix::initialize_core() {
#ifdef THREADS_ENABLED
init_thread_posix();
#endif

initialize_default_fs_access();

#ifndef UNIX_SOCKET_UNAVAILABLE
NetSocketUnix::make_default();
Expand Down
Loading

0 comments on commit 96f8fff

Please sign in to comment.