Skip to content

Commit 9ee4857

Browse files
authored
Merge pull request #146 from boydm/doc_review
Refactor Cache Docs
2 parents 65c47ce + 084721a commit 9ee4857

File tree

7 files changed

+464
-188
lines changed

7 files changed

+464
-188
lines changed

CHANGELOG.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,20 @@
4949
**Breaking Changes:**
5050
* Breaking changes to Scenic.Cache. It has been replaced by asset specific caches.
5151

52-
| Asset Type | Module |
53-
| --- | --- |
54-
| Static Textures | `Scenic.Cache.Static.Texture` |
55-
| Fonts | `Scenic.Cache.Static.Font` |
56-
| Font Metrics | `Scenic.Cache.Static.FontMetrics` |
57-
| Dynamic Textures | `Scenic.Cache.Dynamic.Texture` |
52+
| Asset Class | Module |
53+
| ------------- | -----|
54+
| Fonts | `Scenic.Cache.Static.Font` |
55+
| Font Metrics | `Scenic.Cache.Static.FontMetrics` |
56+
| Textures (images in a fill) | `Scenic.Cache.Static.Texture` |
57+
| Raw Pixel Maps | `Scenic.Cache.Dynamic.Texture` |
58+
59+
Some of the Cache support modules have moved
60+
61+
| Old Module | New Module |
62+
| ------------- | -----|
63+
| `Scenic.Cache.Hash` | `Scenic.Cache.Support.Hash` |
64+
| `Scenic.Cache.File` | `Scenic.Cache.Support.File` |
65+
| `Scenic.Cache.Supervisor` | `Scenic.Cache.Support.Supervisor` |
5866

5967

6068
## 0.9.0

lib/scenic/cache/base.ex

Lines changed: 22 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,18 @@ defmodule Scenic.Cache.Base do
1212
1313
| Asset Class | Module |
1414
| ------------- | -----|
15-
| Fonts | [Scenic.Cache.Font](Scenic.Cache.Font.html) |
16-
| Font Metrics | [Scenic.Cache.FontMetrics](Scenic.Cache.FontMetrics.html) |
17-
| Textures (images in a fill) | [Scenic.Cache.Texture](Scenic.Cache.Texture.html) |
15+
| Fonts | `Scenic.Cache.Static.Font` |
16+
| Font Metrics | `Scenic.Cache.Static.FontMetrics` |
17+
| Textures (images in a fill) | `Scenic.Cache.Static.Texture` |
18+
| Raw Pixel Maps | `Scenic.Cache.Dynamic.Texture` |
19+
20+
Some of the Cache support modules have moved
21+
22+
| Old Module | New Module |
23+
| ------------- | -----|
24+
| `Scenic.Cache.Hash` | `Scenic.Cache.Support.Hash` |
25+
| `Scenic.Cache.File` | `Scenic.Cache.Support.File` |
26+
| `Scenic.Cache.Supervisor` | `Scenic.Cache.Support.Supervisor` |
1827
1928
## Overview
2029
@@ -182,129 +191,6 @@ defmodule Scenic.Cache.Base do
182191
@behaviour Scenic.Cache.Base
183192
end
184193

