mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-23 03:04:29 +08:00
- 将Source Generators概述文档从overview重命名为index - 更新所有相关文档中的链接指向新的index文件 - 移除Godot文档中的许可证和版本信息冗余内容 - 统一Godot版本要求从4.5.1+到4.6 - 删除各模块文档末尾的相关链接部分 - 优化文档结构提升可读性
275 lines
6.2 KiB
Markdown
275 lines
6.2 KiB
Markdown
# 存储模块 (Storage Module)
|
||
|
||
## 概述
|
||
|
||
存储模块是 GFramework.Godot 的核心存储实现,专门为 Godot 引擎设计的文件存储系统。该模块支持 Godot 的虚拟路径系统(如
|
||
`res://` 和 `user://`),并提供了按键级别的细粒度锁机制来保证线程安全。
|
||
|
||
## 核心类
|
||
|
||
### GodotFileStorage
|
||
|
||
Godot 特化的文件存储实现,实现了 `IStorage` 接口。
|
||
|
||
**主要特性:**
|
||
|
||
- ✅ Godot 虚拟路径支持(`res://`, `user://`)
|
||
- ✅ 线程安全(按键级别的细粒度锁)
|
||
- ✅ 同步/异步读写操作
|
||
- ✅ 自动创建目录结构
|
||
- ❌ 删除操作(Delete 方法未实现)
|
||
|
||
## 功能特性
|
||
|
||
### 路径处理
|
||
|
||
该存储系统支持三种路径类型:
|
||
|
||
#### 1. Godot 资源路径 (`res://`)
|
||
|
||
- **用途**:存储游戏资源文件
|
||
- **特点**:只读,包含在游戏构建中
|
||
- **示例**:`res://config/game_settings.json`
|
||
|
||
#### 2. Godot 用户数据路径 (`user://`)
|
||
|
||
- **用途**:存储用户数据、存档、配置等
|
||
- **特点**:可读写,游戏可访问的用户目录
|
||
- **示例**:`user://saves/save_001.dat`
|
||
|
||
#### 3. 普通文件系统路径
|
||
|
||
- **用途**:存储临时文件或调试数据
|
||
- **特点**:完整的文件系统访问
|
||
- **示例**:`C:/Games/MyGame/logs/debug.log`
|
||
|
||
### 路径验证与清理
|
||
|
||
```mermaid
|
||
graph TD
|
||
A[输入路径] --> B{包含 ".." ?}
|
||
B -->|是| C[抛出异常]
|
||
B -->|否| D{是 Godot 路径?}
|
||
D -->|是| E[直接使用]
|
||
D -->|否| F[清理路径段]
|
||
F --> G[替换无效字符]
|
||
G --> H[创建目录结构]
|
||
H --> I[返回绝对路径]
|
||
C --> J[结束]
|
||
E --> J
|
||
I --> J
|
||
```
|
||
|
||
### 线程安全机制
|
||
|
||
每个文件路径都有独立的锁对象,确保:
|
||
|
||
1. **细粒度锁** - 不同文件可以并发访问
|
||
2. **避免死锁** - 锁的获取顺序一致
|
||
3. **高性能** - 减少锁竞争
|
||
|
||
## API 接口
|
||
|
||
### IStorage 接口
|
||
|
||
```csharp
|
||
public interface IStorage
|
||
{
|
||
// 读取操作
|
||
T Read<T>(string key);
|
||
T Read<T>(string key, T defaultValue);
|
||
Task<T> ReadAsync<T>(string key);
|
||
|
||
// 写入操作
|
||
void Write<T>(string key, T value);
|
||
Task WriteAsync<T>(string key, T value);
|
||
|
||
// 检查存在性
|
||
bool Exists(string key);
|
||
Task<bool> ExistsAsync(string key);
|
||
|
||
// 删除操作(未实现)
|
||
void Delete(string key);
|
||
}
|
||
```
|
||
|
||
## 使用示例
|
||
|
||
### 基本使用
|
||
|
||
```csharp
|
||
// 创建存储实例(需要序列化器)
|
||
var serializer = new JsonSerializer(); // 或其他序列化器
|
||
var storage = new GodotFileStorage(serializer);
|
||
|
||
// 写入用户数据
|
||
var userData = new UserData
|
||
{
|
||
PlayerName = "Alice",
|
||
Level = 5,
|
||
Score = 1000
|
||
};
|
||
storage.Write("user://player.dat", userData);
|
||
|
||
// 读取用户数据
|
||
var loadedData = storage.Read<UserData>("user://player.dat");
|
||
Console.WriteLine($"Player: {loadedData.PlayerName}, Level: {loadedData.Level}");
|
||
```
|
||
|
||
### 异步操作
|
||
|
||
```csharp
|
||
// 异步写入游戏配置
|
||
var config = new GameConfig
|
||
{
|
||
Resolution = "1920x1080",
|
||
Fullscreen = true,
|
||
Volume = 0.8f
|
||
};
|
||
await storage.WriteAsync("user://config.json", config);
|
||
|
||
// 异步读取配置
|
||
var loadedConfig = await storage.ReadAsync<GameConfig>("user://config.json");
|
||
```
|
||
|
||
### 不同路径类型使用
|
||
|
||
```csharp
|
||
// 读取游戏资源(只读)
|
||
var levelData = storage.Read<LevelData>("res://levels/level_001.json");
|
||
|
||
// 存储用户存档
|
||
var saveData = new SaveData { /* ... */ };
|
||
storage.Write("user://saves/slot_001.dat", saveData);
|
||
|
||
// 存储调试信息(普通路径)
|
||
var debugLog = new DebugLog { /* ... */ };
|
||
storage.Write("logs/debug_" + DateTime.Now.Ticks + ".json", debugLog);
|
||
```
|
||
|
||
### 存在性检查
|
||
|
||
```csharp
|
||
// 检查文件是否存在
|
||
if (storage.Exists("user://settings.json"))
|
||
{
|
||
var settings = storage.Read<AppSettings>("user://settings.json");
|
||
// 使用设置...
|
||
}
|
||
else
|
||
{
|
||
// 使用默认设置
|
||
var defaultSettings = new AppSettings();
|
||
storage.Write("user://settings.json", defaultSettings);
|
||
}
|
||
```
|
||
|
||
### 带默认值的读取
|
||
|
||
```csharp
|
||
// 尝试读取,如果文件不存在则返回默认值
|
||
var settings = storage.Read("user://user_prefs.json", new UserPrefs
|
||
{
|
||
Language = "en",
|
||
Volume = 1.0f,
|
||
Difficulty = 1
|
||
});
|
||
```
|
||
|
||
## 路径扩展
|
||
|
||
该模块使用了路径扩展方法:
|
||
|
||
```csharp
|
||
public static class GodotPathExtensions
|
||
{
|
||
public static bool IsUserPath(this string path);
|
||
public static bool IsResPath(this string path);
|
||
public static bool IsGodotPath(this string path);
|
||
}
|
||
```
|
||
|
||
**使用示例:**
|
||
|
||
```csharp
|
||
string path1 = "user://save.dat";
|
||
string path2 = "res://config.json";
|
||
string path3 = "C:/temp/file.txt";
|
||
|
||
Console.WriteLine(path1.IsGodotPath()); // true
|
||
Console.WriteLine(path1.IsUserPath()); // true
|
||
Console.WriteLine(path2.IsResPath()); // true
|
||
Console.WriteLine(path3.IsGodotPath()); // false
|
||
```
|
||
|
||
## 性能考虑
|
||
|
||
### 1. 锁机制
|
||
|
||
- 每个文件路径独立锁,减少锁竞争
|
||
- 读写操作串行化,避免数据损坏
|
||
|
||
### 2. 文件访问
|
||
|
||
- Godot 虚拟路径使用 `FileAccess` API
|
||
- 普通路径使用标准 .NET 文件 I/O
|
||
- 自动创建目录结构
|
||
|
||
### 3. 内存使用
|
||
|
||
- 锁对象使用 `ConcurrentDictionary` 管理
|
||
- 锁对象按需创建,避免内存泄漏
|
||
|
||
## 错误处理
|
||
|
||
### 常见异常
|
||
|
||
1. **ArgumentException** - 路径参数无效
|
||
- 空路径
|
||
- 包含 ".." 的路径
|
||
- 无效的存储键
|
||
|
||
2. **FileNotFoundException** - 文件不存在
|
||
- 读取不存在的文件时抛出
|
||
|
||
3. **IOException** - 文件操作失败
|
||
- 写入权限不足
|
||
- 磁盘空间不足
|
||
|
||
### 错误处理示例
|
||
|
||
```csharp
|
||
try
|
||
{
|
||
var data = storage.Read<UserData>("user://save.dat");
|
||
}
|
||
catch (FileNotFoundException)
|
||
{
|
||
Console.WriteLine("存档文件不存在,创建新的存档");
|
||
var newSave = new UserData();
|
||
storage.Write("user://save.dat", newSave);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"读取存档失败: {ex.Message}");
|
||
}
|
||
```
|
||
|
||
## 最佳实践
|
||
|
||
1. **路径选择**
|
||
- 游戏资源使用 `res://`
|
||
- 用户数据使用 `user://`
|
||
- 调试/临时文件使用普通路径
|
||
|
||
2. **异常处理**
|
||
- 总是处理 `FileNotFoundException`
|
||
- 使用带默认值的 `Read` 重载方法
|
||
|
||
3. **性能优化**
|
||
- 批量读写时使用异步方法
|
||
- 避免频繁的小文件操作
|
||
|
||
4. **序列化器选择**
|
||
- JSON:人类可读,调试友好
|
||
- 二进制:性能更好,文件更小 |