Sistema de IO do Ant Engine: Leitura, Escrita e Processamento de Arquivos

O gerenciamento eficiente de operações de Entrada/Saída (IO) é crucial para o desempenho de jogos. O Ant Engine oferece um sistema de IO robusto que inclui manipulação de sistema de arquivos, arquivos em memória e gerenciamento de caminhos.

Arquitetura do Sistema de IO

O sistema de IO do Ant Engine é projetado em camadas, com os seguintes módulos principais:

  • VFS (Virtual File System): Abstração para acesso a arquivos, garantindo compatibilidade entre plataformas.
  • FastIO: Biblioteca de IO de alta performance para operações rápidas de leitura e escrita.
  • Filesystem: Módulo para manipulação de caminhos e gerenciamento de arquivos.

Comparativo de Módulos

Módulo Funcionalidade Principal Características de Desempenho Casos de Uso
VFS Abstração de sistema de arquivos Moderado Acesso a arquivos multiplataforma
FastIO Leitura/Escrita de arquivos de alta performance Alto Carregamento de arquivos grandes, leitura de recursos
Filesystem Gerenciamento de caminhos e arquivos Moderado Operações de sistema de arquivos, manipulação de caminhos

VFS: Abstração de Sistema de Arquivos Virtual

O VFS fornece uma interface unificada para interagir com o sistema de arquivos.

Exemplo de Leitura com VFS

-- Função de leitura principal do VFS
function vfs.read(absolute_path)
   -- Garante que o caminho é absoluto (inicia com '/')
   if not absolute_path:startsWith("/") then
       error("Caminho inválido: deve ser absoluto.")
   end
   
   -- Remove o '/' inicial para uso interno
   local internal_path = absolute_path:sub(2)
   
   -- Utiliza FastIO para ler todo o conteúdo do arquivo
   -- O segundo argumento pode ser usado para metadados ou identificação
   local file_data = fastio.read_all_virtual(internal_path, absolute_path)
   
   return file_data, internal_path
end

-- Função para carregar um arquivo Lua
function load_lua_file(path)
   local file_content, symbol = vfs.read(path)
   
   if not file_content then
       return nil, string.format("Erro: Arquivo não encontrado ou inacessível: %s", path)
   end
   
   -- Utiliza FastIO para carregar o conteúdo como um módulo Lua
   return fastio.load_lua_from_memory(file_content, symbol)
end

Detecção de Ambiente de Execução

O VFS adapta seu comportamento com base no ambiente de execução.

// Detecção de ambiente de execução para Ant Engine
#if defined(__APPLE__) || defined(__ANDROID__)
static bool IS_ANT_RUNTIME = true;
#elif defined(_WIN32)
// Função auxiliar para verificar o ambiente de execução no Windows
bool check_windows_runtime_env() {
   // Lógica específica para Windows
   bool runtime_status = false; 
   // ... implemantação ...
   return runtime_status;
}
static bool IS_ANT_RUNTIME = check_windows_runtime_env();
#else
static bool IS_ANT_RUNTIME = false; // Ambiente padrão ou desconhecido
#endif

FastIO: Biblioteca de IO de Alta Performance

FastIO otimiza operações de leitura e escrita de arquivos.

Função de Leitura para Memória

// Estrutura para representar um arquivo em memória
struct MemoryFile {
   char* data; // Ponteiro para os dados do arquivo
   size_t size; // Tamanho dos dados em bytes
};

// Aloca e lê um arquivo inteiro para a estrutura MemoryFile
MemoryFile* FastIO::read_file_to_memory(const char* filepath) {
   FILE* file_handle = FileUtil::open_file(filepath, "rb"); // Abre em modo binário de leitura
   if (!file_handle) {
       return nullptr; // Falha ao abrir o arquivo
   }

   size_t file_size = FileUtil::get_file_size(file_handle);
   if (file_size == 0) {
       FileUtil::close_file(file_handle);
       return MemoryFile::create_empty(); // Retorna um arquivo vazio se o tamanho for 0
   }

   MemoryFile* mem_file = MemoryFile::allocate(file_size);
   if (!mem_file) {
       FileUtil::close_file(file_handle);
       // Lança erro ou retorna nullptr em caso de falta de memória
       throw std::bad_alloc(); 
   }

   size_t bytes_read = FileUtil::read_from_file(file_handle, mem_file->data, file_size);
   
   FileUtil::close_file(file_handle);

   if (bytes_read != file_size) {
       mem_file->destroy(); // Libera a memória alocada
       // Lança erro ou retorna nullptr em caso de erro de leitura
       throw std::runtime_error("Erro de leitura de arquivo desconhecido.");
   }

   return mem_file;
}

