Skip to content

Commit

Permalink
This patch contains massive refactoring of the abstract operations an…
Browse files Browse the repository at this point in the history
…d internal slots used in the Sensor interface specification.

The refactoring goals are:

    Recompose abstract operations so that duplication of the algorithms steps is removed.
    Drop the unused abstract operations and internal slots.

Also the change brings the following behavioral changes:

Fixes w3c#152. Each Sensor instance reads the sensor readings considering its individual
frequency hint, sends 'onchange' and caches the sensor latest reading at this moment.
The Sensor's attributes return values from the cached reading.
Thus we achieve:

    appearance of a new Sensor instance with a higher frequency hint does not affect the behavior of the existing Sensor instances of the same type.
    consistency between the Sensor's 'onchange' notification and its attribute values.

Fixes w3c#168. A Sensor object returns reading values only in "activated" state and returns null otherwise.

Fixes w3c#199
Fixes w3c#200
Fixes w3c#201
Fixes w3c#203
  • Loading branch information
Mikhail Pozdnyakov committed May 23, 2017
1 parent 2d22842 commit 6dbf88b
Showing 1 changed file with 90 additions and 167 deletions.
257 changes: 90 additions & 167 deletions index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,10 @@ This set is initially [=set/is empty|empty=].
A [=sensor=] has an associated <dfn>latest reading</dfn> [=ordered map|map=]
which holds the latest available [=sensor readings=].

User agent must invoke the [=Report Latest Reading Updated=] abstract operation
for every {{Sensor}} object from [=activated Sensor objects=] [=ordered set|set=]
any time a new [=sensor reading|reading=] is available.

Issue: does the [=latest reading=] map need to be
tied to an origin?

Expand Down Expand Up @@ -597,8 +601,6 @@ its associated [=sensor type=] does.

A [=sensor=] has an associated <dfn>reporting flag</dfn> which is initially unset.

A [=sensor=] has an associated <dfn>periodic reporting mode flag</dfn> which is initially unset.

A [=sensor=] has an associated <dfn>current polling frequency</dfn> which is initially `null`.

<h2 id="api">API</h2>
Expand Down Expand Up @@ -746,21 +748,23 @@ with the internal slots described in the following table:
</tr>
<tr>
<td><dfn attribute for=Sensor>\[[desiredPollingFrequency]]</dfn></td>
<td>The requested polling frequency. It is initially unset.</td>
<td>The requested polling frequency for the {{Sensor}} object.
It is initially unset.</td>
</tr>
<tr>
<td><dfn attribute for=Sensor>\[[lastEventFiredAt]]</dfn></td>
<td>the high resolution timestamp of the latest [=sensor reading=]
that was sent to observers of the {{Sensor}} object,
expressed in milliseconds that passed since the [=time origin=].
It is initially `null`.
<td><dfn attribute for=Sensor>\[[reading]]</dfn></td>
<td>A deep copy of the the latest [=sensor reading=]
for the {{Sensor}} object.
Initially, it contains [=map/entries=] with the same
[=map/keys=] as the [=map/entries=] in [=latest reading=]
[=ordered map|map=] and with all the [=map/values=] set to `null`.
</td>
</tr>
<tr>
<td><dfn attribute for=Sensor>\[[waitingForUpdate]]</dfn></td>
<td>A boolean which indicates wether the observers have been updated
or whether the object is waiting for a new reading to do so.
It is initially `true`.</td>
<td>A boolean which indicates whether {{[[reading]]}} [=ordered map|map=]
needs to be updated after a new [=sensor reading=] was reported.
It is initially `false`.</td>
</tr>
<tr>
<td><dfn attribute for=Sensor>\[[identifyingParameters]]</dfn></td>
Expand All @@ -787,7 +791,7 @@ with the internal slots described in the following table:
### Sensor.timestamp ### {#sensor-timestamp}

The getter of the {{Sensor/timestamp!!attribute}} attribute returns
[=latest reading=]["timestamp"].
<emu-val>this</emu-val>.{{[[reading]]}}["timestamp"].

### Sensor.start() ### {#sensor-start}

Expand All @@ -805,7 +809,7 @@ The {{Sensor/start()}} method must run these steps or their [=equivalent=]:
the [=Request Sensor Access=] abstract operation,
passing it |sensor_instance| as argument.
1. If |permission_state| is "granted",
1. Invoke [=Register a Sensor Object=] passing it |sensor_instance| as argument.
1. Invoke [=Activate a Sensor Object=] passing it |sensor_instance| as argument.
1. Otherwise, if |permission_state| is "denied",
1. let |e| be the result of [=created|creating=]
a "{{NotAllowedError!!exception}}" {{DOMException}}.
Expand All @@ -819,15 +823,14 @@ The {{Sensor/start()}} method must run these steps or their [=equivalent=]:
The {{Sensor/stop()}} method must run these steps or their [=equivalent=]:

