Skip to content

Commit

Permalink
Merge branch 'master' into sound
Browse files Browse the repository at this point in the history
  • Loading branch information
AstraLuma committed Jul 25, 2019
2 parents f88211e + c899802 commit b335b89
Show file tree
Hide file tree
Showing 19 changed files with 256 additions and 75 deletions.
31 changes: 12 additions & 19 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,25 +65,16 @@ the hard problems that they're free to do that.

## I Want To Code

Thanks for considering code contributions! Check out the latest [ppb
release project][projects]. The basic rundown is simple: The `To Do`
column is all of the issues we have to solve before the next release.
Right now, that list is chosen and broken down by the maintainers at the
beginning of each release cycle.

The `Wishlist` column is all of the tickets we'd really like to include,
but aren't critical to the goals of the next release. It mostly gets
filled by the maintainers, but important tickets with lots of discussion
and things suggested by oversight team members will make it there too.

The rest of the columns are for tracking, and a maintainer will move
the tickets appropriately.
Thanks for considering code contributions! The [new contributor label][new contributor]
is a collection of issues we believe are suitable for people new to PPB.
You are welcome to work on any issue you would like, we just think those
are a good starting place for those that are unfamiliar with the code base.

Now you need to [fork ppb][fork]. Once you've done that, go through
either column and find a ticket you like. Start a branch, add your name
to the [CONTRIBUTORS file][contributors], commit that change and open a PR:

* Include "WORKING" in the title
* Include "WIP" in the title or make a [draft pull request][draftpr]
* Reference the issue you want to work on in the body of the PR.

Now get to work! Some tickets have detailed instructions: A maintainer
Expand All @@ -92,11 +83,11 @@ Start working, try to solve the problem. If you get stuck, ask
questions in your open PR. The maintainers and active contributors are
here to help.

Once you think it's ready, time to remove "Working" from the title. Now
someone senior in the project will review. They'll either ask for
changes or approve your PR. If you need to make changes, commit to your
branch and push it up and it'll update the PR. Your reviewer will look
again later.
Once you think it's ready, time to remove "WIP" from the title or hit
the "Ready for review" button. Now someone senior in the project will
review. They'll either ask for changes or approve your PR. If you need
to make changes, commit to your branch and push it up and it'll update
the PR. Your reviewer will look again later.

If you got accepted, your PR will make it into the project soon! Only a
maintainer can merge your PR, so you'll need to wait. They may ask for
Expand Down Expand Up @@ -129,9 +120,11 @@ end you contribute to `ppb` by being part of its community.
[covenant]: http://contributor-covenant.org/ "Contributor's Covenant"
[discuss]: https://github.com/ppb/pursuedpybear/issues?q=is%3Aissue+is%3Aopen+label%3Adiscussion "PPB Discussions"
[docs]: https://github.com/ppb/docs "PPB Docs"
[draftpr]: https://github.blog/2019-02-14-introducing-draft-pull-requests/ "Introducing draft pull requests"
[fork]: https://help.github.com/articles/fork-a-repo/ "Fork a repo"
[goals]: https://github.com/ppb/pursuedpybear#guiding-principles "PPB Goals"
[issues]: https://github.com/ppb/pursuedpybear/issues "PPB Issues"
[new contributor]: https://github.com/ppb/pursuedpybear/labels/new%20contributor "Issues labeld New Contributor"
[projects]: https://github.com/orgs/ppb/projects "PPB Projects"
[readme]: https://github.com/pathunstrom/pursuedpybear/blob/master/README.md "PPB README"
[tutorial]: https://github.com/ppb/tutorials "PPB Tutorials"
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# PursuedPyBear

