Structure the image cache as a state machine

This commit is contained in:
Brian Anderson 2012-08-12 16:09:30 -07:00
parent 05fd04ac8d
commit 91b566cffd

View file

@ -36,30 +36,26 @@ fn image_cache_task(resource_task: ResourceTask) -> ImageCacheTask {
ImageCache { ImageCache {
resource_task: resource_task, resource_task: resource_task,
from_client: from_client, from_client: from_client,
prefetch_map: url_map(), state_map: url_map()
future_image_map: url_map(),
image_map: url_map(),
error_map: url_map()
}.run(); }.run();
} }
} }
// FIXME: All these maps just represent states in the image lifecycle.
// Would be more efficient to just have a single state_map.
struct ImageCache { struct ImageCache {
/// A handle to the resource task for fetching the image binaries /// A handle to the resource task for fetching the image binaries
resource_task: ResourceTask; resource_task: ResourceTask;
/// The port on which we'll receive client requests /// The port on which we'll receive client requests
from_client: port<Msg>; from_client: port<Msg>;
/// A map from URLs to the partially loaded compressed image data. /// The state of processsing an image for a URL
/// Once the data is complete it is then sent to a decoder state_map: UrlMap<ImageState>;
prefetch_map: UrlMap<@PrefetchData>; }
/// A list of clients waiting on images that are currently being decoded
future_image_map: UrlMap<@FutureData>; enum ImageState {
/// The cache of decoded images Init,
image_map: UrlMap<@arc<~Image>>; Prefetching(@PrefetchData),
/// Map of images which could not be obtained Decoding(@FutureData),
error_map: UrlMap<()>; Decoded(@arc<~Image>),
Failed
} }
struct PrefetchData { struct PrefetchData {
@ -86,59 +82,46 @@ impl ImageCache {
} }
} }
/*priv*/ fn get_state(url: url) -> ImageState {
match self.state_map.find(copy url) {
some(state) => state,
none => Init
}
}
/*priv*/ fn set_state(url: url, state: ImageState) {
self.state_map.insert(copy url, state);
}
/*priv*/ fn prefetch(url: url) { /*priv*/ fn prefetch(url: url) {
if self.error_map.contains_key(copy url) { match self.get_state(url) {
// We already know this image can't be used Init => {
return let response_port = port();
self.resource_task.send(resource_task::Load(copy url, response_port.chan()));
let prefetch_data = @PrefetchData {
response_port: response_port,
data: ~[]
};
self.set_state(url, Prefetching(prefetch_data));
}
Prefetching(*)
| Decoding(*)
| Decoded(*)
| Failed => {
// We've already begun working on this image
}
} }
if self.image_map.contains_key(copy url) {
// We've already decoded this image
return
}
if self.future_image_map.contains_key(copy url) {
// We've already begun decoding this image
return
}
if self.prefetch_map.contains_key(copy url) {
// We're already waiting for this image
return
}
let response_port = port();
self.resource_task.send(resource_task::Load(copy url, response_port.chan()));
let prefetch_data = @PrefetchData {
response_port: response_port,
data: ~[]
};
self.prefetch_map.insert(copy url, prefetch_data);
} }
/*priv*/ fn get_image(url: url, response: chan<ImageResponseMsg>) { /*priv*/ fn get_image(url: url, response: chan<ImageResponseMsg>) {
match self.image_map.find(copy url) {
some(image) => {
response.send(ImageReady(clone_arc(image)));
return
}
none => ()
}
match self.future_image_map.find(copy url) { match self.get_state(url) {
some(future_data) => { Init => fail ~"Request for image before prefetch",
// We've started decoding this image but haven't recieved it back yet.
// Put this client on the wait list
vec::push(future_data.waiters, response);
return
}
none => ()
}
match self.prefetch_map.find(copy url) { Prefetching(prefetch_data) => {
some(prefetch_data) => {
let mut image_sent = false; let mut image_sent = false;
@ -166,8 +149,7 @@ impl ImageCache {
waiters: ~[] waiters: ~[]
}; };
self.prefetch_map.remove(copy url); self.set_state(copy url, Decoding(future_data));
self.future_image_map.insert(copy url, future_data);
image_sent = true; image_sent = true;
break; break;
@ -176,8 +158,7 @@ impl ImageCache {
// There was an error loading the image binary. Put it // There was an error loading the image binary. Put it
// in the error map so we remember the error for future // in the error map so we remember the error for future
// requests. // requests.
self.prefetch_map.remove(copy url); self.set_state(copy url, Failed);
self.error_map.insert(copy url, ());
break; break;
} }
} }
@ -186,24 +167,28 @@ impl ImageCache {
if !image_sent { if !image_sent {
response.send(ImageNotReady); response.send(ImageNotReady);
} }
return;
} }
none => ()
}
match self.error_map.find(copy url) { Decoding(future_data) => {
some(*) => { // We've started decoding this image but haven't recieved it back yet.
// Put this client on the wait list
vec::push(future_data.waiters, response);
}
Decoded(image) => {
response.send(ImageReady(clone_arc(image)));
}
Failed => {
response.send(ImageNotReady); response.send(ImageNotReady);
return;
} }
none => fail ~"got a request for image data without prefetch"
} }
} }
/*priv*/ fn store_image(url: url, image: &arc<~Image>) { /*priv*/ fn store_image(url: url, image: &arc<~Image>) {
match self.future_image_map.find(copy url) {
some(future_data) => { match self.get_state(url) {
Decoding(future_data) => {
let mut waiters = ~[]; let mut waiters = ~[];
waiters <-> future_data.waiters; waiters <-> future_data.waiters;
@ -213,11 +198,18 @@ impl ImageCache {
for waiters.each |waiter| { for waiters.each |waiter| {
waiter.send(ImageReady(clone_arc(image))) waiter.send(ImageReady(clone_arc(image)))
} }
self.image_map.insert(copy url, @clone_arc(image));
self.future_image_map.remove(copy url); self.set_state(url, Decoded(@clone_arc(image)));
}
Init
| Prefetching(*)
| Decoded(*)
| Failed => {
fail ~"incorrect state in store_image"
} }
none => fail ~"storing an image that isn't in the future map"
} }
} }
} }