pygame-community / pygame-ce

🐍🎮 pygame - Community Edition is a FOSS Python library for multimedia applications (like games). Built on top of the excellent SDL library.
https://pyga.me
766 stars 120 forks source link

Conquest of the Default Size 0x0 Surface in Pygame #2927

Closed bigwhoopgames closed 1 week ago

bigwhoopgames commented 2 weeks ago

Decree: Let it be known that the time has come to extend the dominion of Pygame-CE. I call upon the mighty developers to bestow upon pygame.Surface() the ability to be invoked without arguments, thus creating a surface of default size 0x0.

Rationale: As valiant warriors in the land of code, we have been granted the power to summon Rect, Vector2, and Vector3 from the void, each manifesting as zero-sized entities upon our command. It is a grievous inconsistency that Surface does not yet heed our call in the same manner. We must rectify this injustice and bring Surface into the fold of our zero-sized arsenal.

Evidence: Observe the current state of our dominion and the opportunity that lies before us:

PS C:\Users\Derp\Desktop\Game> python
Python 3.12.0 (tags/v3.12.0:0fb18b0, Oct  2 2023, 13:03:39) [MSC v.1935 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import pygame
pygame-ce 2.5.0 (SDL 2.30.3, Python 3.12.0)
>>> r = pygame.Rect()
>>> r
Rect(0, 0, 0, 0)
>>> v2 = pygame.Vector2()
>>> v2
Vector2(0, 0)
>>> v3 = pygame.Vector3()
>>> v3
Vector3(0, 0, 0)
>>> s = pygame.Surface()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: function missing required argument 'size' (pos 1)

Call to Arms: Rise, ye valiant developers, and implement the means for pygame.Surface() to be invoked without arguments, thus creating a surface of size 0x0. Let us bring order and consistency to our Pygame-CE kingdom, for this enhancement shall strengthen our code and simplify our endeavors. We shall conquer this inconsistency and emerge victorious, with Surface proudly standing alongside Rect, Vector2, and Vector3 in our zero-sized armory.

JiffyRob commented 2 weeks ago

While the most honorable Sir Whoop brings up a valuable point, and I am not one of the great devlopers of our order myself (though I hope to be someday), I am afraid I have a few concerns regarding our elders going in this direction:

  1. Many of our fellow knights, especially those who have little experience with our order, are not familiar with 0x0 surfaces existing in the first place. I recall many a time mentioning my use of them on ye discord and several users going, "Wait, that's allowed?". Having such unintuitive behaviour by default may confuse the young ones.
  2. The ancient tomes of pyga.me state the purpose of surfaces as "used to represent any image". Why then should they by default represent no image at all?
  3. In my experience a Rect is usually summoned from the void in order to be positioned using their wonderful top, bottom, etc in later lines of code, and a VectorX is usually summoned from the void in otder for its magnitude to be changed later. Sprite velocity is an excellent example of this. Unlike Rects and Vectors, where we know the incantations necessary to change their size and magnitude once summoned, we are not able to change the size of Surfaces once summoned, meaning a larger one would have to be summoned from scratch. It would follow that the versatility of summoning a Surface from the void would not match that of summoning the other great powers mentioned.
  4. Surfaces summoned from the void may create an extra maintenance burden for something that most knights won't use anyway. I know that at least the pixelarray module, though I suspect this may be the case with others as well, does not support 0 size surfaces, and if they become the default then it would cause a lack of order and consistency to not support them everywhere. Observe:
    [jiffyrob@JiffyArcher ~]$ python
    Python 3.12.3 (main, Apr 23 2024, 09:16:07) [GCC 13.2.1 20240417] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import pygame
    pygame-ce 2.4.1 (SDL 2.28.5, Python 3.12.3)
    >>> ye_voide_surface = pygame.Surface((0, 0))
    >>> pygame.pixelarray.PixelArray(ye_voide_surface)
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    ValueError: Cannot create pixelarray on zero-sized Surface
damusss commented 2 weeks ago

I've surely been seduced by the writing style and and I feel like I contributed to this revolution (no I don't know how to speak Shakespeare) While it wouldn't bother me, I don't think it makes much sense for the simple fact that Surface is immutable. Rects and Vectors you can start off as 0 and then edit them as you whish, but a zero size surface will stay 0 forever and will stay useless forever. I mean, I love consistency and it's true that this doesn't conform, but I can reason with it being so. I've read jiffyrobb comment after writing this, we are kind of on the same boat.