[![Documentation Status](https://readthedocs.org/projects/ppb/badge/?version=stable)](https://ppb.readthedocs.io/en/stable/?badge=stable)

PursuedPyBear, also known as `ppb`, exists to be an educational
resource. Most obviously used to teach computer science, it can be a
useful tool for any topic that a simulation can be helpful.
Expand Down
46 changes: 37 additions & 9 deletions docs/reference/assets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ The data is kept in memory for the lifetime of the :py:class:`Asset`. When
nothing is referencing it any more, the Python garbage collector will clean up
the object and its data.

:py:class:`Asset` instances are consolidated or "interned": if you ask for the
same asset twice, you'll get the same instance back. Note that this is a
performance optimization and should not be relied upon (do not do things like
``asset1 is asset2``).


General Asset Interface
-----------------------
Expand All @@ -30,6 +35,30 @@ system and the data logistics.
Called in the background thread.


Subclassing
~~~~~~~~~~~

:py:class:`Asset` makes specific assumptions and is only suitable for loading
file-based assets. These make the consolidation, background-loading, and other
aspects of :py:class:`Asset` possible.

You should really only implement two methods:

* :py:meth:`background_parse()`: This is called with the loaded data and returns
an object constructed from that data. This is called from a background thread
and its return value is accessible from :py:meth:`load()`

This is an excellent place for decompression, data parsing, and other tasks
needed to turn a pile of bytes into a useful data structure.

* :py:meth:`file_missing()`: This is called if the asset is not found. Defining
this method surpresses :py:meth:`load()` from raising a
:py:exc:`FileNotFoundError` and will instead call this, and
:py:meth:`load()` will return what this returns.

For example, :py:class:`ppb.Image` uses this to produce the default square.


Concrete Assets
---------------

Expand All @@ -47,15 +76,14 @@ useful.



Asset Proxies
-------------
Asset Proxies and Virtual Assets
--------------------------------

Asset Proxies are virtual assets that implement the interface but either
delegate to other Assets or are completely virtual, such as
:py:class:`ppb.features.animation.Animation`.
Asset Proxies and Virtual Assets are assets that implement the interface but
either delegate to other Assets or are completely synthesized.

.. class:: Asset Proxy
For example, :py:class:`ppb.features.animation.Animation` is an asset proxy that
delegates to actual :py:class:`ppb.Image` instances.

.. method:: load()

Gets the parsed data from wherever this proxy gets its data.
.. autoclass:: ppb.assets.AbstractAsset
:members:
6 changes: 4 additions & 2 deletions docs/reference/scenes.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. py:currentmodule:: ppb.scenes
================
All About Scenes
================
Expand All @@ -6,8 +8,8 @@ Scenes are the terrain where sprites act. Each game has multiple scenes and may
transition at any time.

.. autoclass:: ppb.BaseScene
:members:
:exclude-members: container_class
:members:
:exclude-members: container_class

.. autoattribute:: background_color

Expand Down
50 changes: 45 additions & 5 deletions ppb/assets.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,45 @@
"""
The asset loading system.
"""

import abc
import concurrent.futures
import logging
import threading

import ppb.vfs as vfs
import ppb.events as events
from ppb.systemslib import System

__all__ = 'Asset', 'AssetLoadingSystem',

logger = logging.getLogger(__name__)


class Asset:
class AbstractAsset(abc.ABC):
"""
The asset interface.
This defines the common interface for virtual assets, proxy assets, and
real/file assets.
"""
@abc.abstractmethod
def load(self):
"""
Get the data of this asset, in the appropriate form.
"""

def is_loaded(self):
"""
Returns if the data is ready now or if :py:meth:`load()` will block.
"""
return True


class Asset(AbstractAsset):
"""
A resource to be loaded from the filesystem and used.
Meant to be subclassed.
Meant to be subclassed, but in specific ways.
"""
def __init__(self, name):
self.name = str(name)
Expand All @@ -38,10 +59,14 @@ def _finished_background(self, fut):
if hasattr(self, 'file_missing'):
logger.warning("File not found: %r", self.name)
self._data = self.file_missing()
if _finished is not None:
_finished(self)
else:
raise
else:
self._data = self.background_parse(raw)
if _finished is not None:
_finished(self)
except Exception as exc:
# Save unhandled exceptions to be raised in the main thread
self._raise_error = exc
Expand Down Expand Up @@ -81,15 +106,18 @@ def load(self, timeout: float = None):


class AssetLoadingSystem(System):
def __init__(self, **_):
def __init__(self, *, engine, **_):
super().__init__(**_)
self.engine = engine
self._executor = concurrent.futures.ThreadPoolExecutor()
self._queue = {} # maps names to futures

def __enter__(self):
# 1. Register ourselves as the hint provider
global _hint, _backlog
global _hint, _finished, _backlog
assert _hint is _default_hint
_hint = self._hint
_finished = self._finished

# 2. Grab-n-clear the backlog (atomically?)
queue, _backlog = _backlog, []
Expand All @@ -114,6 +142,17 @@ def _load(filename):
with vfs.open(filename) as file:
return file.read()

def _finished(self, asset):
statuses = [
fut.running()
for fut in self._queue.values()
]
self.engine.signal(events.AssetLoaded(
asset=asset,
total_loaded=sum(not s for s in statuses),
total_queued=sum(s for s in statuses),
))


_backlog = []

Expand All @@ -123,3 +162,4 @@ def _default_hint(filename, callback=None):


_hint = _default_hint
_finished = None
7 changes: 6 additions & 1 deletion ppb/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from typing import Type
from typing import Union

import ppb.events as events
from ppb import events
from ppb.eventlib import EventMixin
from ppb.systems import EventPoller
from ppb.systems import Renderer
Expand Down Expand Up @@ -115,6 +115,11 @@ def activate(self, next_scene: dict):
self.scenes.append(scene(*args, **kwargs))

def signal(self, event):
"""
Add an event to the event queue.
Thread-safe.
"""
self.events.append(event)

def publish(self):
Expand Down
14 changes: 12 additions & 2 deletions ppb/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
'SceneStopped',
'StopScene',
'Update',
'AssetLoaded',
)

# Remember to define scene at the end so the pargs version of __init__() still works
Expand All @@ -30,7 +31,7 @@
from ppb.buttons import MouseButton
from ppb.keycodes import KeyCode
from ppb_vector import Vector
import ppb.assets
import ppb


@dataclass
Expand Down Expand Up @@ -240,4 +241,13 @@ class PlaySound:
"""
Fire to start a sound playing.
"""
sound: ppb.assets.Asset
sound: 'ppb.assets.Asset'

@dataclass
class AssetLoaded:
"""
Fired whenever an asset finished loading.
"""
asset: 'ppb.assets.Asset'
total_loaded: int
total_queued: int
7 changes: 4 additions & 3 deletions ppb/features/animation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""
import time
import re
import ppb

FILE_PATTERN = re.compile(r'\{(\d+)\.\.(\d+)\}')

Expand Down Expand Up @@ -65,7 +66,7 @@ def _compile_filename(self):
self._filename,
)
self._frames = [
template.format(n)
ppb.Image(template.format(n))
for n in range(start, end + 1)
]

Expand Down Expand Up @@ -108,11 +109,11 @@ def current_frame(self):
else:
return self._paused_frame

def __str__(self):
def load(self):
"""
Get the current frame path.
"""
return self._frames[self.current_frame]
return self._frames[self.current_frame].load()

# This is so that if you assign an Animation to a class, instances will get
# their own copy, so their animations run independently.
Expand Down
6 changes: 3 additions & 3 deletions ppb/sprites.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
from pathlib import Path
from typing import Union

import ppb_vector
from ppb_vector import Vector

import ppb
from ppb import Vector
from ppb.eventlib import EventMixin
from ppb.utils import FauxFloat

import ppb_vector


TOP = "top"
BOTTOM = "bottom"
Expand Down
1 change: 1 addition & 0 deletions ppb/systems/inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import pygame

from ppb import buttons
import ppb.buttons as buttons
from ppb_vector import Vector
import ppb.events as events
Expand Down
2 changes: 1 addition & 1 deletion ppb/systemslib.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ppb.eventlib as eventlib
from ppb import eventlib


class System(eventlib.EventMixin):
Expand Down

0 comments on commit b335b89

Please sign in to comment.