1. If |sensor_instance|.{{[[state]]}} is "idle", then return.
1. Set |sensor_instance|.{{[[state]]}} to "idle".
1. Run these sub-steps [=in parallel=]:
1. Invoke [=Unregister a Sensor=] passing it |sensor_instance| as argument.
1. Invoke [=Deactivate a Sensor Object=] passing it |sensor_instance| as argument.
</div>

### Sensor.onchange ### {#sensor-onchange}

{{Sensor/onchange}} is an {{EventHandler}} which is called
whenever a new [=sensor reading|reading=] is available.
when <emu-val>this</emu-val>.{{[[reading]]}} is updated with the [=latest reading=] [=map/entries=].

Issue: Should this be renamed `onreading`?
Should we instead add an `ondata` {{EventHandler}} for continuous data
Expand Down Expand Up @@ -957,39 +960,47 @@ Gets the {{Error}} object passed to {{SensorErrorEventInit}}.
</div>


<h3 dfn>Register a Sensor Object</h3>
<h3 dfn>Activate a Sensor Object</h3>

<div algorithm="register a sensor object">
<div algorithm="activate a sensor object">

: input
:: |sensor_instance|, a {{Sensor}} object.
: output
:: None

1. Let |sensor| be the [=sensor=] associated with |sensor_instance|.
1. Add |sensor_instance| to |sensor|'s set of [=activated Sensor objects=].
1. Invoke the [=Set Sensor Settings=] abstract operation,
passing it |sensor| as argument.
1. Add |sensor_instance| to |sensor|'s set of [=activated Sensor objects=].
1. Set |sensor_instance|.{{[[state]]}} to "activated".
1. [=Fire an event=] named "activate" at |sensor_instance|.

Issue: How do we handle activation for [=sensors=] that
do not provide values immediately?
Fire a dedicated event to signal brokenness?
<!-- 1. Let |latest_reading| be |sensor|'s associated [=latest reading=] [ordered map|map].
1. If |current_reading|["timestamp"] is not `null` and |sensor_instance|'s state is still "activating", then
1. invoke the [=Update Observers=] operation, passing it
|sensor_instance| and |current_reading| as arguments. -->
</div>


<h3 dfn>Unregister a Sensor</h3>
<h3 dfn>Deactivate a Sensor Object</h3>

<div algorithm="unregister a sensor">
<div algorithm="deactivate a sensor object">

: input
:: |sensor_instance|, a {{Sensor}} object.
: output
:: None

1. Set |sensor_instance|.{{[[state]]}} to "idle".
1. [=map/For each=] |key| → |value| of |sensor_instance|.{{[[reading]]}}.
1. [=map/Set=] |sensor_instance|.{{[[reading]]}}[|key|] to `null`.
1. Let |sensor| be the [=sensor=] associated with |sensor_instance|.
1. Remove |sensor_instance| from |sensor|'s set of [=activated Sensor objects=].
1. If |sensor|'s set of [=activated Sensor objects=] is empty,
1. Unset the [=periodic reporting mode flag=].
1. Set [=current polling frequency=] to `null`.
1. Update the user-agent-specific way in which [=sensor readings=] are obtained from |sensor|
to no longer provide [=sensor readings|readings=].
Expand All @@ -1009,15 +1020,11 @@ Gets the {{Error}} object passed to {{SensorErrorEventInit}}.

1. let |activated_sensors| be |sensor|'s associated [=ordered set|set=] of [=activated Sensor objects=].
1. [=set/For each=] |s| of |activated_sensors|,
1. [=set/Remove=] |s| from |activated_sensors|.
1. Invoke [=Deactivate a Sensor Object=] passing it |s| as argument.
1. let |e| be the result of [=created|creating=]
a "{{NotAllowedError!!exception}}" {{DOMException}}.
1. Invoke the [=Handle Errors=] abstract operation,
passing it |e| and |s| as arguments.
1. Unset |sensor|'s [=periodic reporting mode flag=].
1. Set |sensor|'s [=current polling frequency=] to `null`.
1. Update the user-agent-specific way in which [=sensor readings=] are obtained from |sensor|
to no longer provide [=sensor readings|readings=].

</div>

Expand All @@ -1030,176 +1037,92 @@ Gets the {{Error}} object passed to {{SensorErrorEventInit}}.
:: |sensor|, a [=sensor=].
: output
:: None

