Repository: MaineKuehn/usim

Issue:
Flatten uSim namespace
The ``usim`` namespace currently consists of ``usim``, ``usim.basics`` and ``usim.typing``. Since there are no name conflicts, there really is no point requiring people to remember which is which (I forgot already...). The only valid distinction seems to be "things to use" and "things not to use".

I propose the following structure:

* `usim` everything that people are supposed to use,
* `usim.typing` everything only useful for annotations.

Special packages, such as ``usim.py``, are not affected by this.

Discussion:

Patch:
diff --git a/docs/index.rst b/docs/index.rst
index 9dbab14..e11bcf8 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -12,7 +12,7 @@
 
    source/tutorial/overview
    source/topics/overview
-   source/api/overview
+   source/api/usim
    source/api/usim.py
    source/glossary
 
diff --git a/docs/source/api/overview.rst b/docs/source/api/overview.rst
deleted file mode 100644
index 0ac8c1c..0000000
--- a/docs/source/api/overview.rst
+++ /dev/null
@@ -1,34 +0,0 @@
-μSim API Reference
-==================
-
-The :py:mod:`usim` package provides access to the entire μSim API in one
-convenient, flat namespace.
-Logically, the API can be divided into different topics, however.
-In addition, :py:mod:`usim.typing` allows for type annotations to statically
-verify simulations.
-
-Starting a Simulation
----------------------
-
-.. autofunction:: usim.run
-
-Simulating Time
----------------
-
-μSim provides a number of primitives to control time in a simulation.
-
-======================== ======================================================
-:py:data:`usim.time`     used to define or delay until arbitrary points in time
-:py:data:`usim.eternity` a delay time indefinitely in the future
-:py:data:`usim.instant`  a delay time indistinguishable from the current time
-:py:data:`usim.interval` a repeated delay by a fixed interval
-:py:data:`usim.delay`    a repeated delay
-======================== ======================================================
-
-Detailed Topics
----------------
-
-.. toctree::
-    :maxdepth: 2
-
-    timing
diff --git a/docs/source/api/usim.basics.rst b/docs/source/api/usim.basics.rst
deleted file mode 100644
index 87d487e..0000000
--- a/docs/source/api/usim.basics.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-.. this API reference is autogenerated by apidoc
-   TODO: replace it with a manually curated one
-
-usim.basics module
-==================
-
-.. automodule:: usim.basics
-    :members:
-    :undoc-members:
-    :show-inheritance:
diff --git a/docs/source/api/usim.rst b/docs/source/api/usim.rst
index 4329c4a..eaed960 100644
--- a/docs/source/api/usim.rst
+++ b/docs/source/api/usim.rst
@@ -1,19 +1,172 @@
-.. this API reference is autogenerated by apidoc
-   TODO: replace it with a manually curated one
+μSim API Reference
+==================
 
-usim package
-============
+.. py:module:: usim
+    :synopsis: The μSim namespace
 
-.. automodule:: usim
-    :members:
-    :undoc-members:
-    :show-inheritance:
+The :py:mod:`usim` package provides access to the entire μSim API in one
+convenient, flat namespace.
+Logically, the API can be divided into different topics.
+In addition, :py:mod:`usim.typing` allows for type annotations to statically
+verify simulations.
 