Modos de Leitura Suportados

  1. read_file_to_memory: Retorna um ponteiro para um arquivo alocado na memória.
  2. read_file_to_string: Retorna o conteúdo do arquivo como uma string.
  3. read_file_to_stream_wrapper: Retorna um objeto "wrapper" que permite o processamento em modo de stream.

Módulo Filesystem: Gerenciamento de Caminhos e Arquivos

Este módulo oferece utilitários para manipulação de caminhos e operações comuns de arquivos.

Manipulação de Objetos de Caminho

-- Definindo a metatabela para o tipo 'Path'
local PathMeta = {}
PathMeta.__index = PathMeta

function PathMeta:__tostring()
   return self.value -- Retorna a representação em string do caminho
end

-- Sobrecarga do operador '/' para concatenação de caminhos
function PathMeta:__div__(other)
   local other_str
   if type(other) == "string" then
       other_str = other
   elseif type(other) == "table" and other.value then
       other_str = other.value
   else
       error("Operador '/' suporta apenas string ou outro objeto Path.")
   end
   
   -- Concatena os caminhos, garantindo o separador correto
   return Path(self.value .. "/" .. other_str:gsub("^/", ""))
end

-- Exemplo de uso para criar um caminho de configuração
local base_config_path = Path("/config")
local settings_file_path = base_config_path / "game" / "settings.json"
print(tostring(settings_file_path)) -- Saída: /config/game/settings.json

Operações Comuns de Arquivo

-- Verifica se um caminho existe no sistema de arquivos virtual
function fs.exists(path)
   -- vfs.get_type retorna o tipo do item (arquivo, diretório) ou nil se não existir
   return vfs.get_type(path) ~= nil
end

-- Verifica se um caminho aponta para um diretório
function fs.is_directory(path)
   local item_type = vfs.get_type(path)
   -- Tipos 'd' (diretório) ou 'r' (diretório raiz) indicam um diretório
   return item_type == "d" or item_type == "r"
end

-- Iterador para percorrer o conteúdo de um diretório
function fs.iterate_directory(path)
   -- Garante que o caminho termine com '/' para listar o conteúdo
   local dir_path = path:gsub("/?$", "/") 
   local entries = vfs.list_directory(dir_path)
   local current_index = 0
   
   return functon()
       current_index = current_index + 1
       local entry_name = entries[current_index]
       if not entry_name then return nil end -- Fim da iteração
       
       local full_entry_path = dir_path .. entry_name
       local entry_type = vfs.get_type(full_entry_path)
       
       return full_entry_path, entry_type -- Retorna caminho completo e tipo
   end
end

Exemplos Práticos

Carregamento de Arquivos de Configuração

local io_utils = require("ant.io")
local filesystem = require("filesystem")

-- Função para carregar e decodificar um arquivo de configuração JSON
function load_game_settings(config_path)
   if not filesystem.exists(config_path) then
       return nil, "Arquivo de configuração não encontrado."
   end
   
   -- Lê o conteúdo completo do arquivo para uma string
   local config_content = io_utils.read_all_as_string(config_path)
   if not config_content then
       return nil, "Falha ao ler o arquivo de configuração."
   end
   
   -- Assume que existe um módulo JSON para decodificação
   local json_parser = require "ant.json" 
   local settings, decode_error = json_parser.decode(config_content)
   
   if not settings then
       return nil, "Erro ao decodificar JSON: " .. tostring(decode_error)
   end
   
   return settings
end

-- Exemplo de uso
local game_settings = load_game_settings("/config/game/settings.json")
if game_settings then
   print("Título do Jogo:", game_settings.title)
   print("Resolução:", game_settings.resolution)
end

Processamento em Lote de Recursos

-- Função fictícia para processar um arquivo de textura
local function process_texture_file(texture_path)
   print("Processando textura:", texture_path)
   -- Lógica de processamento de textura aqui...
   return true -- Indica sucesso
end

-- Processa todos os arquivos .png em um diretório de recursos
function batch_process_assets(assets_directory)
   local processed_count = 0
   local errors_list = {}
   
   for entry_path, entry_type in filesystem.iterate_directory(assets_directory) do
       -- Verifica se é um arquivo regular e tem a extensão .png
       if entry_type == "f" and entry_path:has_extension(".png") then
           local success, error_message = process_texture_file(entry_path)
           if not success then
               table.insert(errors_list, {file = entry_path, error = error_message})
           else
               processed_count = processed_count + 1
           end
       end
   end
   
   return processed_count, errors_list
end

Processamento de Arquivos Grandes em Blocos