1. Let |settings_changed| be `false`.
1. Let |is_periodic| be the result of invoking
the [=Is Current Reporting Mode Periodic=] abstract operation,
with |sensor| as argument.
1. If |is_periodic| is `false` and the [=periodic reporting mode flag=] is set, then
1. set |settings_changed| to `true`.
1. Unset the [=periodic reporting mode flag=].
1. Otherwise if |is_periodic| is `true` and the [=periodic reporting mode flag=] is unset, then
1. set |settings_changed| to `true`.
1. Set the [=periodic reporting mode flag=].
1. Let |frequency| be the result of invoking
the [=Find the polling frequency of a Sensor=] abstract operation,
with |sensor| as argument.
1. If |frequency| is different from |sensor|'s [=current polling frequency=],
1. set |settings_changed| to `true`.
1. Set [=current polling frequency=] to |frequency|.
1. If |settings_changed| is `true`
1. Invoke the [=Observe a Sensor=] abstract operation,
passing it |sensor| as argument.

Issue: This abstract operation needs to return |settings_changed|
instead of the [=Observe a Sensor=] abstract operation itself.
</div>


<h3 dfn>Observe a Sensor</h3>

<div algorithm="observe a sensor">

Issue: This needs to be refactored in an abstract operation
that has access to the {{Sensor}} instance |sensor_instance|
that just got started.

: input
:: |sensor|, a [=sensor=].
: output
:: None

<!-- 1. Immediately invoke the [=Update latest reading=] abstract operation
to report fresh [=sensor readings|readings=],
passing it |sensor| and [=latest reading=]["timestamp"] as arguments. -->
1. If |sensor|'s |latest reading|["timestamp"] is not `null`,
invoke the [=update observers=] abstract operation passing it |sensor_instance|
and |latest reading|["timestamp"] as arguments.
1. Otherwise, poll |sensor| immediately.

Issue: How do we handle this for [=sensors=] that
do not provide values immediately?
Fire a dedicated event to signal brokenness?

1. If |sensor|'s [=periodic reporting mode flag=] is set,
1. let |frequency| be the [=current polling frequency=],
capped by the upper and lower bounds of the underlying hardware.

Issue: Should this max polling frequency be reflected in the {{Sensor}} interface?
E.g. Through a dedicated attribute?

Issue: Does the max polling frequency affect the reporting frequency?
If so, should we advise the developer of this issue?
E.g. via a dedicated event?
1. Poll |sensor| at |frequency|.
1. Issue: Hook into the `requestAnimationFrame` framework [[HTML]]
to invoke the [=update latest reading=] abstract operation
with every new frame passing it |sensor| and
the latest [=sensor reading=] as arguments.

Issue: Relying on `requestAnimationFrame` gives us a perfect point
to buffer readings > 60Hz and to pass them to together with every new frame.
That's a level 2 feature.

Issue: Figure out how to handle sensors/platforms that push the data
rather than wait for it to be polled.
1. If the [=periodic reporting mode flag=] is unset,
1. the user-agent can decide on the best reporting strategy
for this particular |sensor| and [=sensor type=].

Issue: This needs to be defined better.
1. User agent must set |sensor|'s [=current polling frequency=] <!-- and other settings -->
based on |options| of each |sensor_instance| from |sensor|'s associated [=ordered set|set=]
of [=activated Sensor objects=].
</div>


<h3 dfn>Is Current Reporting Mode Periodic</h3>
<h3 dfn>Calculate Reporting Frequency</h3>

<div algorithm="is current reporting mode periodic">
<div algorithm="calculate reporting frequency">

: input
:: |sensor|, a [=sensor=].
: output
:: |result|, a boolean.

1. Let |result| be `false`.
1. [=list/For each=] |sensor_instance| in |sensor|'s set of [=activated Sensor objects=]:
1. if |sensor_instance|.{{[[desiredPollingFrequency]]}} is set,
1. set |result| to `true`, then [=break=].
1. return |result|.
</div>


<h3 dfn>Find the polling frequency of a Sensor</h3>

<div algorithm="find the polling frequency of a sensor">

: input
:: |sensor|, a [=sensor=].
:: |sensor_instance|, a {{Sensor}} object.
: output
:: |frequency|, a [=frequency=].

1. Let |frequency| be `null`.
1. [=set/For each=] |sensor_instance| in |sensor|'s set of [=activated Sensor objects=]:
1. let |f| be |sensor_instance|.{{[[desiredPollingFrequency]]}}.
1. if |f| is set and |f| is greater than |frequency|,
1. set |frequency| to |f|.
1. Let |f| be |sensor_instance|.{{[[desiredPollingFrequency]]}}.
1. if |f| is set,
1. set |frequency| to |f| capped by the upper and lower [=polling frequency=]
bounds for the associated [=sensor=].
1. Otherwise,
1. user agent can assign |frequency| to an appropriate value.
1. return |frequency|.
</div>