-Submodules
-----------
+.. hint::
+
+    μSim provides a :ref:`compatibility layer <simpy_compatibility>`
+    to the `SimPy`_ simulation framework.
+    The :py:mod:`usim.py` package is a drop-in replacement
+    for the :py:mod:`simpy` package.
+
+Starting a Simulation
+---------------------
+
+Simulations in μSim are always defined based on a number of initial root
+:term:`activities <activity>`,
+which may branch out to more :term:`activities <activity>`.
+A simulation is started by calling :py:func:`usim.run`, passing in its
+:term:`activities <activity>` and optionally the time window to simulate.
+
+.. autofunction:: usim.run
+
+Simulating Time
+---------------
+
+μSim provides a number of primitives to check or wait for specific points in time
+of a simulation.
+
+:py:data:`usim.time`
+    Expressions define points in time or delays.
+    For example, ``await (time + 20)`` delays for 20 time units.
+
+:py:data:`usim.eternity`
+    A point in time indefinitely far into the future.
+
+:py:data:`usim.instant`
+    A point in time indistinguishable from the current moment.
+
+In addition, recurring actions can be repeated at specific delays or intervals
+in ``async for`` block.
+
+:py:data:`usim.interval`
+    Repeat in fixed intervals, regardless of any time spent in a block.
+
+:py:data:`usim.delay`
+    Repeat after fixed delays, in addition to any time spent in a block.
+
+Branching and Multitasking
+--------------------------
+
+An :term:`activity` may not only ``await`` another,
+it may also run one or more :term:`activities <activity>` concurrently.
+This requires opening a scope in an ``async with`` context
+which defines the lifetime of child activities.
+
+:py:class:`usim.Scope`
+    An :term:`asynchronous context manager` which can concurrently
+    run :term:`activities <activity>` until it is closed.
+    A scope is forcefully closed if an exception occurs
+    in the hosting :term:`activity` or a child.
+
+:py:func:`usim.until`
+    A :py:class:`~usim.Scope` that is forcefully closed
+    if a specific notification triggers.
+
+Each child :term:`activity` is represented by a :py:class:`~usim.typing.Task`.
+Tasks can be inspected for their current status,
+and various exceptions only occur when interacting with tasks.
+
+:py:class:`usim.TaskState`
+    Enum describing the possible :py:meth:`~usim.typing.Task.status`
+    of a :py:class:`~usim.typing.Task`.
+
+:py:exc:`usim.TaskClosed` and :py:exc:`usim.VolatileTaskClosed`
+    The exception value of a :py:class:`~usim.typing.Task` that was forcefully
+    closed by its :py:class:`~usim.Scope`.
+
+:py:exc:`usim.CancelTask` and :py:exc:`usim.TaskCancelled`
+    Exception used to :py:meth:`~usim.typing.Task.cancel`
+    a :py:class:`~usim.typing.Task`,
+    and the resulting exception value of the :py:class:`~usim.typing.Task`.
+
+During a :py:class:`usim.Scope`,
+multiple child activities may fail with an exception at the same time.
+The :py:class:`usim.Scope` collects and propagates all exceptions from child activities.
+
+:py:exc:`usim.Concurrent`
+    Exception that propagates all exceptions from child activities at once;
+    also occurs if only a single child activity fails.
+    Never includes an exception raised in the scope itself.
+
+Synchronising Conditions
+------------------------
+
+μSim allows to model any boolean that may change at a later time
+as a :py:class:`~usim.typing.Condition`.
+These can be combined and negated to derive new :py:class:`~usim.typing.Condition`\ s.
+
+:py:class:`usim.Flag`
+    A :py:class:`~usim.typing.Condition` with a fixed value which can be explicitly
+    :py:meth:`~usim.Flag.set` to either :py:data:`True` or :py:data:`False`.
+
+It is common for types to return a :py:class:`~usim.typing.Condition` instead of
+a plain :py:class:`bool`.
+
+:py:class:`usim.Tracked`
+    A mutable value which can be explicitly :py:meth:`~usim.Tracked.set` or modified
+    using arithmetic operators, such as ``+``, ``-``, ``*`` or ``/``.
+    Comparison operators, such as ``==``, ``<=`` or ``>``, provide
+    a :py:class:`~usim.typing.Condition` which triggers once the value matches.
+
+Sharing State
+-------------
+
+Concurrently running :term:`activities <activity>` frequently need to access,
+modify or exchange state.
+μSim provides several types to easily write :term:`activities <activity>` that
+safely share state.
+
+:py:class:`usim.Lock`
+    Ensure mutually exclusive access for multiple activities.
+
+:py:class:`usim.Channel`
+    Broadcast messages to multiple subscribers.
+
+:py:class:`usim.Queue`
+    Send and receive unique messages.
+
+:py:exc:`usim.StreamClosed`
+    Exception for operations not supported by a closed
+    :py:class:`~usim.Channel` or :py:class:`~usim.Queue`
+
+Modelling Resources
+-------------------
+
+Simulations commonly revolve around resources which are produced/consumed,
+blocked or waited for.
+μSim implements a range of generic, ready-to-use resources for various use-cases.
+
+:py:class:`usim.Resources`
+    Supply of named resources which can be temporarily borrowed
+    or permanently produced/consumed.
+
+:py:class:`usim.Capacities`
+    Fixed supply of named resources which can be temporarily borrowed.
+
+:py:exc:`usim.ResourcesUnavailable`
+    Exception raised when an attempt to :py:meth:`~usim.Resources.claim`
+    resources fails.
+
+Detailed Topics
+---------------
 
 .. toctree::
