Repository navigation
Replace the default constructor of sf::Text with one that takes an sf::Font #2194
Description
Activity
The compilation errors occur in the examples in island/Island.cpp, joystick/Joystick.cpp, shader/Shader.cpp, and tennis/Tennis.cpp. Some of those errors are trivial to fix but the errors in Joystick.cpp are particularly hard since many many text objects are being created in a map and lazily initialized. If we can find a good way to refactor late-initialization-heavy code to this new, stricter API that bodes well for this as good API design.
A half measure worth considering is to assert within
sf::Text::drawthat you shall not draw without a valid font.void Text::draw(RenderTarget& target, const RenderStates& states) const { if (m_font) { ensureGeometryUpdate(); RenderStates statesCopy(states); statesCopy.transform *= getTransform(); statesCopy.texture = &m_font->getTexture(m_characterSize); // Only draw the outline if there is something to draw if (m_outlineThickness != 0) target.draw(m_outlineVertices, statesCopy); target.draw(m_vertices, statesCopy); } }
becomes
void Text::draw(RenderTarget& target, const RenderStates& states) const { assert(m_font); ensureGeometryUpdate(); RenderStates statesCopy(states); statesCopy.transform *= getTransform(); statesCopy.texture = &m_font->getTexture(m_characterSize); // Only draw the outline if there is something to draw if (m_outlineThickness != 0) target.draw(m_outlineVertices, statesCopy); target.draw(m_vertices, statesCopy); }
Reacted by therocodeI think I have it working. The hardest part was the changes I had to make to the Joystick example but once those were in place it was easy to remove that
sf::Textconstructor. Hope this helps the team judge whether this is an API change we actually want.Reacted by therocodeA case we need to think about:
sf::Text text(font); addText(std::move(text)); // give ownership away // now `text` is like a "default-constructed" instance -- what are its semantics?
Also,
sf::Fontrelates tosf::Textthe same way thatsf::Texturerelates tosf::Sprite, orsf::SoundBuffertosf::Sound. What do you think about those resources?A case we need to think about:
sf::Text text(font); addText(std::move(text)); // give ownership away // now `text` is like a "default-constructed" instance -- what are its semantics?
Also,
sf::Fontrelates tosf::Textthe same way thatsf::Texturerelates tosf::Sprite, orsf::SoundBuffertosf::Sound. What do you think about those resources?- How
sf::Spritecurrently works is that rendering without a texture produces a white square if I'm not mistaken? This could be considered valid and well defined semantics of thesf::Spritetype, so nothing would need to change with this one. However, if we do want to consider this usage ofsf::Spritean error, and that the proper way to render a while square issf::RectangleShape, then I would be all for making the corresponding change onsf::Spritetoo. - Agree that
sf::Soundshould have the same change - I don't think it makes much sense to ever play a sound with no soundbuffer, nor do I see the value in considering this a valid state. - As for the code snippet, I believe
sf::Textshould work likesf::Spritehandles textures - pass-by-reference when setting, but interally represent it as a non-owning raw pointer. That way,addText(std::move(text))works without surprises where the added text would use the same font as the moved text was set to.
Reacted by Bambo- How
I've been maintaining my implementation of this issue here. If the SFML teams likes the idea I'll make a PR out of it.
Reacted by therocodenow
textis like a "default-constructed" instance -- what are its semantics?Same as any Standard library container -- i.e. "valid but unspecified". In other words, don't touch it again :)
Reacted by Chris ThrasherAny more thoughts on what I've implemented here?
- linked a pull request that will close this issueRemove bugprone `sf::Text` constructor #2486
on Apr 3, 2023 - added a commit that references this issue
on May 4, 2023
Inspired by a case of a user asking the Discord server for help in why their text was not displaying despite them using
setStringsetStylesetFillColorsetCharacterSize. They lackedsetFont.This suggests a breaking change, so I propose this be added to SFML 3. The change is trivial so doesn't take much work, but provides safety and a clearer API for the user.
Proposal
Change the default constructor of
sf::Textfrom:Text()Into:
Text(const Font& font, unsigned int charachterSize = 30)The reasoning is that allowing the user to create an
sf::Textwith no font is not useful, and opens up for confusion and bugs since rendering ansf::Textwith no font will just display nothing. Especially with SFML's history of providing a default font, and also with many beginners not realising that a font needs to be set, this opens up a pitfall.By removing the ability to even create an
sf::Textin this state removes that class of bugs and goes along good API practices of making APIs harder to use without compromising their usefulness.Drawback
The only obvious rebuttal I see to this are things like:
sf::Textin anstd::vectorsf::Texts in my engine that are going to reference not-yet-loadedsf::Fonts and I'll usesetFontwhen the time comesThese all boil down to "Sometimes it is useful to be able to construct uninitialised values for later use". This is true, and I'd argue the proper modern C++ way of doing this would be through
std::optional<sf::Text>orstd::unique_ptr<sf::Text>or similar, which to some might seem more verbose but this is a good type of verbose as it:sf::Textmight be uninitialised (currently ALL usages ofsf::Textcould be, which is far from the default case)sf::Textopaquely might be not ready for use.With this in mind, I personally don't see any further drawbacks with this change.
Compile errors
@ChrisThrasher did a quick test to build SFML with the default constructor removed, and there was an error in the joystick code where it seems like it uses an uninitialised instane of
sf::Text. This is hopefully trivially fixed by changing it tostd::optional<sf::Text>.Summary
All in all, I think this would be a great change since it adds seatbelts against an issue that might otherwise trip up unexpecting users. It brings the API closer to being safe and modern, utilising the type system for bulletproof eradication of the described bugs. The change is seemingly easy to apply and I don't see any unaddressed drawbacks.
Interested in hearing the thoughts from the regular SFML maintainers in case I've missed something.