<h3 dfn>Update latest reading</h3>
<h3 dfn>Report Latest Reading Updated</h3>

<div algorithm="update latest reading">
<div algorithm="report latest reading updated">

: input
:: |sensor|, a [=sensor=].
:: |reading|, a [=sensor reading=].
:: |reading_timestamp|, the timestamp at which [=sensor=] was polled.

Issue: The timestamp needs to be specified more precisely,
see [issue #155](https://github.com/w3c/sensors/issues/155).
:: |sensor_instance|, a {{Sensor}} object.
: output
:: None

1. If |sensor|’s [=reporting flag=] is set,
1. abort these steps.
1. If |reading_timestamp| is equal [=latest reading=]["timestamp"],
1. abort these steps.
1. Set |sensor|’s [=reporting flag=].
1. [=map/Set=] [=latest reading=]["timestamp"] to |reading_timestamp|.
1. [=map/For each=] |key| → |value| of [=latest reading=].
1. If |key| is "timestamp", [=continue=].
1. [=map/Set=] [=latest reading=][|key|] to the corresponding
value of |reading|.
1. If |sensor_instance|.{{[[waitingForUpdate]]}} is `true`,
1. Abort these steps.
1. Set |sensor_instance|.{{[[waitingForUpdate]]}} to `true`.
1. Let |lastReportedTimestamp| be |sensor_instance|.{{[[reading]]}}["timestamp"].

Issue: Maybe compare |value| with corresponding
value of |reading| to see if there's a change
that needs to be propagated.
1. Unset |sensor|’s [=reporting flag=].
</div>
Issue: The Sensor timestamp needs to be specified more precisely,
see [issue #155](https://github.com/w3c/sensors/issues/155).

1. If |lastReportedTimestamp| is not set
1. Invoke the [=Update Reading=] abstract operation, passing it |sensor_instance| as argument.
1. Abort these steps.
1. Let |reportingFrequency| be result of invoking the "Calculate Reporting Frequency" abstract operation.
1. If |reportingFrequency| is not set
1. Invoke the [=Update Reading=] abstract operation, passing it |sensor_instance| as argument.
1. Abort these steps.
1. Let |reportingInterval| be the result of 1 / |reportingFrequency|.
1. Let |timestampDelta| be the result of [=latest reading=]["timestamp"] - |lastReportedTimestamp|.
1. If |timestampDelta| is less than or equal to |reportingInterval|
1. Invoke the [=Update Reading=] abstract operation, passing it |sensor_instance| as argument.
1. Abort these steps.
1. Let |deferUpdateTime| be the result of |reportingInterval| - |timestampDelta|.
1. User agent must defer invoking of the [=Update Reading=] abstract operation for a period of time
equal to |deferUpdateTime|.

Issue: Consider hooking into the `requestAnimationFrame` framework
[[HTML]] to invoke the [=Update Reading=] abstract operation
with every new frame passing it |sensor_instance| as argument.
See [issue #198](https://github.com/w3c/sensors/issues/198).

Issue: Relying on `requestAnimationFrame` gives us a perfect point
to buffer readings > 60Hz and to pass them to together with every new frame.
That's a level 2 feature.
</div>

<h3 dfn>Update Observers</h3>
<h3 dfn>Update Reading</h3>

<div algorithm="update observers">
<div algorithm="update reading">

: input
:: |sensor_instance|, a {{Sensor}} object.
:: |timestamp|, a high resolution timestamp.
: output
:: None

1. If |sensor_instance|.{{[[state]]}} is "activating":
1. Set |sensor_instance|.{{[[state]]}} to "activated".
1. [=Fire an event=] named "activate" at |sensor_instance|.
1. If |sensor_instance|.{{[[waitingForUpdate]]}} is true, then

Issue: Should we fire delayed readings? Or should we just drop readings instead?

1. Set |sensor_instance|.{{[[waitingForUpdate]]}} to `false`.
1. [=Fire an event=] named "reading" at |sensor_instance|.
1. Set |sensor_instance|.{{[[lastEventFiredAt]]}} to |timestamp|.
1. [=map/For each=] |key| → |value| of [=latest reading=].
1. [=map/Set=] |sensor_instance|.{{[[reading]]}}[|key|] to the corresponding
value of [=latest reading=].

Issue: Should these last steps be done from within a new task?
Issue: Maybe compare |value| with corresponding
value of |reading| to see if there's a change
that needs to be propagated.
1. Set |sensor_instance|.{{[[waitingForUpdate]]}} to `false`.
1. [=Fire an event=] named "change" at |sensor_instance|.
</div>


Expand Down

0 comments on commit 6dbf88b

Please sign in to comment.