Files
SergObsidian/PERSONAL PROJECTS/PS1 DOCS/PSX code - modules load and exec.md
2025-04-18 23:10:56 +05:00

14 KiB
Raw Permalink Blame History

Вот доработанная версия с общими переменными между основной программой и модулями. Мы создадим единую структуру 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. Механика общих переменных

  1. Основная программа:

    • Создаёт глобальную структуру GameState.
    • Передаёт указатель на неё в модули через header->entry(&game_state).
  2. Модули:

    • Читают и изменяют GameState через полученный указатель.
    • Могут проверять:
      if (state->is_paused) { ... }
      
    • Могут изменять:
      state->lives--;
      
  3. Синхронизация:

    • Все изменения мгновенно видны в основном модуле и других модулях.

7. Пример работы

  1. Основная программа:
    • Устанавливает lives = 3, score = 0.
  2. Модуль MDEC:
    • Уменьшает lives до 2, если игрок ошибся.
  3. Модуль GPU:
    • Отображает текущие lives и score на экране.
  4. Основная программа:
    • Видит изменения и может реагировать (например, завершить игру при 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. Как это работает в памяти?

  1. Основная программа:

    • Занимает нижнюю часть RAM (например, 0x80010000).
    • Не знает о существовании PsyQ-функций.
  2. Модули:

    • Загружаются в фиксированный адрес (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. Оптимизация

Если модули используют одни и те же библиотеки, можно:

  1. Вынести общие библиотеки в отдельный загружаемый модуль.
  2. Передавать указатели на функции (как в предыдущих примерах).

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.

Спасибо за замечание! Теперь архитектура стала ещё понятнее 😊.