Fork of https://github.com/modality/charred-black.
Short term, has some fixes.
Long term, may include a tool to create and edit stock/lifepath/skill/trait data.
http://charred.obscuritus.ca:8080/#/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
230 lines
5.6 KiB
230 lines
5.6 KiB
# A really simple MRU cache, especially for session[:id] so we don't have
|
|
# to go to Couch to get the account information.
|
|
#
|
|
# In you Sinatra app, simple declare this in your application
|
|
#
|
|
# def self.cache
|
|
# @@cache ||= Mu::Cache.new :max_size => 1024, :max_time => 30.0
|
|
# end
|
|
#
|
|
# and then wrap your cachable Couch-queries or HAML fragments with
|
|
#
|
|
# Application.cache.fetch session[:id] do
|
|
# couch.get session[:id] rescue nil
|
|
# end
|
|
module Mu
|
|
class Cache
|
|
# Each entry in the cache that simultaneously exists both in the
|
|
# hash as well in the list below giving us O(1) deletes
|
|
class Entry
|
|
attr_accessor :key, :val, :next, :prev, :time
|
|
|
|
def initialize key=nil, val=nil
|
|
@time = Time.now
|
|
@key = key
|
|
@val = val
|
|
end
|
|
|
|
def inspect
|
|
"#<Cache::Entry #{key}=#{val}>"
|
|
end
|
|
end
|
|
|
|
# A simple doubly-linked list of entries with basic operations to
|
|
# push/pop/shift and delete a specific entry.
|
|
class List
|
|
attr_reader :head
|
|
attr_reader :tail
|
|
|
|
def push entry
|
|
if not @tail
|
|
@head = @tail = entry
|
|
else
|
|
@tail.next = entry
|
|
entry.prev = @tail
|
|
entry.next = nil
|
|
@tail = entry
|
|
end
|
|
end
|
|
|
|
alias_method :<<, :push
|
|
|
|
def pop
|
|
return nil if not @tail
|
|
|
|
entry = @tail
|
|
@tail = entry.prev
|
|
if @tail
|
|
@tail.next = nil
|
|
else
|
|
@head = nil
|
|
end
|
|
|
|
entry.next = nil
|
|
entry.prev = nil
|
|
return entry
|
|
end
|
|
|
|
def shift
|
|
return nil if not @head
|
|
|
|
entry = @head
|
|
@head = entry.next
|
|
if @head
|
|
@head.prev = nil
|
|
else
|
|
@tail = nil
|
|
end
|
|
|
|
entry.next = nil
|
|
entry.prev = nil
|
|
return entry
|
|
end
|
|
|
|
def delete entry
|
|
if not entry.prev and not entry.next
|
|
@head = @tail = nil
|
|
return
|
|
end
|
|
|
|
if not entry.prev and entry.next
|
|
@head = entry.next
|
|
entry.next.prev = nil
|
|
entry.next = nil
|
|
return
|
|
end
|
|
|
|
if entry.prev and not entry.next
|
|
@tail = entry.prev
|
|
entry.prev.next = nil
|
|
entry.prev = nil
|
|
return
|
|
end
|
|
|
|
entry.prev.next = entry.next
|
|
entry.next.prev = entry.prev
|
|
entry.next = nil
|
|
entry.prev = nil
|
|
end
|
|
|
|
def clear
|
|
@head = @tail = nil
|
|
end
|
|
end
|
|
|
|
attr_reader :max_size, :max_time
|
|
|
|
def initialize opts={}
|
|
@max_size = opts.fetch :max_size, 1024
|
|
@max_time = opts.fetch :max_time, 1.0
|
|
@hash = Hash.new
|
|
@list = List.new
|
|
end
|
|
|
|
def clear
|
|
hash.clear
|
|
list.clear
|
|
end
|
|
|
|
def size
|
|
hash.size
|
|
end
|
|
|
|
# Forcefully delete an entry from the cache. Returns the value of the
|
|
# deleted key, if one exists.
|
|
def delete key
|
|
entry = hash.delete key
|
|
if entry
|
|
list.delete entry
|
|
return entry.val
|
|
end
|
|
return nil
|
|
end
|
|
|
|
# Store the key => val into the cache. If it's already there, then just
|
|
# update the entry and move it to the back. Otherwise insert a new
|
|
# entry and move it to the back
|
|
def store key, val
|
|
purge
|
|
entry = hash[key]
|
|
if entry
|
|
update entry, val
|
|
else
|
|
insert key, val
|
|
end
|
|
return val
|
|
end
|
|
|
|
def member? key
|
|
purge
|
|
hash.member? key
|
|
end
|
|
|
|
alias_method :[]=, :store
|
|
|
|
# Look up the key in the cache and if it's there refresh it. Otherwise
|
|
# yield to get the value and insert that into the cache. The usage is
|
|
# as follows:
|
|
# val = cache.fetch key do
|
|
# some expensive operation that returns val
|
|
# end
|
|
def fetch key, refresh=true, &block
|
|
purge
|
|
entry = hash[key]
|
|
|
|
if entry
|
|
update entry if refresh
|
|
return entry.val
|
|
end
|
|
|
|
val = yield rescue nil
|
|
return if not val
|
|
|
|
insert key, val
|
|
return val
|
|
end
|
|
|
|
private
|
|
|
|
# If the cache is getting too long, then remove the oldest entry (which
|
|
# is in the front of the list)
|
|
def purge opts={}
|
|
size = opts.fetch :max_size, @max_size
|
|
time = opts.fetch :max_time, @max_time
|
|
|
|
# Make sure that the cache doesn't grow beyond a certain size, no
|
|
# matter how recent the entries are
|
|
hash.delete list.shift.key while hash.size > size
|
|
|
|
# Then purge entries in the list that've been there longer than a
|
|
# certain duration
|
|
now = Time.now
|
|
while not hash.empty? and now - list.head.time >= time
|
|
hash.delete list.shift.key
|
|
end
|
|
end
|
|
|
|
# Update an entry with an optionally new value. This moves the the
|
|
# entry to the back of the queue so that it doesn't "expire" during
|
|
# the next purge. This also assumes that the entry is not present
|
|
# in the cache.
|
|
def update entry, val=nil
|
|
entry.time = Time.now
|
|
entry.val = val if val
|
|
list.delete entry
|
|
list << entry
|
|
return entry
|
|
end
|
|
|
|
# Inserts a new entry to the "back" of the queue. This assumes that
|
|
# the entry does not already exist in the hash
|
|
def insert key, val
|
|
entry = Entry.new(key, val)
|
|
hash[key] = entry
|
|
list << entry
|
|
return entry
|
|
end
|
|
|
|
attr_reader :hash, :list
|
|
end
|
|
end # Mu |