-- Função fictícia para processar um bloco de dados
local function handle_data_chunk(chunk_data)
   -- Lógica de processamento do bloco de dados...
   print("Processando bloco de dados de tamanho:", #chunk_data)
end

-- Processa um arquivo grande em blocos gerenciáveis
function process_large_data_file(file_path)
   -- Obtém um wrapper para leitura em stream
   local stream_wrapper = io_utils.read_stream_wrapper(file_path)
   
   -- stream_wrapper() retorna (ponteiro_dados, tamanho_total, objeto_fechamento)
   local data_pointer, total_size, closer = stream_wrapper()
   
   if not data_pointer then
       print("Erro ao abrir ou ler o arquivo:", file_path)
       return
   end

   local chunk_size = 1024 * 1024  -- Define o tamanho do bloco (ex: 1MB)
   local bytes_processed = 0
   
   while bytes_processed < total_size do
       -- Calcula o fim do bloco atual, sem exceder o tamanho total
       local end_of_chunk = math.min(bytes_processed + chunk_size, total_size)
       
       -- Extrai o bloco de dados usando string.sub
       local current_chunk = string.sub(data_pointer, bytes_processed + 1, end_of_chunk)
       
       handle_data_chunk(current_chunk)
       
       bytes_processed = bytes_processed + #current_chunk -- Atualiza o contador de bytes processados
   end
   
   -- Garante que os recursos do stream sejam liberados
   closer:release() 
end

Otimização de Performance em IO

1. Operações de Arquivo em Lote

Reduz a sobrecarga chamando operações de IO em grupos.

-- Carrega múltiplos arquivos de forma eficiente
function batch_load_files(file_paths_list)
   local loaded_contents = {}
   local fast_io_module = require "fastio"
   
   for i, path in ipairs(file_paths_list) do
       -- Utiliza a leitura otimizada para memória
       local memory_representation = fast_io_module.read_file_to_memory(path)
       if memory_representation then
           -- Converte para string se necessário e armazena
           loaded_contents[i] = fast_io_module.memory_to_string(memory_representation)
           -- Libera a memória alocada para o arquivo
           memory_representation:destroy() 
       else
           loaded_contents[i] = nil -- Indica falha na leitura
       end
   end
   
   return loaded_contents
end

2. Reutilização de Arquivos em Memória

Implementa um cache simples para evitar leituras repetidas do disco.

-- Cache para armazenar arquivos lidos em memória
local memory_cache = {}

-- Obtém um arquivo do cache ou carrega do disco se não existir
function get_from_cache_or_load(cache_key)
   if memory_cache[cache_key] then
       return memory_cache[cache_key] -- Retorna do cache
   end
   
   -- Carrega o arquivo usando IO (assumindo que io.read_all_to_memory retorna um objeto gerenciável)
   local file_data = io.read_all_to_memory(cache_key) 
   if file_data then
       memory_cache[cache_key] = file_data -- Armazena no cache
   end
   return file_data
end

-- Limpa o cache e libera a memória alocada
function clear_memory_cache()
   for _, cached_file in pairs(memory_cache) do
       if cached_file and cached_file.release then -- Verifica se o objeto tem método de liberação
           cached_file:release() 
       end
   end
   memory_cache = {} -- Reseta a tabela do cache
end

Melhores Práticas em Tratamento de Erros

Operações de Arquivo Seguras

Encapsula operações de arquivo para capturar e tratar exceções.

-- Função genérica para executar operações de arquivo de forma segura
function execute_safe_io(operation_func, ...)
   local success, result_or_error = pcall(operation_func, ...)
   
   if not success then
       -- Registra o erro de forma apropriada (ex: log_error)
       print("Erro de IO: " .. tostring(result_or_error)) 
       return nil, result_or_error -- Retorna nil e a mensagem de erro
   end
   
   return result_or_error -- Retorna o resultado bem-sucedido da operação
end

-- Exemplo de uso para ler um arquivo
local file_content, error_msg = execute_safe_io(io.read_all_as_string, "/data/important.dat")

if not file_content then
   -- Lógica para tratar a falha na leitura do arquivo
   print("Falha ao carregar dados importantes:", error_msg)
else
   -- Processa o conteúdo do arquivo
   print("Conteúdo carregado com sucesso.")
end

O sistema de IO do Ant Engine oferece uma solução unificada e de alta performance para gerenciamento de arquivos. Através do VFS, FastIO e um conjunto abrangente de utilitários de sistema de arquivos, os desenvolvedores podem otimizar o acesso a recursos, garantindo escalabilidade e eficiência em seus projetos.

Tags: ant engine io file system vfs fastio

Publicado em 7-19 19:21