1. 历时两天,完整且完美的设计了宠物类,使用到了多种设计模式,完成了低耦合,高内聚的完美代码,测试也完美通过。

2. 顺便完善了底层通信类的封装,基于websocket,基准测试通过,但存在一点很小的线程bug,似乎是来自于esp32 idf底层的问题,待解决
This commit is contained in:
Misaki
2025-09-15 01:49:09 +08:00
parent 97fe13da26
commit dc420c3b7a
12 changed files with 1466 additions and 74 deletions
-2
View File
@@ -26,8 +26,6 @@ WebSocketManager::WebSocketManager()
{
// 初始化统计信息
stats = {0, 0, 0, 0};
}
+110
View File
@@ -4,8 +4,118 @@
#include "CppHandle.h"
#include "OTAClass.h"
#include "PetBaseClass.h"
#include "PetDao.h"
#include <iostream>
void testPetSystem() {
std::cout << "Test point1" << std::endl;
// 创建阶段策略
auto stageStrategy = std::make_unique<PetStageStrategy>();
stageStrategy->addStage(PetStageType::PET_STAGE_YOUNG, "models/young.obj");
stageStrategy->addStage(PetStageType::PET_STAGE_ADULT, "models/adult.obj");
stageStrategy->addStage(PetStageType::PET_STAGE_OLD, "models/old.obj");
stageStrategy->addStageAudio(PetStageType::PET_STAGE_YOUNG, "audio/young.mp3");
stageStrategy->addStageAudio(PetStageType::PET_STAGE_ADULT, "audio/adult.mp3");
stageStrategy->addStageAudio(PetStageType::PET_STAGE_OLD, "audio/old.mp3");
// 创建动作策略
auto actionStrategy = std::make_unique<PetActionStrategy>();
actionStrategy->addAction(PetActionType::PET_ACTION_EAT, "models/eat.obj");
actionStrategy->addAction(PetActionType::PET_ACTION_HAPPY, "models/happy.obj");
actionStrategy->addAction(PetActionType::PET_ACTION_SLEEP, "models/sleep.obj");
actionStrategy->addAction(PetActionType::PET_ACTION_ANGRY, "models/angry.obj");
actionStrategy->addAction(PetActionType::PET_ACTION_SAD, "models/sad.obj");
actionStrategy->addAction(PetActionType::PET_ACTION_EVOLVE, "models/evolve.obj");
actionStrategy->addAction(PetActionType::PET_ACTION_TOUCH, "models/touch.obj");
actionStrategy->addActionAudio(PetActionType::PET_ACTION_EAT, "audio/eat.mp3");
actionStrategy->addActionAudio(PetActionType::PET_ACTION_HAPPY, "audio/happy.mp3");
actionStrategy->addActionAudio(PetActionType::PET_ACTION_SLEEP, "audio/sleep.mp3");
std::cout << "Test point2" << std::endl;
// 创建宠物信息
PetBaseInfo info;
info.pet_name = "芝士雪豹";
info.pet_hp = 100;
info.pet_density = 50;
info.pet_identity = "我是顶真";
// 创建宠物
auto pet = std::make_shared<PetBase>(info, std::move(stageStrategy), std::move(actionStrategy));
std::cout << "Test point3" << std::endl;
// 创建音频观察者
auto audioStrategy = std::make_shared<PetAudioStrategy>();
audioStrategy->setAudioCallback([](const std::string& audioPath) {
std::cout << "Playing audio: " << audioPath << std::endl;
});
audioStrategy->subscribe(pet);
std::cout << "Test point4" << std::endl;
// 创建渲染器观察者
auto rendererStrategy = std::make_shared<PetRendererStrategy>(
pet->getStageStrategy(),
pet->getActionStrategy()
);
rendererStrategy->setRenderCallback([](const std::string& modelPath) {
std::cout << "Rendering model: " << modelPath << std::endl;
});
rendererStrategy->subscribe(pet);
std::cout << "Test point5" << std::endl;
// 执行一些动作
std::cout << "=== Testing basic actions ===" << std::endl;
pet->feed();
pet->play();
pet->touch();
std::cout << "Test point6" << std::endl;
// 检查当前状态
std::cout << "Current HP: " << pet->getPetInfo().pet_hp << std::endl;
std::cout << "Current density: " << pet->getPetInfo().pet_density << std::endl;
std::cout << "Current stage: " << static_cast<int>(pet->getCurrentStage()) << std::endl;
std::cout << "Current action: " << static_cast<int>(pet->getCurrentAction()) << std::endl;
std::cout << "Test point7" << std::endl;
// 测试进化
std::cout << "\n=== Testing evolution ===" << std::endl;
// 直接修改亲密度来测试进化
PetBaseInfo newInfo = pet->getPetInfo();
newInfo.pet_density = 100; // 达到进化条件
pet->setPetInfo(newInfo);
std::cout << "Test point8" << std::endl;
if (pet->checkEvolution()) {
std::cout << "Evolution successful!" << std::endl;
} else {
std::cout << "Evolution failed!" << std::endl;
}
// 进一步增加亲密度到150,尝试再次进化
newInfo.pet_density = 150;
pet->setPetInfo(newInfo);
if (pet->checkEvolution()) {
std::cout << "Second evolution successful!" << std::endl;
} else {
std::cout << "Second evolution failed!" << std::endl;
}
std::cout << "Final stage: " << static_cast<int>(pet->getCurrentStage()) << std::endl;
PetDAO petDAO(SDFileManager::getInstance());
petDAO.savePet(pet, "my_pet.json");
// 列出所有宠物文件
auto petFiles = petDAO.listPetFiles();
for (const auto& file : petFiles) {
std::cout << "Pet file: " << file << std::endl;
}
std::cout << SDFileManager::getInstance()->catCommand("/sdcard/pet_data/my_pet.json") << std::endl;
}
void Cpp_Hand() {
testPetSystem();
OTAClass oc;
oc.Init();
}
+176 -29
View File
@@ -20,17 +20,6 @@ const auto sleep_time = seconds{
5
};
esp_pthread_cfg_t create_config(const char *name, int core_id, int stack, int prio)
{
auto cfg = esp_pthread_get_default_config();
cfg.thread_name = name;
cfg.pin_to_core = core_id;
cfg.stack_size = stack;
cfg.prio = prio;
return cfg;
}
#include "ThreadManager.h"
#include "WifiConnectors.h"
#include <string>
@@ -38,6 +27,124 @@ esp_pthread_cfg_t create_config(const char *name, int core_id, int stack, int pr
#include "LVGLRender.h"
#include "SDFileManager.h"
#include "AudioOutput.h"
#include "CommClass.h"
// JSON数据回调函数
void onJsonData(cJSON* json) {
// 打印接收到的JSON数据
char* jsonStr = cJSON_Print(json);
ESP_LOGI("JSON_CALLBACK", "收到JSON数据: %s", jsonStr);
free(jsonStr);
// 解析消息类型并处理
cJSON* type = cJSON_GetObjectItem(json, "type");
if (type && cJSON_IsString(type)) {
if (strcmp(type->valuestring, "greeting") == 0) {
ESP_LOGI("JSON_CALLBACK", "收到服务器问候消息");
} else if (strcmp(type->valuestring, "heartbeat") == 0) {
ESP_LOGI("JSON_CALLBACK", "收到心跳响应");
} else if (strcmp(type->valuestring, "response") == 0) {
ESP_LOGI("JSON_CALLBACK", "收到服务器响应");
} else if (strcmp(type->valuestring, "echo") == 0) {
ESP_LOGI("JSON_CALLBACK", "收到回显消息");
} else if (strcmp(type->valuestring, "broadcast") == 0) {
ESP_LOGI("JSON_CALLBACK", "收到广播消息");
}
}
// 记得删除cJSON对象
cJSON_Delete(json);
}
// WebSocket事件回调函数
void onWebSocketEvent(WebSocketEvent event, const std::string& message) {
switch (event) {
case WebSocketEvent::CONNECTED:
ESP_LOGI("EVENT_CALLBACK", "WebSocket已连接: %s", message.c_str());
break;
case WebSocketEvent::DISCONNECTED:
ESP_LOGI("EVENT_CALLBACK", "WebSocket已断开: %s", message.c_str());
break;
case WebSocketEvent::DATA_RECEIVED:
ESP_LOGI("EVENT_CALLBACK", "收到原始数据: %s", message.c_str());
break;
case WebSocketEvent::ERROR:
ESP_LOGE("EVENT_CALLBACK", "WebSocket错误: %s", message.c_str());
break;
}
}
// 发送状态信息函数
void sendStatus() {
cJSON* status = cJSON_CreateObject();
cJSON_AddStringToObject(status, "type", "status");
cJSON* data = cJSON_CreateObject();
cJSON_AddNumberToObject(data, "free_heap", esp_get_free_heap_size());
cJSON_AddNumberToObject(data, "uptime", xTaskGetTickCount() * portTICK_PERIOD_MS / 1000);
cJSON_AddItemToObject(status, "data", data);
if (WebSocketManager::getInstance()->sendJson(status)) {
ESP_LOGI("SEND", "已发送状态信息");
} else {
ESP_LOGE("SEND", "发送状态信息失败");
}
}
// 发送问候消息函数
void sendGreeting() {
cJSON* greeting = cJSON_CreateObject();
cJSON_AddStringToObject(greeting, "type", "greeting");
cJSON_AddStringToObject(greeting, "message", "Hello from ESP32-S3");
cJSON_AddNumberToObject(greeting, "timestamp", xTaskGetTickCount() * portTICK_PERIOD_MS / 1000);
if (WebSocketManager::getInstance()->sendJson(greeting)) {
ESP_LOGI("SEND", "已发送问候消息");
} else {
ESP_LOGE("SEND", "发送问候消息失败");
}
}
void websocket_task() {
TickType_t lastStatusTime = 0;
TickType_t lastHeartbeatTime = 0;
TickType_t lastGreetingTime = 0;
while (true) {
TickType_t currentTime = xTaskGetTickCount();
// 检查连接状态
if (!WebSocketManager::getInstance()->isConnected()) {
ESP_LOGI("APP_TASK", "WebSocket未连接,尝试重新连接...");
// 确保WiFi已连接
if (!WifiConnectors::getInstance()->isWifiConnect()) {
ESP_LOGI("APP_TASK", "WiFi未连接,等待WiFi连接...");
vTaskDelay(5000 / portTICK_PERIOD_MS);
continue;
}
if (WebSocketManager::getInstance()->connect()) {
ESP_LOGI("APP_TASK", "重新连接成功");
} else {
ESP_LOGI("APP_TASK", "重新连接失败");
}
vTaskDelay(5000 / portTICK_PERIOD_MS);
continue;
}
// 每10秒发送状态信息
if (currentTime - lastStatusTime > (10000 / portTICK_PERIOD_MS)) {
sendStatus();
lastStatusTime = currentTime;
}
// 每60秒发送问候
if (currentTime - lastGreetingTime > (60000 / portTICK_PERIOD_MS)) {
sendGreeting();
lastGreetingTime = currentTime;
}
vTaskDelay(100 / portTICK_PERIOD_MS);
}
}
void OTAClass::Init() {
ESP_LOGI("OTA", "Init");
@@ -65,27 +172,67 @@ void OTAClass::Init() {
// 同步播放
AudioOutput::getInstance()->playSync("/sdcard/music", "Old_Memory.mp3");
// 等待5秒
// vTaskDelay(pdMS_TO_TICKS(10000));
// 暂停
// AudioOutput::getInstance()->pause();
// // 配置Wifi连接线程参数
// ThreadConfig wifi_config;
// wifi_config.name = "WifiConnector"; // 线程名称
// wifi_config.core_id = 1; // 绑定到核心1(避免与主线程冲突)
// wifi_config.stack_size = 4096; // 设置稍大的栈空间(Wifi连接可能需要)
// wifi_config.priority = 6; // 设置较高优先级(确保连接及时)
// // 使用单例方式创建线程,调用connectWifi成员函数
// std::thread wifi_thread = ThreadManager::createSingletonThread<WifiConnectors>(
// wifi_config,
// &WifiConnectors::connectWifi,
// "Misaki-2.4G", // SSID
// "88888888", // 密码
// 5 // 最大重试次数
// );
// wifi_thread.detach();
WifiConnectors::getInstance()->connectWifi("Misaki-2.4G", "88888888", 5);
// 配置Wifi连接线程参数
ThreadConfig wifi_config;
wifi_config.name = "WifiConnector"; // 线程名称
wifi_config.core_id = 1; // 绑定到核心1(避免与主线程冲突)
wifi_config.stack_size = 4096; // 设置稍大的栈空间(Wifi连接可能需要)
wifi_config.priority = 6; // 设置较高优先级(确保连接及时)
// 使用单例方式创建线程,调用connectWifi成员函数
std::thread wifi_thread = ThreadManager::createSingletonThread<WifiConnectors>(
wifi_config,
&WifiConnectors::connectWifi,
"Misaki-2.4G", // SSID
"88888888", // 密码
5 // 最大重试次数
);
// 等待WiFi连接成功后再连接WebSocket
ESP_LOGI("APP_TASK", "等待WiFi连接...");
while (!WifiConnectors::getInstance()->isWifiConnect()) {
ESP_LOGI("APP_TASK", "WiFi未连接,等待中...");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
// 配置WebSocket
WebSocketConfig config;
config.uri = "ws://" + std::string("192.168.1.11") + ":" + std::to_string(8080) + "/ws";
config.auto_reconnect = true; // 自动重连
config.reconnect_interval = 5000; // 重连间隔(毫秒)
config.heartbeat_interval = 30000; // 心跳间隔(毫秒)
config.max_reconnect_attempts = 10; // 最大重连尝试次数
// TODO: 此处通信类存在线程重复创建bug,似乎是来自esp-idf的bug,待查证
// 初始化WebSocket管理器
// if (!WebSocketManager::getInstance()->initialize(config)) {
// ESP_LOGE("APP_TASK", "WebSocket管理器初始化失败");
// vTaskDelete(NULL);
// return;
// }
// 设置回调函数
// WebSocketManager::getInstance()->setJsonCallback(onJsonData);
// WebSocketManager::getInstance()->setEventCallback(onWebSocketEvent);
// 连接WebSocket服务器
// ESP_LOGI("APP_TASK", "正在连接WebSocket服务器: %s", config.uri.c_str());
// if (!WebSocketManager::getInstance()->connect()) {
// ESP_LOGE("APP_TASK", "WebSocket连接失败");
// }
// 创建WebSocket任务
// ThreadConfig websocket_config;
// websocket_config.core_id = 0;
// websocket_config.inherit_cfg = true;
// websocket_config.name = "websocket_task";
// websocket_config.priority = 5;
// websocket_config.stack_size = 4096;
// std::thread websocket_thread = ThreadManager::createThread(websocket_config, websocket_task);
// websocket_thread.detach();
// ThreadConfig ota_config;
// ota_config.name = "OTA";
+210 -1
View File
@@ -2,5 +2,214 @@
// Created by misaki on 2025/9/2.
//
#pragma once
#include "PetInterface.h"
#include "PetObserver.h"
// 宠物基类
// 宠物基本信息
struct PetBaseInfo {
///<! 宠物名称
std::string pet_name;
///<! 宠物生命值(1~100)
int pet_hp;
///<! 宠物亲密度(1~150) 每过50为一个阶段
int pet_density;
///<! 宠物身份
std::string pet_identity;
};
// 宠物阶段策略,继承自主题
class PetBase : public PetSubject, public std::enable_shared_from_this<PetBase> {
public:
// 构造函数
PetBase() = default;
// 带参数的构造函数
PetBase(PetBaseInfo info,
std::shared_ptr<PetStageStrategy> stageStrategy,
std::shared_ptr<PetActionStrategy> actionStrategy)
: pet_info(std::move(info)),
pet_stage_strategy(std::move(stageStrategy)),
pet_action_strategy(std::move(actionStrategy)) {}
virtual ~PetBase() = default;
// 实现PetSubject接口
/**
* 添加观察者
* @param observer 观察者
*/
void addObserver(std::shared_ptr<PetObserver> observer) override {
observers.push_back(observer);
}
/**
* 移除观察者
* @param observer 观察者
*/
void removeObserver(std::shared_ptr<PetObserver> observer) override {
observers.remove(observer);
}
/**
* 通知动作
* @param action 动作
*/
void notifyAction(PetActionType action) override {
for (auto& observer : observers) {
observer->onPetAction(action);
}
}
/**
* 通知阶段
* @param oldStage 旧阶段
* @param newStage 新阶段
*/
void notifyStageChange(PetStageType oldStage, PetStageType newStage) override {
for (auto& observer : observers) {
observer->onPetStageChange(oldStage, newStage);
}
}
// 宠物行为方法
// 喂食
virtual void feed() {
// 喂食会增加生命值和亲密度
pet_info.pet_hp = std::min(100, pet_info.pet_hp + 10);
pet_info.pet_density = std::min(150, pet_info.pet_density + 15);
// 执行喂食动作并通知观察者
if (pet_action_strategy->performAction(PetActionType::PET_ACTION_EAT)) {
notifyAction(PetActionType::PET_ACTION_EAT);
}
// 检查是否可以进化
checkEvolution();
}
// 玩耍
virtual void play() {
// 玩耍会增加亲密度
pet_info.pet_density = std::min(150, pet_info.pet_density + 10);
// 执行开心动作并通知观察者
if (pet_action_strategy->performAction(PetActionType::PET_ACTION_HAPPY)) {
notifyAction(PetActionType::PET_ACTION_HAPPY);
}
// 检查是否可以进化
checkEvolution();
}
// 责骂
virtual void scold() {
// 责骂会减少亲密度
pet_info.pet_density = std::max(0, pet_info.pet_density - 10);
// 执行生气动作并通知观察者
if (pet_action_strategy->performAction(PetActionType::PET_ACTION_ANGRY)) {
notifyAction(PetActionType::PET_ACTION_ANGRY);
}
}
// 忽视
virtual void neglect() {
// 忽视会减少生命值和亲密度
pet_info.pet_hp = std::max(0, pet_info.pet_hp - 5);
pet_info.pet_density = std::max(0, pet_info.pet_density - 8);
// 执行沮丧动作并通知观察者
if (pet_action_strategy->performAction(PetActionType::PET_ACTION_SAD)) {
notifyAction(PetActionType::PET_ACTION_SAD);
}
}
// 触摸
virtual void touch() {
// 触摸会增加亲密度
pet_info.pet_density = std::min(150, pet_info.pet_density + 5);
// 执行被触摸动作并通知观察者
if (pet_action_strategy->performAction(PetActionType::PET_ACTION_TOUCH)) {
notifyAction(PetActionType::PET_ACTION_TOUCH);
}
// 检查是否可以进化
checkEvolution();
}
// 进化检查
virtual bool checkEvolution() {
// 获取当前阶段
auto currentStage = pet_stage_strategy->getCurrentStageType();
// 根据亲密度决定是否可以进化
if (pet_info.pet_density >= 100 && currentStage == PetStageType::PET_STAGE_YOUNG) {
// 从幼年进化到青年
return evolveToStage(PetStageType::PET_STAGE_ADULT);
} else if (pet_info.pet_density >= 150 && currentStage == PetStageType::PET_STAGE_ADULT) {
// 从青年进化到成年
return evolveToStage(PetStageType::PET_STAGE_OLD);
}
return false;
}
// 进化到指定阶段
virtual bool evolveToStage(PetStageType newStage) {
auto oldStage = pet_stage_strategy->getCurrentStageType(); // 获取当前阶段
if (pet_stage_strategy->switchToStage(newStage)) {
// 执行进化动作并通知观察者
if (pet_action_strategy->performAction(PetActionType::PET_ACTION_EVOLVE)) {
notifyAction(PetActionType::PET_ACTION_EVOLVE);
}
// 通知阶段变化
notifyStageChange(oldStage, newStage);
return true;
}
return false;
}
/**
* 获取宠物信息
* @return 宠物信息
*/
virtual const PetBaseInfo& getPetInfo() const {
return pet_info;
}
/**
* 设置宠物信息
* @param info 宠物信息
*/
virtual void setPetInfo(const PetBaseInfo& info) {
pet_info = info;
}
/**
* 获取阶段策略
* @return 阶段策略
*/
virtual std::shared_ptr<PetStageStrategy> getStageStrategy() const {
return pet_stage_strategy;
}
/**
* 设置阶段策略
* @param strategy 阶段策略
*/
virtual void setStageStrategy(std::unique_ptr<PetStageStrategy> strategy) {
pet_stage_strategy = std::move(strategy);
}
/**
* 获取动作策略
* @return 动作策略
*/
virtual std::shared_ptr<PetActionStrategy> getActionStrategy() const {
return pet_action_strategy;
}
/**
* 设置动作策略
* @param strategy 动作策略
*/
virtual void setActionStrategy(std::unique_ptr<PetActionStrategy> strategy) {
pet_action_strategy = std::move(strategy);
}
/**
* 获取当前阶段
* @return 当前阶段
*/
virtual PetStageType getCurrentStage() const {
return pet_stage_strategy->getCurrentStageType();
}
/**
* 获取当前动作
* @return 当前动作
*/
virtual PetActionType getCurrentAction() const {
return pet_action_strategy->getCurrentActionType();
}
public:
///<! 宠物信息
PetBaseInfo pet_info;
///<! 宠物阶段策略
std::shared_ptr<PetStageStrategy> pet_stage_strategy;
///<! 宠物动作策略
std::shared_ptr<PetActionStrategy> pet_action_strategy;
///<! 观察者列表
std::list<std::shared_ptr<PetObserver>> observers;
};
+306
View File
@@ -0,0 +1,306 @@
//
// Created by misaki on 2025/9/14.
//
#include "PetDao.h"
#include <iostream>
#include <sstream>
using namespace PetEnumConverter;
// PetEnumConverter 实现
std::string PetEnumConverter::stageTypeToString(PetStageType stage) {
switch (stage) {
#define X(name, desc) case PetStageType::name: return #name;
PET_STAGE_TYPES
#undef X
default: return "UNKNOWN";
}
}
PetStageType PetEnumConverter::stringToStageType(const std::string& str) {
#define X(name, desc) if (str == #name) return PetStageType::name;
PET_STAGE_TYPES
#undef X
return PetStageType::PET_STAGE_YOUNG; // 默认值
}
std::string PetEnumConverter::actionTypeToString(PetActionType action) {
switch (action) {
#define X(name, desc) case PetActionType::name: return #name;
PET_ACTION_TYPES
#undef X
default: return "UNKNOWN";
}
}
PetActionType PetEnumConverter::stringToActionType(const std::string& str) {
#define X(name, desc) if (str == #name) return PetActionType::name;
PET_ACTION_TYPES
#undef X
return PetActionType::PET_ACTION_SLEEP; // 默认值
}
// PetDAO 实现
PetDAO::PetDAO(SDFileManager* fileManager) : fileManager(fileManager) {
// 确保宠物数据目录存在
fileManager->mkdirCommand( PET_DATA_DIR);
}
bool PetDAO::savePet(const std::shared_ptr<PetBase>& pet, const std::string& filename) {
// 转换为JSON
cJSON* json = petToJson(pet);
if (!json) {
return false;
}
// 转换为字符串
char* jsonStr = cJSON_PrintUnformatted(json);
std::string fullPath = std::string(PET_DATA_DIR) + "/" + filename;
// 保存到文件
bool success = fileManager->writeFileSync(fullPath.c_str(), jsonStr);
// 清理资源
free(jsonStr);
cJSON_Delete(json);
return success;
}
std::shared_ptr<PetBase> PetDAO::loadPet(const std::string& filename) {
std::string fullPath = std::string(PET_DATA_DIR) + "/" + filename;
// 读取文件内容
std::string content = fileManager->readFileSync(fullPath.c_str());
if (content.empty()) {
return nullptr;
}
// 解析JSON
cJSON* json = cJSON_Parse(content.c_str());
if (!json) {
return nullptr;
}
// 创建宠物对象
auto pet = petFromJson(json);
// 清理资源
cJSON_Delete(json);
return pet;
}
std::vector<std::string> PetDAO::listPetFiles() {
return fileManager->listFilesSync(PET_DATA_DIR, ".json");
}
bool PetDAO::deletePetFile(const std::string& filename) {
std::string fullPath = std::string(PET_DATA_DIR) + "/" + filename;
return fileManager->rmCommand(fullPath.c_str(), false);
}
cJSON* PetDAO::petToJson(const std::shared_ptr<PetBase>& pet) {
cJSON* json = cJSON_CreateObject();
if (!json) return nullptr;
// 添加基本信息
cJSON_AddStringToObject(json, "name", pet->pet_info.pet_name.c_str());
cJSON_AddNumberToObject(json, "hp", pet->pet_info.pet_hp);
cJSON_AddNumberToObject(json, "density", pet->pet_info.pet_density);
cJSON_AddStringToObject(json, "identity", pet->pet_info.pet_identity.c_str());
// 添加阶段策略
cJSON* stageJson = stageStrategyToJson(pet->pet_stage_strategy);
if (stageJson) {
cJSON_AddItemToObject(json, "stage_strategy", stageJson);
}
// 添加动作策略
cJSON* actionJson = actionStrategyToJson(pet->pet_action_strategy);
if (actionJson) {
cJSON_AddItemToObject(json, "action_strategy", actionJson);
}
return json;
}
std::shared_ptr<PetBase> PetDAO::petFromJson(cJSON* json) {
if (!json) return nullptr;
// 创建宠物基本信息
PetBaseInfo info;
cJSON* nameItem = cJSON_GetObjectItemCaseSensitive(json, "name");
if (cJSON_IsString(nameItem)) {
info.pet_name = nameItem->valuestring;
}
cJSON* hpItem = cJSON_GetObjectItemCaseSensitive(json, "hp");
if (cJSON_IsNumber(hpItem)) {
info.pet_hp = hpItem->valueint;
}
cJSON* densityItem = cJSON_GetObjectItemCaseSensitive(json, "density");
if (cJSON_IsNumber(densityItem)) {
info.pet_density = densityItem->valueint;
}
cJSON* identityItem = cJSON_GetObjectItemCaseSensitive(json, "identity");
if (cJSON_IsString(identityItem)) {
info.pet_identity = identityItem->valuestring;
}
// 创建阶段策略
cJSON* stageJson = cJSON_GetObjectItemCaseSensitive(json, "stage_strategy");
auto stageStrategy = stageStrategyFromJson(stageJson);
// 创建动作策略
cJSON* actionJson = cJSON_GetObjectItemCaseSensitive(json, "action_strategy");
auto actionStrategy = actionStrategyFromJson(actionJson);
// 创建宠物对象
return std::make_shared<PetBase>(info, stageStrategy, actionStrategy);
}
cJSON* PetDAO::stageStrategyToJson(const std::shared_ptr<PetStageStrategy>& strategy) {
if (!strategy) return nullptr;
cJSON* json = cJSON_CreateObject();
if (!json) return nullptr;
// 添加当前阶段
cJSON_AddStringToObject(json, "current_stage",
stageTypeToString(strategy->getCurrentStageType()).c_str());
// 添加阶段模型映射
cJSON* modelMap = cJSON_CreateObject();
for (const auto& pair : strategy->getStageModelMap()) {
cJSON_AddStringToObject(modelMap,
stageTypeToString(pair.first).c_str(),
pair.second.c_str());
}
cJSON_AddItemToObject(json, "stage_model_map", modelMap);
// 添加阶段音频映射
cJSON* audioMap = cJSON_CreateObject();
for (const auto& pair : strategy->getStageAudioMap()) {
cJSON_AddStringToObject(audioMap,
stageTypeToString(pair.first).c_str(),
pair.second.c_str());
}
cJSON_AddItemToObject(json, "stage_audio_map", audioMap);
return json;
}
std::shared_ptr<PetStageStrategy> PetDAO::stageStrategyFromJson(cJSON* json) {
if (!json) return nullptr;
auto strategy = std::make_shared<PetStageStrategy>();
// 获取当前阶段
cJSON* currentStageItem = cJSON_GetObjectItemCaseSensitive(json, "current_stage");
if (cJSON_IsString(currentStageItem)) {
strategy->current_stage = stringToStageType(currentStageItem->valuestring);
}
// 获取阶段模型映射
cJSON* modelMapItem = cJSON_GetObjectItemCaseSensitive(json, "stage_model_map");
if (cJSON_IsObject(modelMapItem)) {
cJSON* child = modelMapItem->child;
while (child) {
if (cJSON_IsString(child)) {
PetStageType stage = stringToStageType(child->string);
strategy->stage_model_map[stage] = child->valuestring;
}
child = child->next;
}
}
// 获取阶段音频映射
cJSON* audioMapItem = cJSON_GetObjectItemCaseSensitive(json, "stage_audio_map");
if (cJSON_IsObject(audioMapItem)) {
cJSON* child = audioMapItem->child;
while (child) {
if (cJSON_IsString(child)) {
PetStageType stage = stringToStageType(child->string);
strategy->stage_audio_map[stage] = child->valuestring;
}
child = child->next;
}
}
return strategy;
}
cJSON* PetDAO::actionStrategyToJson(const std::shared_ptr<PetActionStrategy>& strategy) {
if (!strategy) return nullptr;
cJSON* json = cJSON_CreateObject();
if (!json) return nullptr;
// 添加当前动作
cJSON_AddStringToObject(json, "current_action",
actionTypeToString(strategy->getCurrentActionType()).c_str());
// 添加动作模型映射
cJSON* modelMap = cJSON_CreateObject();
for (const auto& pair : strategy->getActionModelMap()) {
cJSON_AddStringToObject(modelMap,
actionTypeToString(pair.first).c_str(),
pair.second.c_str());
}
cJSON_AddItemToObject(json, "action_model_map", modelMap);
// 添加动作音频映射
cJSON* audioMap = cJSON_CreateObject();
for (const auto& pair : strategy->getActionAudioMap()) {
cJSON_AddStringToObject(audioMap,
actionTypeToString(pair.first).c_str(),
pair.second.c_str());
}
cJSON_AddItemToObject(json, "action_audio_map", audioMap);
return json;
}
std::shared_ptr<PetActionStrategy> PetDAO::actionStrategyFromJson(cJSON* json) {
if (!json) return nullptr;
auto strategy = std::make_shared<PetActionStrategy>();
// 获取当前动作
cJSON* currentActionItem = cJSON_GetObjectItemCaseSensitive(json, "current_action");
if (cJSON_IsString(currentActionItem)) {
strategy->current_action = stringToActionType(currentActionItem->valuestring);
}
// 获取动作模型映射
cJSON* modelMapItem = cJSON_GetObjectItemCaseSensitive(json, "action_model_map");
if (cJSON_IsObject(modelMapItem)) {
cJSON* child = modelMapItem->child;
while (child) {
if (cJSON_IsString(child)) {
PetActionType action = stringToActionType(child->string);
strategy->action_model_map[action] = child->valuestring;
}
child = child->next;
}
}
// 获取动作音频映射
cJSON* audioMapItem = cJSON_GetObjectItemCaseSensitive(json, "action_audio_map");
if (cJSON_IsObject(audioMapItem)) {
cJSON* child = audioMapItem->child;
while (child) {
if (cJSON_IsString(child)) {
PetActionType action = stringToActionType(child->string);
strategy->action_audio_map[action] = child->valuestring;
}
child = child->next;
}
}
return strategy;
}
+65
View File
@@ -0,0 +1,65 @@
//
// Created by misaki on 2025/9/14.
//
#pragma once
#include "PetBaseClass.h"
#include "SDFileManager.h"
#include "cJSON.h"
#include <unordered_map>
#include <string>
// 辅助函数:枚举类型与字符串的转换
namespace PetEnumConverter {
// PetStageType 转换
std::string stageTypeToString(PetStageType stage);
PetStageType stringToStageType(const std::string& str);
// PetActionType 转换
std::string actionTypeToString(PetActionType action);
PetActionType stringToActionType(const std::string& str);
}
// PetDAO 类 - 负责宠物的数据持久化
class PetDAO {
public:
// 构造函数,需要SDFileManager实例
explicit PetDAO(SDFileManager* fileManager);
// 保存宠物数据到文件
bool savePet(const std::shared_ptr<PetBase>& pet, const std::string& filename);
// 从文件加载宠物数据
std::shared_ptr<PetBase> loadPet(const std::string& filename);
// 获取所有保存的宠物文件列表
std::vector<std::string> listPetFiles();
// 删除宠物文件
bool deletePetFile(const std::string& filename);
private:
// 将宠物数据转换为JSON对象
cJSON* petToJson(const std::shared_ptr<PetBase>& pet);
// 从JSON对象创建宠物
std::shared_ptr<PetBase> petFromJson(cJSON* json);
// 将阶段策略转换为JSON对象
cJSON* stageStrategyToJson(const std::shared_ptr<PetStageStrategy>& strategy);
// 从JSON对象创建阶段策略
std::shared_ptr<PetStageStrategy> stageStrategyFromJson(cJSON* json);
// 将动作策略转换为JSON对象
cJSON* actionStrategyToJson(const std::shared_ptr<PetActionStrategy>& strategy);
// 从JSON对象创建动作策略
std::shared_ptr<PetActionStrategy> actionStrategyFromJson(cJSON* json);
// 文件管理器实例
SDFileManager* fileManager;
// 宠物数据存储目录
static constexpr const char* PET_DATA_DIR = "/sdcard/pet_data";
};
+373 -38
View File
@@ -1,54 +1,389 @@
//
// Created by misaki on 2025/9/8.
//
/**
* 需要实现的最终效果
1. 可以选择多种宠物,如雪豹,卡皮巴拉,守宫,每种宠物有独特音频和动作,通过联网更新切换。
2. 每种宠物有可以编辑的身份设定,类似小智AI的身份
3. 宠物三个阶段,幼年,青年,成年,阶段切换载入新模型即可。
4. 语音互动可以实现喂食,开心,生气,沮丧几种动作
5. 宠物具有生命值hp和亲密度,当发生喂食等动作会对生命值和亲密度产生影响,亲密度达到一定程度宠物可以进化到新的阶段。
6. 宠物的各种信息是可以保存和读取的。
分析一下具体设计思路:
1. 首先抽象出所有的宠物的共同点为一个基类,包括:名称,生命值,亲密度,身份设定
对于阶段,动作,不同的宠物之间存在不同的实现差异,可以为其抽象出不同的接口,组合到抽象出来的宠物基类当中
这样当创建新的宠物时,只需要实现对应的接口即可(在此使用策略模式),而在创建不同的宠物的过程中,又使用到了抽象工厂模式
对于音频,由于阶段变化或是产生动作,都会触发响应的音频,因此音频需要作为一个观察者,当宠物产生阶段变化或是产生动作,就会触发相应的音频播放
2. 接着要考虑到与宠物相关的一些事件,例如外部通知网络更新,语音输入互动,喂食这些事件,需要及时做出响应,在此使用依赖注入来解决这一类事件处理问题
向外部类当中组合一个依赖注入类,将宠物类当中的回调函数注入到其中,当发生某些事件时,就会触发相应的回调函数,并执行相应的操作
这样在增加新的事件时,就不需要频繁修改外部事件类,只需要新增新的依赖注入项(如果C++17支持反射的话还有更好的实现方法)
3. 再者是对于宠物的信息保存与读取,在此处增加一个DAO层,专门用于数据持久化,信息以json的方式存储与读取
既然存在了宠物的数据信息,那么就需要在DAO层之上增加一个可以从json数据中得到新的宠物对象的方法或者是类,以此组合出所需的宠物对象
4. 还需要考虑到一些极端事件的情况,例如设备断电,用户强制关机,网络中断等,最好的处理方式就是及时做好数据持久化和数据备份
设置bak备份防止在写文件的时候断电导致写文件错误
*/
#pragma once
#include <string>
#include <memory>
#include <unordered_map>
#include <utility>
#include <vector>
#include <list>
#include <algorithm>
#include <functional>
// 前向声明主题
class PetSubject;
// 资源句柄,防止裸指针到处飞
template<class T>
using Handle = std::shared_ptr<T>; /// 宠物句柄
// 某个宠物不同阶段的抽象接口 宠物的不同阶段对应于不同的模型文件路径
// 如果需要添加新的阶段,要在PetStageType当中增加新的阶段,并将新的类继承自PetStage类
// 由于使用的是C++17,并不支持反射,只能通过宏定义统一管理,以此实现松耦合和开闭原则
#define PET_STAGE_TYPES \
X(PET_STAGE_YOUNG, "幼年") \
X(PET_STAGE_ADULT, "青年") \
X(PET_STAGE_OLD, "成年")
enum class PetStageType {
#define X(name, desc) name,
PET_STAGE_TYPES
#undef X
};
// 某个宠物所有阶段的接口集合,该接口集合会被组合到抽象出来的宠物基类当中,该接口还需要抽象出宠物不同阶段可能具备的行为
class PetStageStrategy {
public:
virtual ~PetStageStrategy() = default;
// 定义阶段顺序的静态常量
static const std::vector<PetStageType>& getStageOrder() {
static const std::vector<PetStageType> stageOrder = []() {
std::vector<PetStageType> order;
#define X(name, desc) order.push_back(PetStageType::name);
PET_STAGE_TYPES
#undef X
return order;
}();
return stageOrder;
}
/**
* 添加阶段及其模型路径
* @param stage 阶段类型
* @param model_path 模型路径
*/
virtual void addStage(PetStageType stage, const std::string& model_path) {
stage_model_map[stage] = model_path;
// 如果这是第一个阶段,设置为当前阶段
if (stage_model_map.size() == 1) {
current_stage = stage;
}
}
/**
* 添加阶段音频映射
* @param stage 阶段类型
* @param audio_path 音频文件路径
*/
virtual void addStageAudio(PetStageType stage, const std::string& audio_path) {
stage_audio_map[stage] = audio_path;
}
/**
* 获取阶段模型映射表
* @return 阶段到模型路径的映射表
*/
virtual const std::unordered_map<PetStageType, std::string>& getStageModelMap() const {
return stage_model_map;
}
/**
* 获取阶段音频映射表
* @return 阶段到音频路径的映射表
*/
virtual const std::unordered_map<PetStageType, std::string>& getStageAudioMap() const {
return stage_audio_map;
}
/**
* 获取特定阶段的模型路径
* @param stage 阶段类型
* @return 模型路径,如果不存在则返回空字符串
*/
virtual std::string getStageModelPath(PetStageType stage) const {
auto it = stage_model_map.find(stage);
if (it != stage_model_map.end()) {
return it->second;
}
return "";
}
/**
* 获取特定阶段的音频路径
* @param stage 阶段类型
* @return 音频路径,如果不存在则返回空字符串
*/
virtual std::string getStageAudioPath(PetStageType stage) const {
auto it = stage_audio_map.find(stage);
if (it != stage_audio_map.end()) {
return it->second;
}
return "";
}
/**
* 切换到指定阶段
* @param newStage 要切换到的阶段类型
* @return 是否成功切换
*/
virtual bool switchToStage(PetStageType newStage) {
if (stage_model_map.find(newStage) != stage_model_map.end()) {
current_stage = newStage;
return true;
}
return false;
}
/**
* 进化到下一阶段(基于当前阶段)
* @return 是否成功进化
*/
virtual bool evolveToNextStage() {
const auto& stageOrder = getStageOrder();
auto it = std::find(stageOrder.begin(), stageOrder.end(), current_stage);
// 前向声明
class IPet; /// 宠物接口
using IPetPtr = Handle<IPet>; /// 宠物句柄
// 事件类型
enum class Emotion {
Feed, /// 喂食
Happy, /// 高兴
Angry, /// 生气
Upset /// 沮丧
if (it == stageOrder.end() || it == stageOrder.end() - 1) {
return false;
}
// 查找下一个可用阶段
for (auto iter = it + 1; iter != stageOrder.end(); ++iter) {
if (stage_model_map.find(*iter) != stage_model_map.end()) {
current_stage = *iter;
return true;
}
}
return false;
}
/**
* 获取当前阶段
* @return 当前阶段类型
*/
virtual PetStageType getCurrentStageType() const {
return current_stage;
}
/**
* 获取当前阶段的模型路径
* @return 当前阶段模型路径
*/
virtual std::string getCurrentStageModelPath() const {
return getStageModelPath(current_stage);
}
/**
* 获取所有可用阶段
* @return 所有阶段的列表
*/
virtual std::list<PetStageType> getAllStages() const {
std::list<PetStageType> stages;
for (const auto& stage : stage_model_map) {
stages.push_back(stage.first);
}
return stages;
}
/**
* 移除阶段
* @param stage 要移除的阶段类型
*/
virtual void removeStage(PetStageType stage) {
if (current_stage == stage && stage_model_map.size() > 1) {
// 找到另一个阶段作为当前阶段
for (const auto& s : stage_model_map) {
if (s.first != stage) {
current_stage = s.first;
break;
}
}
}
stage_model_map.erase(stage);
stage_audio_map.erase(stage);
}
public:
///<! 阶段到模型路径的映射表
std::unordered_map<PetStageType, std::string> stage_model_map;
///<! 阶段到音频路径的映射表
std::unordered_map<PetStageType, std::string> stage_audio_map;
///<! 当前阶段
PetStageType current_stage;
};
// 成长阶段
enum class Stage {
Baby, /// 幼年
Teen, /// 青年
Adult /// 成长
// 宠物动作接口
// 一般而言,对于所有的动物,其动作行为基本相同,不同的地方在与发生动作时,所触发的模型不同
// 因此,宠物的动作设计,和阶段设计基本类似
#define PET_ACTION_TYPES \
X(PET_ACTION_SLEEP, "睡觉") \
X(PET_ACTION_EAT, "喂食") \
X(PET_ACTION_HAPPY, "开心") \
X(PET_ACTION_ANGRY, "生气") \
X(PET_ACTION_SAD, "沮丧") \
X(PET_ACTION_EVOLVE, "进化") \
X(PET_ACTION_TOUCH, "被触摸")
enum class PetActionType {
#define X(name, desc) name,
PET_ACTION_TYPES
#undef X
};
// 宠物动作策略类
class PetActionStrategy {
public:
virtual ~PetActionStrategy() = default;
// 定义动作顺序的静态常量
static const std::vector<PetActionType>& getActionOrder() {
static const std::vector<PetActionType> actionOrder = []() {
std::vector<PetActionType> order;
#define X(name, desc) order.push_back(PetActionType::name);
PET_ACTION_TYPES
#undef X
return order;
}();
return actionOrder;
}
/**
* 添加动作及其模型路径
* @param action 动作类型
* @param model_path 模型路径
*/
virtual void addAction(PetActionType action, const std::string& model_path) {
action_model_map[action] = model_path;
}
/**
* 添加动作音频映射
* @param action 动作类型
* @param audio_path 音频文件路径
*/
virtual void addActionAudio(PetActionType action, const std::string& audio_path) {
action_audio_map[action] = audio_path;
}
/**
* 获取动作模型映射表
* @return 动作到模型路径的映射表
*/
virtual const std::unordered_map<PetActionType, std::string>& getActionModelMap() const {
return action_model_map;
}
/**
* 获取动作音频映射表
* @return 动作到音频路径的映射表
*/
virtual const std::unordered_map<PetActionType, std::string>& getActionAudioMap() const {
return action_audio_map;
}
/**
* 获取特定动作的模型路径
* @param action 动作类型
* @return 模型路径,如果不存在则返回空字符串
*/
virtual std::string getActionModelPath(PetActionType action) const {
auto it = action_model_map.find(action);
if (it != action_model_map.end()) {
return it->second;
}
return "";
}
/**
* 获取特定动作的音频路径
* @param action 动作类型
* @return 音频路径,如果不存在则返回空字符串
*/
virtual std::string getActionAudioPath(PetActionType action) const {
auto it = action_audio_map.find(action);
if (it != action_audio_map.end()) {
return it->second;
}
return "";
}
/**
* 执行指定动作
* @param action 要执行的动作类型
* @return 是否成功执行
*/
virtual bool performAction(PetActionType action) {
if (action_model_map.find(action) != action_model_map.end()) {
current_action = action;
return true;
}
return false;
}
/**
* 获取当前动作
* @return 当前动作类型
*/
virtual PetActionType getCurrentActionType() const {
return current_action;
}
/**
* 获取当前动作的模型路径
* @return 当前动作模型路径
*/
virtual std::string getCurrentActionModelPath() const {
return getActionModelPath(current_action);
}
/**
* 获取所有可用动作
* @return 所有动作的列表
*/
virtual std::list<PetActionType> getAllActions() const {
std::list<PetActionType> actions;
for (const auto& action : action_model_map) {
actions.push_back(action.first);
}
return actions;
}
/**
* 获取动作的描述信息
* @param action 动作类型
* @return 动作描述
*/
virtual std::string getActionDescription(PetActionType action) const {
switch (action) {
#define X(name, desc) case PetActionType::name: return desc;
PET_ACTION_TYPES
#undef X
default: return "未知动作";
}
}
/**
* 获取当前动作的描述信息
* @return 当前动作描述
*/
virtual std::string getCurrentActionDescription() const {
return getActionDescription(current_action);
}
/**
* 移除动作
* @param action 要移除的动作类型
*/
virtual void removeAction(PetActionType action) {
action_model_map.erase(action);
action_audio_map.erase(action);
if (current_action == action) {
current_action = PetActionType::PET_ACTION_SLEEP; // 默认动作
}
}
public:
///<! 动作到模型路径的映射表
std::unordered_map<PetActionType, std::string> action_model_map;
///<! 动作到音频路径的映射表
std::unordered_map<PetActionType, std::string> action_audio_map;
///<! 当前动作
PetActionType current_action = PetActionType::PET_ACTION_SLEEP; // 默认动作
};
// 数值封装
struct Vitals {
int hp = 100; // 0-100 /// 生命值
int intimacy = 0; // 0-100 /// 亲密度
// 观察者接口
class PetObserver {
public:
virtual ~PetObserver() = default;
// 宠物动作
virtual void onPetAction(PetActionType action) = 0;
// 宠物阶段
virtual void onPetStageChange(PetStageType oldStage, PetStageType newStage) = 0;
};
// 身份设定
struct Persona {
std::string systemPrompt; /// 系统提示
std::string greet; /// 默认欢迎语
// …可扩展
};
// 动物元数据(只读)
struct AnimalManifest {
std::string id; // "snow_leopard" /// ID
std::string displayName; /// 显示名称
Persona persona; /// 身份设定
std::unordered_map<Stage, std::string> modelPath; /// 阶段→模型
std::unordered_map<Emotion, std::string> audioPath; /// 阶段→音频
std::string version; /// 版本
// 主题接口
class PetSubject {
public:
virtual ~PetSubject() = default;
// 添加观察者
virtual void addObserver(std::shared_ptr<PetObserver> observer) = 0;
// 移除观察者
virtual void removeObserver(std::shared_ptr<PetObserver> observer) = 0;
// 通知动作
virtual void notifyAction(PetActionType action) = 0;
// 通知阶段
virtual void notifyStageChange(PetStageType oldStage, PetStageType newStage) = 0;
};
+4
View File
@@ -0,0 +1,4 @@
//
// Created by misaki on 2025/9/14.
//
#include "PetObserver.h"
+209
View File
@@ -0,0 +1,209 @@
//
// Created by misaki on 2025/9/14.
//
#pragma once
#include "PetInterface.h"
// 宠物音频播放观察者类,继承自宠物观察者
class PetAudioStrategy : public PetObserver, public std::enable_shared_from_this<PetAudioStrategy> {
public:
using AudioCallback = std::function<void(const std::string&)>;
/**
* 构造时候就将“事件→音频”两张表填好
* @param actionAudios 动作→音频
* @param stageAudios 阶段→音频
*/
explicit PetAudioStrategy(
std::unordered_map<PetActionType, std::string> actionAudios = {},
std::unordered_map<PetStageType, std::string> stageAudios = {})
: action_audio(std::move(actionAudios)),
stage_audio(std::move(stageAudios))
{}
/**
* 构造函数,需要传入阶段策略和动作策略,从其中获取所需的类型→模型路径映射
* @param stageStrategy 阶段策略
* @param actionStrategy 动作策略
*/
explicit PetAudioStrategy(
const std::shared_ptr<PetStageStrategy>& stageStrategy,
const std::shared_ptr<PetActionStrategy>& actionStrategy) {
stage_audio = stageStrategy->getStageAudioMap(); // 获取阶段→音频map
action_audio = actionStrategy->getActionAudioMap(); // 获取动作→音频map
}
~PetAudioStrategy() override {
// 自动取消注册
if (auto subject = pet_subject.lock()) {
subject->removeObserver(shared_from_this());
}
}
/**
* 设置音频播放回调函数,注入真正的“播放函数”,由外部(主程序 / 平台层)提供
* @param callback 音频播放回调,接受音频文件路径
*/
virtual void setAudioCallback(AudioCallback callback) {
audio_callback = std::move(callback);
}
/**
* 注册到 Subject(一般由 Pet 基类实现)
* @param subject 主题
*/
void subscribe(std::shared_ptr<PetSubject> subject)
{
if (auto old = pet_subject.lock()) {
old->removeObserver(shared_from_this());
}
pet_subject = subject;
if (subject) {
subject->addObserver(shared_from_this());
}
}
/**
* 宠物动作时触发[Observer 接口实现]
* @param action 动作
*/
void onPetAction(const PetActionType action) override
{
if (!audio_callback) return;
auto it = action_audio.find(action);
if (it != action_audio.end()) {
audio_callback(it->second); // 播放对应音频
}
}
/**
* 宠物阶段时触发[Observer 接口实现]
* @param oldStage 旧阶段
* @param newStage 新阶段
*/
void onPetStageChange(PetStageType oldStage, const PetStageType newStage) override
{
if (!audio_callback) return;
auto it = stage_audio.find(newStage); // 注意:播“新阶段”的音频
if (it != stage_audio.end()) {
audio_callback(it->second);
}
}
private:
///<! 宠物音频播放回调
AudioCallback audio_callback;
///<! 宠物主题
std::weak_ptr<PetSubject> pet_subject;
///<! 两张映射表,保证“同一事件只对应一种音频”
std::unordered_map<PetActionType, std::string> action_audio;
std::unordered_map<PetStageType, std::string> stage_audio;
};
// 渲染器观察者类,继承自PetObserver
class PetRendererStrategy : public PetObserver, public std::enable_shared_from_this<PetRendererStrategy> {
public:
using RenderCallback = std::function<void(const std::string&)>;
/**
* 构造函数,可以传入动作到模型路径和阶段到模型路径的映射表
* @param actionModels 动作→模型路径映射
* @param stageModels 阶段→模型路径映射
*/
explicit PetRendererStrategy(
std::unordered_map<PetActionType, std::string> actionModels = {},
std::unordered_map<PetStageType, std::string> stageModels = {})
: action_models(std::move(actionModels)),
stage_models(std::move(stageModels))
{}
/**
* 构造函数,需要传入阶段策略和动作策略,从其中获取所需的类型→模型路径映射
* @param stageStrategy 阶段策略
* @param actionStrategy 动作策略
*/
explicit PetRendererStrategy(
const std::shared_ptr<PetStageStrategy>& stageStrategy,
const std::shared_ptr<PetActionStrategy>& actionStrategy) {
stage_models = stageStrategy->getStageModelMap();
action_models = actionStrategy->getActionModelMap();
}
~PetRendererStrategy() override {
// 自动取消注册
if (auto subject = pet_subject.lock()) {
subject->removeObserver(shared_from_this());
}
}
/**
* 设置渲染回调函数,注入真正的"渲染函数",由外部(主程序/平台层)提供
* @param callback 渲染回调,接受模型文件路径
*/
virtual void setRenderCallback(RenderCallback callback) {
render_callback = std::move(callback);
}
/**
* 注册到Subject(一般由Pet基类实现)
* @param subject 主题
*/
void subscribe(std::shared_ptr<PetSubject> subject) {
if (auto old = pet_subject.lock()) {
old->removeObserver(shared_from_this());
}
pet_subject = subject;
if (subject) {
subject->addObserver(shared_from_this());
}
}
/**
* 宠物动作时触发[Observer接口实现]
* @param action 动作类型
*/
void onPetAction(PetActionType action) override {
if (!render_callback) return;
auto it = action_models.find(action);
if (it != action_models.end()) {
render_callback(it->second); // 渲染对应动作模型
}
}
/**
* 宠物阶段变化时触发[Observer接口实现]
* @param oldStage 旧阶段
* @param newStage 新阶段
*/
void onPetStageChange(PetStageType oldStage, PetStageType newStage) override {
if (!render_callback) return;
auto it = stage_models.find(newStage); // 注意:渲染"新阶段"的模型
if (it != stage_models.end()) {
render_callback(it->second);
}
}
/**
* 添加动作到模型的映射
* @param action 动作类型
* @param modelPath 模型路径
*/
virtual void addActionModel(PetActionType action, const std::string& modelPath) {
action_models[action] = modelPath;
}
/**
* 添加阶段到模型的映射
* @param stage 阶段类型
* @param modelPath 模型路径
*/
virtual void addStageModel(PetStageType stage, const std::string& modelPath) {
stage_models[stage] = modelPath;
}
/**
* 移除动作模型映射
* @param action 动作类型
*/
virtual void removeActionModel(PetActionType action) {
action_models.erase(action);
}
/**
* 移除阶段模型映射
* @param stage 阶段类型
*/
virtual void removeStageModel(PetStageType stage) {
stage_models.erase(stage);
}
private:
///<! 渲染回调函数
RenderCallback render_callback;
///<! 宠物主题
std::weak_ptr<PetSubject> pet_subject;
///<! 动作到模型路径的映射表
std::unordered_map<PetActionType, std::string> action_models;
///<! 阶段到模型路径的映射表
std::unordered_map<PetStageType, std::string> stage_models;
};
@@ -135,7 +135,7 @@ public:
/**
* @brief 打印系统内存信息
*/
static void print_sys_memory(void)
static void print_sys_memory()
{
size_t internal = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
size_t spiram = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
@@ -144,9 +144,9 @@ public:
static void stats_task(void)
static void stats_task()
{
char stats_buf[2*1024];
char stats_buf[2 * 1024]; // 存储任务列表和绑核信息,占用 2KB 栈空间,调用时需注意
/* 任务列表 + 绑核信息 */
printf("\n-------- vTaskList --------\n");
vTaskList(stats_buf);
+2
View File
@@ -30,6 +30,8 @@ idf_component_register(SRCS "Bionic_sphere.c"
# 业务代码(使用Cpp编写)
"../Bionic_Core/PetBaseClass/PetBaseClass.cpp" # 宠物基类库
"../Bionic_Core/PetBaseClass/PetInterface.cpp" # 宠物接口层
"../Bionic_Core/PetBaseClass/PetObserver.cpp" # 宠物观察者库
"../Bionic_Core/PetBaseClass/PetDao.cpp" # 宠物数据访问层
"../Bionic_Core/OTAClass/OTAClass.cpp" # OTA类库
"../Bionic_Core/CommClass/CommClass.cpp" # 通信类库
"../Bionic_Core/ToolsClass/ToolsClass.cpp" # 工具类库
+7
View File
@@ -217,3 +217,10 @@
- [x] 3. 增加了一些CPU资源占用的日志打印函数,运行在主线程当中
- [x] 4. 完善了底层通信类的封装,基于websocket,尚未测试
#### Day14 2025.9.15
##### 主要目标:完成具体业务开发&各种优化
实际完成任务:
- [x] 1. 历时两天,完整且完美的设计了宠物类,使用到了多种设计模式,完成了低耦合,高内聚的完美代码,测试也完美通过。
- [x] 2. 顺便完善了底层通信类的封装,基于websocket,基准测试通过,但存在一点很小的线程bug,似乎是来自于esp32 idf底层的问题,待解决