14 KiB
Вот доработанная версия с общими переменными между основной программой и модулями. Мы создадим единую структуру GameState, которая будет передаваться в каждый модуль.
1. Общий заголовок (SHARED.H)
// Структура для общих переменных игры
typedef struct {
int lives; // Количество жизней
int level; // Текущий уровень
bool is_paused; // Состояние паузы
uint32_t score; // Счёт игрока
} GameState;
// Заголовок модуля
typedef void (*ModuleEntry)(GameState*); // Тип функции-входа
typedef struct {
uint32_t magic; // 0x55AA1234
ModuleEntry entry; // Функция входа
uint32_t size; // Размер модуля
} ModuleHeader;
2. Основная программа (MAIN.C)
#include <libetc.h>
#include <libcd.h>
#include "SHARED.H"
#define MODULE_ADDR 0x80030000 // Адрес загрузки модулей
GameState game_state = {
.lives = 3,
.level = 1,
.is_paused = false,
.score = 0
};
void load_and_run_module(int sector, int size) {
CdRead(sector, (uint32_t*)MODULE_ADDR, size);
CdReadSync(0);
ModuleHeader *header = (ModuleHeader*)MODULE_ADDR;
if (header->magic == 0x55AA1234) {
header->entry(&game_state); // Передаём указатель на game_state
}
}
int main() {
ResetGraph(0);
CdInit();
FntLoad(960, 256);
FntOpen(16, 16, 256, 224, 0, 512);
while (1) {
// Обновляем общие переменные
game_state.score += 10;
FntPrint("Main: Lives=%d, Score=%d\n", game_state.lives, game_state.score);
// Загружаем и выполняем модули
load_and_run_module(MODULE_MDEC_SECTOR, MODULE_MDEC_SIZE);
load_and_run_module(MODULE_GPU_SECTOR, MODULE_GPU_SIZE);
VSync(0);
}
}
3. Модуль для MDEC (MODULE_MDEC.C)
#include <libmdec.h>
#include "SHARED.H"
void module_entry(GameState *state) {
// Читаем и изменяем общие переменные
if (state->lives > 0) {
state->lives--; // Уменьшаем жизни
FntPrint("MDEC: Lives left=%d\n", state->lives);
}
// Декодируем видео только если игра не на паузе
if (!state->is_paused) {
uint8_t data[1024];
uint16_t buffer[256*256];
MDEC_DecodeFrame(data, buffer, MDEC_MODE_RGB24);
}
}
ModuleHeader __header__ __attribute__((section(".header"))) = {
.magic = 0x55AA1234,
.entry = module_entry,
.size = 0
};
4. Модуль для GPU (MODULE_GPU.C)
#include <libgpu.h>
#include "SHARED.H"
void module_entry(GameState *state) {
// Проверяем глобальный статус
if (state->is_paused) {
FntPrint("GPU: Game is paused!\n");
return;
}
// Рисуем HUD с общими переменными
char hud_text[32];
sprintf(hud_text, "LIVES: %d SCORE: %d", state->lives, state->score);
FntPrint(hud_text);
// Пример работы с GPU
RECT rect = {10, 10, 100, 100};
uint16_t color = state->lives > 1 ? 0xFF00 : 0xFF0000; // Красный если 1 жизнь
DrawPrim(&rect);
}
ModuleHeader __header__ __attribute__((section(".header"))) = {
.magic = 0x55AA1234,
.entry = module_entry,
.size = 0
};
5. Сборка
# Компиляция модулей с их библиотеками
ccpsx MODULE_MDEC.C -o MODULE_MDEC.BIN -lpsxmdec
ccpsx MODULE_GPU.C -o MODULE_GPU.BIN -lpsxgpu
# Основная программа (без лишних библиотек)
ccpsx MAIN.C -o MAIN.EXE
6. Механика общих переменных
-
Основная программа:
- Создаёт глобальную структуру
GameState. - Передаёт указатель на неё в модули через
header->entry(&game_state).
- Создаёт глобальную структуру
-
Модули:
- Читают и изменяют
GameStateчерез полученный указатель. - Могут проверять:
if (state->is_paused) { ... } - Могут изменять:
state->lives--;
- Читают и изменяют
-
Синхронизация:
- Все изменения мгновенно видны в основном модуле и других модулях.
7. Пример работы
- Основная программа:
- Устанавливает
lives = 3,score = 0.
- Устанавливает
- Модуль MDEC:
- Уменьшает
livesдо 2, если игрок ошибся.
- Уменьшает
- Модуль GPU:
- Отображает текущие
livesиscoreна экране.
- Отображает текущие
- Основная программа:
- Видит изменения и может реагировать (например, завершить игру при
lives == 0).
- Видит изменения и может реагировать (например, завершить игру при
8. Важные нюансы
- Безопасность: Модули могут перезаписать любые данные в
GameState. Добавьте проверки:if (state->lives < 0) state->lives = 0; // Защита от отрицательных значений - Производительность: Частый доступ к общим переменным может замедлить работу. Оптимизируйте:
int local_lives = state->lives; // Кэширование в локальную переменную - Конфликты: Если модули работают с одними данными, добавьте флаги блокировки.
Теперь модули могут полноценно взаимодействовать с основной программой, проверяя и изменяя глобальное состояние игры! 🎮
Да, вы правильно поняли! В этой архитектуре основная программа и модули компилируются по-разному:
1. Основная программа (MAIN.EXE)
- Не требует явного указания библиотек PsyQ (если не использует их напрямую).
- Её задача — только загружать модули и управлять общим состоянием (
GameState). - Пример компиляции:
ccpsx MAIN.C -o MAIN.EXE # Без -lpsxetc, -lpsxgpu и т.д.
2. Дополнительные модули (MODULE_*.BIN)
- Должны включать нужные библиотеки PsyQ, так как:
- Каждый модуль — самодостаточный исполняемый код.
- PsyQ не загружается глобально для модулей (они работают в изолированном контексте).
- Пример компиляции:
# Модуль для GPU ccpsx MODULE_GPU.C -o MODULE_GPU.BIN -lpsxgpu -lpsxgte # Модуль для MDEC ccpsx MODULE_MDEC.C -o MODULE_MDEC.BIN -lpsxmdec
3. Почему такая разница?
| Компонент | Библиотеки PsyQ | Причина |
|---|---|---|
| Основная программа | Не нужны | Только загружает модули и передаёт GameState. Не использует GPU/MDEC. |
| Модули | Обязательны | Каждый модуль — независимый код, требующий свои библиотеки. |
4. Как это работает в памяти?
-
Основная программа:
- Занимает нижнюю часть RAM (например,
0x80010000). - Не знает о существовании PsyQ-функций.
- Занимает нижнюю часть RAM (например,
-
Модули:
- Загружаются в фиксированный адрес (
0x80030000). - Содержат свои копии библиотек (например,
libgpu). - Получают доступ к
GameStateчерез указатель.
- Загружаются в фиксированный адрес (
5. Пример: вызов функций
-
В модуле
MODULE_GPU.C:#include <libgpu.h> // Библиотека внутри модуля void module_entry(GameState *state) { // Использует свою копию LoadImage RECT rect = {0, 0, 256, 256}; LoadImage(&rect, (uint32_t*)texture); // Функция из libgpu } -
В основной программе (
MAIN.C):// Нет #include <libgpu.h>! void load_module() { // Только загрузка бинарника CdRead(sector, (uint32_t*)0x80030000, size); }
6. Ограничения
- Дублирование кода: Каждый модуль содержит свои библиотеки → увеличивает размер.
- Нет глобального состояния: Если
libgpuхранит внутренние переменные, они будут разными для каждого модуля. - Только последовательное выполнение: Модули не могут работать одновременно (перезаписывают один регион памяти).
7. Оптимизация
Если модули используют одни и те же библиотеки, можно:
- Вынести общие библиотеки в отдельный загружаемый модуль.
- Передавать указатели на функции (как в предыдущих примерах).
8. Итог
- Основная программа — минималистична, без зависимостей.
- Модули — самодостаточные бинарники со своими библиотеками.
- Общение через общую структуру
GameState.
Это архитектурно напоминает плагины в современных движках, где каждый плагин собирается со своими зависимостями.
Да, вы абсолютно правы! Моя ошибка — основная программа действительно использует libcd.h (и другие библиотеки PsyQ для базовых операций), но ключевое различие в следующем:
1. Уточнение архитектуры
| Компонент | Библиотеки PsyQ | Почему? |
|---|---|---|
| Основная программа | libcd.h, libetc.h, libgte.h |
Только для загрузки модулей, управления памятью и отладки (FntPrint). Не использует GPU/MDEC/SPU. |
| Модули | Свои специфичные библиотеки (libgpu, libmdec) |
Каждый модуль — самостоятельный код, требующий все свои зависимости. |
2. Исправленный пример компиляции
Основная программа (MAIN.C)
# Нужны только базовые библиотеки для загрузки модулей
ccpsx MAIN.C -o MAIN.EXE -lpsxcd -lpsxetc -lpsxgte
Модули
# GPU-модуль (со своим libgpu)
ccpsx MODULE_GPU.C -o MODULE_GPU.BIN -lpsxgpu -lpsxgte
# MDEC-модуль (со своим libmdec)
ccpsx MODULE_MDEC.C -o MODULE_MDEC.BIN -lpsxmdec
3. Почему основная программа не включает libgpu/libmdec?
- Она не вызывает функции вроде
LoadImage()илиMDEC_Decode(). - Её задача — лишь:
- Чтение секторов с диска (
libcd). - Вывод текста (
libetc). - Передача управления модулям.
- Чтение секторов с диска (
4. Аналогия с операционной системой
- Основная программа — как ядро ОС, которое:
- Умеет загружать драйверы (модули).
- Не содержит код для работы с графикой/звуком.
- Модули — как драйверы, которые:
- Содержат весь специфичный код (например, для GPU).
- Получают доступ к «ядру» через общие структуры (
GameState).
5. Полный список зависимостей
Для MAIN.C:
#include <libcd.h> // CdRead(), CdInit()
#include <libetc.h> // FntPrint(), VSync()
#include <libgte.h> // Базовая математика (если используется)
Для MODULE_GPU.C:
#include <libgpu.h> // LoadImage(), DrawPrim()
#include <libetc.h> // FntPrint() (если нужно)
6. Итог
- Основная программа использует минимальный набор PsyQ (
libcd,libetc). - Модули включают все необходимые им библиотеки (
libgpu,libmdecи т.д.). - Общение между ними происходит через общую структуру
GameState.
Спасибо за замечание! Теперь архитектура стала ещё понятнее 😊.