+    :maxdepth: 2
 
-   usim.basics
-   usim.typing
+    usim_timing
+    usim_branching
+    usim_synchronising
+    usim_sharing
+    usim_resources
+    usim.typing
 
+.. _SimPy: https://simpy.readthedocs.io/
diff --git a/docs/source/api/usim_branching.rst b/docs/source/api/usim_branching.rst
new file mode 100644
index 0000000..96f48d6
--- /dev/null
+++ b/docs/source/api/usim_branching.rst
@@ -0,0 +1,26 @@
+Branching Tasks and Concurrent Exceptions
+=========================================
+
+.. autoclass:: usim.Scope
+    :members:
+
+.. autofunction:: usim.until
+    :async-with:
+
+.. autoclass:: usim.TaskState
+    :members:
+
+.. autoexception:: usim.TaskClosed
+    :members:
+
+.. autoexception:: usim.VolatileTaskClosed
+    :members:
+
+.. autoexception:: usim.CancelTask
+    :members:
+
+.. autoexception:: usim.TaskCancelled
+    :members:
+
+.. autoexception:: usim.Concurrent
+    :members:
diff --git a/docs/source/api/usim_resources.rst b/docs/source/api/usim_resources.rst
new file mode 100644
index 0000000..7bb8645
--- /dev/null
+++ b/docs/source/api/usim_resources.rst
@@ -0,0 +1,11 @@
+Resource Modelling
+==================
+
+.. autoclass:: usim.Resources
+    :members:
+
+.. autoclass:: usim.Capacities
+    :members:
+
+.. autoexception:: usim.ResourcesUnavailable
+    :members:
diff --git a/docs/source/api/usim_sharing.rst b/docs/source/api/usim_sharing.rst
new file mode 100644
index 0000000..21d36fd
--- /dev/null
+++ b/docs/source/api/usim_sharing.rst
@@ -0,0 +1,14 @@
+Sharing State
+=============
+
+.. autoclass:: usim.Lock
+    :members:
+
+.. autoclass:: usim.Channel
+    :members:
+
+.. autoclass:: usim.Queue
+    :members:
+
+.. autoexception:: usim.StreamClosed
+    :members:
diff --git a/docs/source/api/usim_synchronising.rst b/docs/source/api/usim_synchronising.rst
new file mode 100644
index 0000000..14d71c7
--- /dev/null
+++ b/docs/source/api/usim_synchronising.rst
@@ -0,0 +1,8 @@
+Synchronisation via Conditions
+==============================
+
+.. autoclass:: usim.Flag
+    :members:
+
+.. autoclass:: usim.Tracked
+    :members:
diff --git a/docs/source/api/timing.rst b/docs/source/api/usim_timing.rst
similarity index 100%
rename from docs/source/api/timing.rst
rename to docs/source/api/usim_timing.rst
diff --git a/docs/source/topics/simpy.rst b/docs/source/topics/simpy.rst
index ec8bc6b..5bbb73a 100644
--- a/docs/source/topics/simpy.rst
+++ b/docs/source/topics/simpy.rst
@@ -1,3 +1,5 @@
+.. _simpy_compatibility:
+
 SimPy Compatibility
 ===================
 
