Skip to content

Commit

Permalink
Add Render_sprite::can_paint() and throw Late_paint_error...
Browse files Browse the repository at this point in the history
if the client tries to paint but cannot (because the Render_sprite has already been
rendered).
  • Loading branch information
tov committed Mar 5, 2021
1 parent 1d6c07e commit e23d839
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 49 deletions.
2 changes: 1 addition & 1 deletion include/ge211_color.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ private:
uint8_t alpha_;

friend Text_sprite;
friend internal::Render_sprite;
friend ::ge211::internal::Render_sprite;

SDL_Color to_sdl_() const NOEXCEPT;
uint32_t to_sdl_(const SDL_PixelFormat*) const NOEXCEPT;
Expand Down
22 changes: 16 additions & 6 deletions include/ge211_error.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ private:
std::string action_;
};

/// Thrown by member functions of @ref internal::Render_sprite when
/// the sprite has already been rendered to the screen and can no longer
/// be modified.
class Late_paint_error : public Client_logic_error
{
explicit Late_paint_error(char const* who);

// Throwers
friend ::ge211::internal::Render_sprite;
};

/// Indicates that an error was encountered by the game engine or
/// in the client's environment.
/// This could indicate a problem with your video driver,
Expand All @@ -96,7 +107,6 @@ class Ge211_logic_error : public Environment_error
explicit Ge211_logic_error(const std::string& message);

/// Throwers
friend internal::Render_sprite;
friend Mixer;
friend Text_sprite;
};
Expand All @@ -118,7 +128,7 @@ class Host_error : public Environment_error
/// Throwers
friend Text_sprite;
friend Window;
friend internal::Render_sprite;
friend ::ge211::internal::Render_sprite;
friend class detail::Renderer;
friend class detail::Texture;
};
Expand Down Expand Up @@ -188,8 +198,8 @@ enum class Log_level
fatal,
};

// Right now a Logger just keeps track of the current log
// level. There's only one Logger (Singleton Pattern).
/// Right now a Logger just keeps track of the current log
/// level. There's only one Logger (Singleton Pattern).
class Logger
{
public:
Expand All @@ -208,8 +218,8 @@ private:
Log_level level_ = Log_level::warn;
};