bigwhoopgames commented 2 weeks ago

There are many other use cases for zero-sized surfaces.

  1. Placeholders for future surfaces prior to a load or during a period of unknown size. Also good for debugging functions.
  2. Safe execution of code without None checks, allocate a zero surface first then replace it later, operating on it is safe when operating on a None is not.
  3. A Fallback in cases where a specific size is unavailable, just create a zero surface and use it instead of crashing the program.
  4. A minimal memory use default allocation that still gives access to the features of Surface.

To JiffyRob's points:

  1. I think the concept that a Surface must have a size is actually more confusing. It works just like creating an empty list, set, or dict. In Python we can create empty / zero sized objects all the time calling list(), set(), dict(), and even immutable ones like tuple().
  2. It can represent so much more. Keep in mind other possible uses of zero-sized surfaces as I have mentioned above. A Surface also holds information about bit-depth, alpha channels, palettes, and so on.
  3. Summoning an empty surface while it cannot be changed itself could indicate a placeholder to be replaced at a later point in time. I also mentioned prevention of None checks and unknown sizes at the same it might need be created. Mutability should not be the reason preventing default zero sized Surfaces. Just like we sometimes need empty tuples, sometimes we need zero sized surfaces.
  4. Considering Pygame-CE supports zero sized surfaces, shouldn't functions that accept or work with surfaces then expect to handle a zero Surface as a possibility? Maybe those other functions are incorrect and not the other way around?
damusss commented 2 weeks ago

Your usecases and points are convincing. And yes, functions that deal with surfaces must check for 0 sized ones and (?) early return (I've made a pr fixing one of those bugs myself)

From what I can see in surface.c implementing that is as simple as not requiring the first argument, and if it was not passed (the pointer = NULL, which is not the same as None) set width and height to 0, so it looks like a simple API change.

It's probably best to hear the opinion of some senior reviewer

damusss commented 2 weeks ago

Now that I think of it, It would be "nice" to allow pygame.Surface(100) to create a surface 100x100 (another simple code change from what I can see), what do you think?

bigwhoopgames commented 2 weeks ago

I would also support adding functionality that allow for the creation of square surfaces by passing a single Int. Seem like a logical thing to do and square surfaces are fairly common. I would even support adding the ability for a surface to be created by passing the dimensions separately as is the case for so many other types.

damusss commented 2 weeks ago

I would also support adding functionality that allow for the creation of square surfaces by passing a single Int. Seem like a logical thing to do and square surfaces are fairly common. I would even support adding the ability for a surface to be created by passing the dimensions separately as is the case for so many other types.

About the second proposal, I'm afraid it's nearly impossible, because in python there is no overloading. All we can see is what types the user passed.

if you do:

if we allow an int for square surfaces, we can have

but if you were to do

wait, it's the same - so how can we know what you wanted to do? and even if the int for square wasn't added, adding a new parameter in the middle of old parameters is hard to detect (or impossible), as both flags and the new h would be integers.

I'm also afraid that an int for square surfaces could cause confusion, I can see a very common error being pygame.Surface(100, 100), which correctly raises an error, but if we add single int for square the code would try and use 100 as a flag, but it wouldn't fail, so you'd get a square surface where you didn't want one. Idk, tell me what you think

Starbuck5 commented 2 weeks ago

At this point I'm against this proposal, for largely the same reasons as JiffyRob and damus mentioned.

Additionally, 0-sized surfaces are veritable magnets for segfaults, so I'm not sure we want to encourage their use.

If you want a zero sized Surface I don't think it's too much of a burden to provide the size (0, 0).

ankith26 commented 1 week ago

I am also against this proposal, I get the arguments for why a zero surface could be useful in some cases, but I don't think it's so common it deserves a special/default case handling.

However, I definitely think we should still work on fixing any of those nasty edge case bugs that zero surfaces can bring up instead of ignoring it.