185-
@moduledoc """
186-
In memory cache for static #{unquote(using_opts)[:name]} assets.
187-
188-
Assets such as #{unquote(using_opts)[:name]} tend to be relatively large compared to
189-
other data. These assets are often used across multiple scenes and may need to be shared
190-
with multiple drivers.
191-
192-
These assets also tend to have a significant load cost. Fonts need to be rendered. Images
193-
interpreted into their final binary form, etc.
194-
195-
## Goals
196-
197-
Given this situation, the Cache module has multiple goals.
198-
* __Reuse__ - assets used by multiple scenes should only be stored in memory once
199-
* __Load Time__- loading cost should only be paid once
200-
* __Copy time__ - assets are stored in ETS, so they don't need to be copied as they are used
201-
* __Pub/Sub__ - Consumers of static assets (drivers...) should be notified when an asset is
202-
loaded or changed. They should not poll the system.
203-
* __Security__ - Base assets can become an attack vector. Helper modules are provided
204-
to assist in verifying these files.
205-
206-
## Scope
207-
208-
When a #{unquote(using_opts)[:name]} is loaded into the cache, it is assigned a scope.
209-
The scope is used to
210-
determine how long to hold the asset in memory before it is unloaded. Scope is either
211-
the atom `:global`, or a `pid`.
212-
213-
The typical flow is that a scene will load a #{unquote(using_opts)[:name]} into the cache.
214-
A scope is automatically
215-
defined that tracks the asset against the pid of the scene that loaded it. When the scene
216-
is closed, the scope becomes empty and the asset is unloaded.
217-
218-
If, while that scene is loaded, another scene (or any process...) attempts to load
219-
the same asset into the cache, a second scope is added and the duplicate load is
220-
skipped. When the first scene closes, the asset stays in memory as long as the second
221-
scope remains valid.
222-
223-
When a scene closes, it's scope stays valid for a short time in order to give the next
224-
scene a chance to load its assets (or claim a scope) and possibly re-use the already
225-
loaded assets.
226-
227-
This is also useful in the event of a scene crashing and being restarted. The delay
228-
in unloading the scope means that the replacement scene will use already loaded
229-
assets instead of loading the same files again for no real benefit.
230-
231-
When you load assets you can alternately provide your own scope instead of taking the
232-
default, which is your processes pid. If you provide `:global`, then the asset will
233-
stay in memory until you explicitly release it.
234-
235-
## Hashes
236-
237-
At its simplest, accessing the cache is a key-value store. This cache is meant to be
238-
static in nature, so the key is be a hash of the data.
239-
240-
Why? Read below...
241-
242-
## Security
243-
244-
A lesson learned the hard way is that static assets (fonts, images, etc) that your app
245-
loads out of storage can easily become attack vectors.
246-
247-
These formats are complicated! There is no guarantee (on any system) that a malformed
248-
asset will not cause an error in the C code that interprets it. Again - these are complicated
249-
and the renderers need to be fast...
250-
251-
The solution is to compute a SHA hash of these files during build-time of your
252-
and to store the result in your applications code itself. Then during run time, you
253-
compare then pre-computed hash against the run-time of the asset being loaded.
254-
255-
Please take advantage of the helper modules [`Cache.File`](Scenic.Cache.File.html),
256-
[`Cache.Term`](Scenic.Cache.Term.html), and [`Cache.Hash`](Scenic.Cache.Hash.html) to
257-
do this for you. These modules load files and insert them into the cache while checking
258-
a precomputed hash.
259-
260-
These scheme is much stronger when the application code itself is also signed and
261-
verified, but that is an exercise for the packaging tools.
262-
263-
Full Example:
264-
265-
defmodule MyApp.MyScene do
266-
use Scenic.Scene
267-
import Scenic.Primitives
268-
269-
# build the path to the static asset file (compile time)
270-
@asset_path :code.priv_dir(:my_app) |> Path.join("/static/images/asset.jpg")
271-
272-
# pre-compute the hash (compile time)
273-
@asset_hash Scenic.Cache.Hash.file!( @asset_path, :sha )
274-
275-
# build a graph that uses the asset (compile time)
276-
@graph Scenic.Graph.build()
277-
|> rect( {100, 100}, fill: {:image, @asset_hash} )
278-
279-
280-
def init( _, _ ) do
281-
# load the asset into the cache (run time)
282-
Scenic.Cache.File.load(@asset_path, @asset_hash)
283-
284-
{:ok, :some_state, push: @graph}
285-
end
286-
287-
end
288-
289-
When assets are loaded this way, the `@asset_hash` term is also used as the key in
290-
the cache. This has the additional benefit of allowing you to pre-compute
291-
the graph itself, using the correct keys for the correct assets.
292-
293-
## Pub/Sub
294-
295-
Drivers (or any process...) listen to the #{unquote(using_opts)[:name]}
296-
Cache via a simple pub/sub api.
297-
298-
Because the graph, may be computed during compile time and pushed at some
299-
other time than the assets are loaded, the drivers need to know when the assets
300-
become available.
301-
302-
Whenever any asset is loaded into the cache, messages are sent to any
303-
subscribing processes along with the affected keys. This allows them to react in a
304-
loosely-coupled way to how the assets are managed in your scene.
305-
306-
"""
307-
308194
unless unquote(using_opts)[:name] do
309195
raise "You must supply a :name option to the \"use Scenic.Cache.Base\" macro."
310196
end
@@ -373,8 +259,7 @@ defmodule Scenic.Cache.Base do
373259
* `scope` - Optional scope to track the lifetime of this asset against. Can be `:global`
374260
but is usually nil, which defaults to the pid of the calling process.
375261
376-
Returns:
377-
{:ok, hash}
262+
Returns: `{:ok, hash}`
378263
"""
379264
@spec put(
380265
hash :: Scenic.Cache.Base.hash(),
@@ -400,8 +285,7 @@ defmodule Scenic.Cache.Base do
400285
* `scope` - Optional scope to track the lifetime of this asset against. Can be `:global`
401286
but is usually nil, which defaults to the pid of the calling process.
402287
403-
Returns:
404-
{:ok, hash}
288+
Returns: `{:ok, hash}`
405289
"""
406290
@spec put_new(
407291
hash :: Scenic.Cache.Base.hash(),
@@ -692,46 +576,6 @@ defmodule Scenic.Cache.Base do
692576
GenServer.call(service, {:put, normalize_scope(scope), key, data})
693577
end
694578

695-
# # --------------------------------------------------------
696-
# @doc """
697-
# Insert an item into the Cache. If it is already in the cache, then it
698-
# does nothing and returns {:ok, hash}
699-
700-
# Parameters:
701-
# * `key` - term to use as the retrieval key. Typically a hash of the data itself.
702-
# * `data` - term to use as the stored data
703-
# * `scope` - Optional scope to track the lifetime of this asset against. Can be `:global`
704-
# but is usually nil, which defaults to the pid of the calling process.
705-
706-
# ## Examples
707-
# iex> Scenic.Cache.get("test_key")
708-
# nil
709-
710-
# iex> :ets.insert(:scenic_cache_key_table, {"test_key", 1, :test_data})
711-
# ...> true
712-
# ...> Scenic.Cache.get("test_key")
713-
# :test_data
714-
# """
715-
# @spec put_new(
716-
# service :: atom,
717-
# hash :: Scenic.Cache.Base.hash(),
718-
# data :: term(),
719-
# scope :: :global | nil | GenServer.server()
720-
# ) :: term()
721-
722-
# def put_new(service, hash, data, scope \\ nil)
723-
# when service != nil and (is_atom(service) or is_pid(service)) do
724-
# scope = normalize_scope(scope)
725-
726-
# case :ets.member(service, hash) do
727-
# true ->
728-
# {:ok, hash}
729-
730-
# false ->
731-
# GenServer.call(service, {:put_new, scope, hash, data})
732-
# end
733-
# end
734-
735579
# --------------------------------------------------------
736580
@doc """
737581
Add a scope to an existing asset in the cache.
@@ -776,7 +620,7 @@ defmodule Scenic.Cache.Base do
776620
processes a chance to claim a scope before it is unloaded.
777621
"""
778622

779-
# returns :ok
623+
# returns `:ok`
780624
@spec release(
781625
service :: atom,
782626
hash :: Scenic.Cache.Base.hash(),
@@ -815,9 +659,11 @@ defmodule Scenic.Cache.Base do
815659
Pass in the service, hash, and a scope.
816660
817661
returns one of:
818-
{:ok, hash} # it is claimed by the given scope
819-
{:ok, :global} # it is NOT claimed by the given scope, but is :global
820-
{:error, :not_found} # it is not in the cache at all
662+
```elixir
663+
{:ok, hash} # it is claimed by the given scope
664+
{:ok, :global} # it is NOT claimed by the given scope, but is :global
665+
{:error, :not_found} # it is not in the cache at all
666+
```
821667
"""
822668
@spec status(
823669
service :: atom,
@@ -855,7 +701,7 @@ defmodule Scenic.Cache.Base do
855701
856702
Pass in the service and a hash.
857703
858-
returns true or false.
704+
returns `true` or `false`.
859705
"""
860706
@spec member?(
861707
service :: atom,
@@ -870,7 +716,7 @@ defmodule Scenic.Cache.Base do
870716
871717
Pass in the service, hash, and scope.
872718
873-
returns true or false.
719+
returns `true` or `false`.
874720
"""
875721
@spec claimed?(
876722
service :: atom,

lib/scenic/cache/dynamic/texture.ex

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,76 @@ defmodule Scenic.Cache.Dynamic.Texture do
99

1010
# import IEx
1111

12+
@moduledoc """
13+
In memory cache for static dynamic Image assets.
14+
15+
In graphics-speak, an image that is being drawn to the screen is a "Texture".
16+
17+
Unlike `Scenic.Cache.Static.Texture`, this module assumes that the pixels to be rendered
18+
in the image are being generated on the at runtime and change, perhaps rapidly.
19+
20+
The primary example is capturing an image off of a camera. This data is generated
21+
on the fly and needs to be presented to the video card with as little encoding being done
22+
as possible.
23+
24+
So instead of using a hash of the data as a key (which doesn't make sense given that the data
25+
is changing...), you pick a unique string to represent the stream of data. It is up to you
26+
to make sure the names you choose are unique in your app.
27+
28+
The only data the `Scenic.Cache.Dynamic.Texture` accepts is a raw pixel map. Please use the `Scenic.Utilities.Texture`
29+
module to help build and manage these data sets.
30+
31+
## Goals
32+
33+
Given this situation, the dynamic Cache module has multiple goals.
34+
* __Reuse__ - assets used by multiple scenes should only be stored in memory once
35+
* __Copy time__ - assets are stored in ETS, so they don't need to be copied as they are used
36+
* __Pub/Sub__ - Consumers of static assets (drivers...) should be notified when an asset is
37+
loaded or changed. They should not poll the system.
38+
39+
## Scope
40+
41+
When a texture term is loaded into the cache, it is assigned a scope.
42+
The scope is used to
43+
determine how long to hold the asset in memory before it is unloaded. Scope is either
44+
the atom `:global`, or a `pid`.
45+
46+
The typical flow is that a scene will load a font into the cache. A scope is automatically
47+
defined that tracks the asset against the pid of the scene that loaded it. When the scene
48+
is closed, the scope becomes empty and the asset is unloaded.
49+
50+
If, while that scene is loaded, another scene (or any process...) attempts to load
51+
the same asset into the cache, a second scope is added and the duplicate load is
52+
skipped. When the first scene closes, the asset stays in memory as long as the second
53+
scope remains valid.
54+
55+
When a scene closes, it's scope stays valid for a short time in order to give the next
56+
scene a chance to load its assets (or claim a scope) and possibly re-use the already
57+
loaded assets.
58+
59+
This is also useful in the event of a scene crashing and being restarted. The delay
60+
in unloading the scope means that the replacement scene will use already loaded
61+
assets instead of loading the same files again for no real benefit.
62+
63+
When you load assets you can alternately provide your own scope instead of taking the
64+
default, which is your processes pid. If you provide `:global`, then the asset will
65+
stay in memory until you explicitly release it.
66+
67+
## Pub/Sub
68+
69+
Drivers (or any process...) listen to the font
70+
Cache via a simple pub/sub api.
71+
72+
Because the graph, may be computed during compile time and pushed at some
73+
other time than the assets are loaded, the drivers need to know when the assets
74+
become available.
75+
76+
Whenever any asset is loaded into the cache, messages are sent to any
77+
subscribing processes along with the affected keys. This allows them to react in a
78+
loosely-coupled way to how the assets are managed in your scene.
79+
80+
"""
81+
1282
# --------------------------------------------------------
1383
def put(key, data, opts) do
1484
case validate(data) do

0 commit comments

Comments
 (0)