// A Log_message accumulates information and then prints it all at
// once when it's about to be destroyed.
/// A Log_message accumulates information and then prints it all at
/// once when it's about to be destroyed.
class Log_message
{
public:
Expand Down
1 change: 1 addition & 0 deletions include/ge211_forward.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class Client_logic_error;
class Session_needed_error;
class Environment_error;
class Ge211_logic_error;
class Late_paint_error;
class Host_error;
class File_error;
class Font_error;
Expand Down
2 changes: 1 addition & 1 deletion include/ge211_geometry.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ struct Rect

private:
friend Circle_sprite;
friend internal::Render_sprite;
friend ::ge211::internal::Render_sprite;
friend class detail::Renderer;

/// Converts this rectangle to an internal SDL rectangle.
Expand Down
53 changes: 39 additions & 14 deletions include/ge211_sprites.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -90,18 +90,17 @@ private:
namespace internal {

/// A `Render_sprite` works by allowing its derived classes to render
/// themselves pixel-by-pixel onto an [`SDL_Surface` ☛]. Then it converts
/// themselves pixel-by-pixel onto an [`SDL_Surface`☛]. Then it converts
/// the rendered surface into a `Texture`, which it caches.
///
/// The constructor of the derived class should pass the required
/// dimensions to the `Render_sprite` constructor. Then, in its own
/// constructor, use @ref fill_surface(), @ref fill_rectangle(), and
/// @ref set_pixel() to paint the desired sprite image to the
/// surface. Or for direct access to the underlying [`SDL_Surface` ☛], use
/// surface. Or for direct access to the underlying [`SDL_Surface`☛], use
/// @ref raw_surface().
///
/// [`SDL_Surface` ☛]:
/// <https://wiki.libsdl.org/SDL_Surface>
/// [`SDL_Surface`☛]: https://wiki.libsdl.org/SDL_Surface
class Render_sprite : public detail::Texture_sprite
{
protected:
Expand All @@ -111,36 +110,62 @@ protected:
/// - Both dimensions are positive.
explicit Render_sprite(Dims<int>);

/// Returns whether we can paint to this Render_sprite.
///
/// If this sprite has already been rendered to the screen then this
/// function returns `false`. When the result is `false`, then calling
/// any of @ref fill_surface(), @ref fill_rectangle(), @ref
/// set_pixel(), or @ref raw_surface() will throw an
/// @ref exceptions::Late_paint_error exception.
bool can_paint() const;

/// Fills the whole surface with the given color.
///
/// This should only be called from the derived class's constructor.
/// Typically this will only be called from a derived class's
/// constructor.
///
/// \precondition
/// Throws @ref exceptions::Late_paint_error if `!`@ref can_paint().
void fill_surface(Color);

/// Fills the given rectangle in the given color.
///
/// This should only be called from the derived class's constructor.
/// Typically this will only be called from a derived class's
/// constructor.
///
/// \precondition
/// Throws @ref exceptions::Late_paint_error if `!`@ref can_paint().
void fill_rectangle(Rect<int>, Color);

/// Sets one pixel to the given color.
///
/// This should only be called from the derived class's constructor.
/// Typically this will only be called from a derived class's
/// constructor.
///
/// \precondition
/// Throws @ref exceptions::Late_paint_error if `!`@ref can_paint().
void set_pixel(Posn<int>, Color);

/// Gains access to the underlying [`SDL_Surface` ☛].
/// Gains access to the underlying [`SDL_Surface`☛].
///
/// This function should only be called from the derived class's
/// constructor. If this sprite has already been rendered then
/// Ge211_logic_error will be thrown. If this function returns, the
/// result will be non-null.
/// Typically this will only be called from a derived class's
/// constructor. Never returns null.
///
/// [`SDL_Surface` ☛]:
/// <https://wiki.libsdl.org/SDL_Surface>
/// \precondition
/// Throws @ref exceptions::Late_paint_error if `!`@ref can_paint().
///
/// [`SDL_Surface`☛]: https://wiki.libsdl.org/SDL_Surface
Borrowed<SDL_Surface> raw_surface();

private:
detail::Texture texture_;

detail::Texture const& get_texture_() const override;

void fill_rectangle_(Rect<int>, Color, char const* who);

Borrowed<SDL_Surface> raw_surface_(char const* who);

static detail::Uniq_SDL_Surface
create_surface_(Dims<int>);
};
Expand Down
14 changes: 14 additions & 0 deletions src/ge211_error.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,20 @@ Session_needed_error::Session_needed_error(const std::string& action)
, action_(action)
{ }

static std::string build_late_paint_message(char const* who) {
std::ostringstream oss;
oss << "\n\nERROR\n=====\n\n"
<< who
<< ": Cannot paint to a ge211::internal::Render_sprite\n"
"that has already been rendered.\n";

return oss.str();
}

Late_paint_error::Late_paint_error(char const* who)
: Client_logic_error(build_late_paint_message(who))
{ }

static std::string build_sdl_error_message(const std::string& message) {
const char* reason = take_sdl_error();

Expand Down
64 changes: 37 additions & 27 deletions src/ge211_sprites.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -63,53 +63,63 @@ void Texture_sprite::prepare(const Renderer& renderer) const

namespace internal {

Uniq_SDL_Surface Render_sprite::create_surface_(Dims<int> dimensions)
{
SDL_Surface* surface =
SDL_CreateRGBSurfaceWithFormat(0,
dimensions.width,
dimensions.height,
32,
SDL_PIXELFORMAT_RGBA32);
if (!surface)
throw Host_error{"Could not create sprite surface"};

return Uniq_SDL_Surface(surface);
}

Render_sprite::Render_sprite(Dims<int> dimensions)
: texture_{create_surface_(dimensions)}
{ }

const Texture& Render_sprite::get_texture_() const
{
return texture_;
}

Borrowed<SDL_Surface> Render_sprite::raw_surface()
{
SDL_Surface* result = texture_.raw_surface();
if (result) return result;

throw Ge211_logic_error{"Render_sprite::raw_surface: already a texture"};
return raw_surface_("Render_sprite::raw_surface");
}

void Render_sprite::fill_surface(Color color)
{
auto* surface = raw_surface();
auto* surface = raw_surface_("Render_sprite::fill_surface");
SDL_FillRect(surface, nullptr, color.to_sdl_(surface->format));
}

void Render_sprite::fill_rectangle(Rect<int> rect, Color color)
{
auto* surface = raw_surface();
fill_rectangle_(rect, color, "Render_sprite::fill_rectangle");
}

void Render_sprite::set_pixel(Posn<int> xy, Color color)
{
fill_rectangle_({xy.x, xy.y, 1, 1}, color, "Render_sprite::set_pixel");
}

const Texture& Render_sprite::get_texture_() const
{
return texture_;
}

void Render_sprite::fill_rectangle_(Rect<int> rect, Color color, char const* who)
{
auto* surface = raw_surface_(who);
SDL_Rect rect_buf = rect;
SDL_FillRect(surface, &rect_buf, color.to_sdl_(surface->format));
}

void Render_sprite::set_pixel(Posn<int> xy, Color color)
Borrowed<SDL_Surface> Render_sprite::raw_surface_(char const* who)
{
SDL_Surface* result = texture_.raw_surface();
if (result) return result;

throw Late_paint_error{who};
}

Uniq_SDL_Surface Render_sprite::create_surface_(Dims<int> dimensions)
{
fill_rectangle({xy.x, xy.y, 1, 1}, color);
SDL_Surface* surface =
SDL_CreateRGBSurfaceWithFormat(0,
dimensions.width,
dimensions.height,
32,
SDL_PIXELFORMAT_RGBA32);
if (!surface)
throw Host_error{"Could not create sprite surface"};

return Uniq_SDL_Surface(surface);
}

} // end namespace internal
Expand Down

0 comments on commit e23d839

Please sign in to comment.