diff --git a/usim/__init__.py b/usim/__init__.py
index 61f1cf6..4d1fc1d 100644
--- a/usim/__init__.py
+++ b/usim/__init__.py
@@ -6,16 +6,23 @@ from ._primitives.timing import Time, Eternity, Instant, interval, delay
 from ._primitives.flag import Flag
 from ._primitives.locks import Lock
 from ._primitives.context import until, Scope, VolatileTaskClosed
-from ._primitives.task import TaskCancelled, TaskState, TaskClosed
+from ._primitives.task import TaskCancelled, TaskState, TaskClosed, CancelTask
 from ._primitives.concurrent_exception import Concurrent
+from ._basics.streams import Channel, Queue, StreamClosed
+from ._basics.tracked import Tracked
+from ._basics.resource import Capacities, Resources, ResourcesUnavailable
 
 
 __all__ = [
     'run',
     'time', 'eternity', 'instant', 'interval', 'delay',
-    'until', 'Scope', 'TaskCancelled', 'VolatileTaskClosed', 'TaskClosed', 'TaskState',
-    'Flag', 'Lock',
+    'until', 'Scope',
+    'TaskCancelled', 'VolatileTaskClosed', 'TaskClosed', 'TaskState', 'CancelTask',
     'Concurrent',
+    'Flag', 'Tracked',
+    'Lock',
+    'Channel', 'Queue', 'StreamClosed',
+    'Capacities', 'Resources', 'ResourcesUnavailable',
 ]
 
 
diff --git a/usim/basics.py b/usim/basics.py
deleted file mode 100644
index da1bd38..0000000
--- a/usim/basics.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from ._basics.streams import Channel, Queue, StreamClosed
-from ._basics.tracked import Tracked
-from ._basics.resource import Capacities, Resources, ResourcesUnavailable
-
-__all__ = [
-    'Channel', 'Queue', 'StreamClosed',
-    'Tracked', 'Capacities', 'Resources', 'ResourcesUnavailable',
-]


Test patch:
diff --git a/usim_pytest/test_types/test_resource.py b/usim_pytest/test_types/test_resource.py
index 5e018c3..3146c03 100644
--- a/usim_pytest/test_types/test_resource.py
+++ b/usim_pytest/test_types/test_resource.py
@@ -3,7 +3,7 @@ import math
 from typing import Type
 
 from usim import Scope, time, until
-from usim.basics import Resources, Capacities, ResourcesUnavailable
+from usim import Resources, Capacities, ResourcesUnavailable
 from usim._basics.resource import BaseResources
 
 from ..utility import via_usim, assertion_mode
diff --git a/usim_pytest/test_types/test_streams.py b/usim_pytest/test_types/test_streams.py
index 1008ad5..06456c9 100644
--- a/usim_pytest/test_types/test_streams.py
+++ b/usim_pytest/test_types/test_streams.py
@@ -2,7 +2,7 @@ import pytest
 from typing import Type
 
 from usim import time, Scope
-from usim.basics import Queue, Channel, StreamClosed
+from usim import Queue, Channel, StreamClosed
 from usim.typing import Stream
 
 from ..utility import via_usim, turnstamp
diff --git a/usim_pytest/test_types/test_tracked.py b/usim_pytest/test_types/test_tracked.py
index 2847b33..44d2e56 100644
--- a/usim_pytest/test_types/test_tracked.py
+++ b/usim_pytest/test_types/test_tracked.py
@@ -2,7 +2,7 @@ import operator
 import pytest
 
 from usim import Scope, time
-from usim.basics import Tracked
+from usim import Tracked
 
 from ..utility import via_usim, assertion_mode