samedi 28 janvier 2017

SDL2 / C++ std::vector and std::array (or c-style array) behaves differently when accessing elements

I'm attempting to create a replica of space invaders using SDL2 and C++, and I've encountered a very weird problem when attempting to put my alien invaders in a vector and render them in the main loop. This is my code:

//sdlUtilsLib.hpp
class Sprite {
  public:
    Sprite(SDL_Texture* texture);
    Sprite(const Sprite& other);
    ~Sprite();

    Sprite& operator=(const Sprite&) = default;

    void setPosition(int x, int y);
    void move(int x, int y);

    int getPositionX() const;
    int getPositionY() const;

    void draw(SDL_Renderer* renderer) const;
  private:
    SDL_Texture* m_texture;
    SDL_Rect m_targetArea;
};

class WindowManager {
  public:
    WindowManager() = delete;
    WindowManager(int width, int height);
    ~WindowManager();

    bool update();

    Sprite createSprite(const std::string& filename);

    void drawSprite(const Sprite& sprite);

    SDL_Renderer* m_renderer;
  private:
    SDL_Window* m_window;
};

And the relevant parts of the definitions:

//sdlUtilsLib.cpp

Sprite::Sprite(SDL_Texture* texture)
: m_texture(texture)
{
    m_targetArea.x = 0;
    m_targetArea.y = 0;
    SDL_QueryTexture(texture, NULL, NULL, &(m_targetArea.w), &(m_targetArea.h));
}

Sprite::Sprite(const Sprite& other)
: m_texture(other.m_texture)
{
    m_targetArea.x = other.m_targetArea.x;
    m_targetArea.y = other.m_targetArea.y;
    m_targetArea.w = other.m_targetArea.w;
    m_targetArea.h = other.m_targetArea.h;
}

Sprite::~Sprite() {
    SDL_DestroyTexture(m_texture);
}

void Sprite::draw(SDL_Renderer* renderer) const {
    SDL_RenderCopy(renderer, m_texture, NULL, &m_targetArea);
}

// WindowManager
WindowManager::WindowManager(int width, int height) {
    if(SDL_Init(SDL_INIT_EVERYTHING) < 0) {
        SDL_Log("SDL failed the initialization: %s\n", SDL_GetError());
    }

    //Create window
    m_window = SDL_CreateWindow("Space Invaders", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height,
                                          SDL_WINDOW_SHOWN);
    if(m_window == NULL) {
        SDL_Log("Window could not be created! SDL_Error: %s\n", SDL_GetError());
    }

    //Create renderer for window
    m_renderer = SDL_CreateRenderer(m_window, -1, SDL_RENDERER_ACCELERATED);
    if(m_renderer == NULL) {
        SDL_Log("Renderer could not be created! SDL Error: %s\n", SDL_GetError());
    }

    //Initialize renderer color
    SDL_SetRenderDrawColor(m_renderer, 0xFF, 0xFF, 0xFF, 0xFF);

}

WindowManager::~WindowManager() {
    SDL_DestroyRenderer(m_renderer);
    SDL_DestroyWindow(m_window);
    SDL_Quit();
}


bool WindowManager::update() {
    SDL_PumpEvents();
    SDL_RenderPresent(m_renderer);
    SDL_RenderClear(m_renderer);
    return true;
}

Sprite WindowManager::createSprite(const std::string& filename) {
    SDL_Surface* surface = SDL_LoadBMP(filename.data());
    if(surface == NULL) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to load image %s! SDL_image Error: %s\n",
                     filename, SDL_GetError());
        return NULL;
    }

    SDL_Texture* texture = SDL_CreateTextureFromSurface(m_renderer, surface);
    SDL_FreeSurface(surface);
    if(texture == NULL) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Unable to create texture from %s! SDL Error: %s\n",
                     filename, SDL_GetError());
        return NULL;
    }
    return Sprite(texture);
}

void WindowManager::drawSprite(const Sprite& sprite) {
    sprite.draw(m_renderer);
}

And this is the main.cpp file:

int main(int argc, char* argv[]) {
    WindowManager windowManager { 800, 640 };
    bool isRunning { true };
    constexpr std::chrono::duration<int64_t, std::nano> nsPerUpdate { std::chrono::nanoseconds(16666667) };
    Clock timer;
    Sprite sprite = windowManager.createSprite("/home/jooster/Programming/space_invaders/res/enemy_0.bmp");
    Sprite al(sprite);
    al.move(15, 15);
    //std::array<Sprite, 2> aliens = {Sprite(sprite), al};
    std::vector<Sprite> aliens = {Sprite(sprite), al};
    while(isRunning) {
        const float deltaTime = timer.restartAsSeconds().count();
        /*for(Sprite& alien : aliens) {
            alien.move(1, 1);
            std::cout << "Drawing alien " << alien.getPositionX() << ":" << alien.getPositionY() << '\n';
            windowManager.drawSprite(alien);
        }*/
        for(int i=0; i<2; ++i) {
            windowManager.drawSprite(aliens[i]);
        }
        //Clear and draw the window.
        windowManager.update();

        if(Input::QUIT) { isRunning = false; }
        const auto sleepTime = nsPerUpdate - timer.get();
        //std::cout << sleepTime.count() << '\n';
        if(sleepTime.count() < 0) {
            std::cout << "Too long sleep" << '\n';
            continue;
        }
        std::this_thread::sleep_for(sleepTime);
    }
    return 0;
}

Either of the two for-loops with calls to WindowManager::drawSprite works for the std::array (or with a C-style array), but if I just switch from the std::array to the std:vector, the rendering stops working, in either loop. Interestingly enough, the calls to Sprite::move and std::cout in the range-based for loop works for the std::vector, and outputs exactly what I would expect, at every iteration, it's just the draw-call that doesn't work.

From what I can see on cppreference.com, operator[] should return a reference or a const reference for both the array and the vector, i.e. they should behave identically in this simple case. What am I missing?

Thanks for any help :)

Aucun commentaire:

Enregistrer un commentaire