root/trunk/common/Cache.js

Revision 159, 8.0 kB (checked in by ydnar, 3 years ago)

added line

  • Property svn:keywords set to Id
Line 
1/*
2LRU Cache Library
3$Id$
4
5Copyright (c) 2005, Six Apart, Ltd.
6All rights reserved.
7
8Redistribution and use in source and binary forms, with or without
9modification, are permitted provided that the following conditions are
10met:
11
12    * Redistributions of source code must retain the above copyright
13notice, this list of conditions and the following disclaimer.
14
15    * Redistributions in binary form must reproduce the above
16copyright notice, this list of conditions and the following disclaimer
17in the documentation and/or other materials provided with the
18distribution.
19
20    * Neither the name of "Six Apart" nor the names of its
21contributors may be used to endorse or promote products derived from
22this software without specific prior written permission.
23
24THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
36*/
37
38Cache = new Class( Object, {
39    maxLength: 100,
40    hits: 0,
41    misses: 0,
42   
43   
44    init: function( maxLength ) {
45        if ( maxLength > 0 )
46            this.maxLength = maxLength;
47        this.flush();
48    },
49
50
51    /* public interface */
52
53    /* flush the cache */
54    flush: function() {
55        /* xxx should this clear the hits and miss? */
56       
57        this.length = 0;
58       
59        /* least recently used */
60        this.LRU = 0;
61       
62        /* most recently used */
63        this.MRU = 0;
64       
65        /* idx to position in value,prev,next arrays */
66        this.IDX = {};
67       
68        /* each node has the same offset in: */
69        this.KEY = [];
70        this.VALUE = [];
71        this.PREV = [];
72        this.NEXT = [];
73        this.DELETE = [];
74    },
75
76
77    getItemsOrdered: function( offset, count ) {
78        offset = offset || 0;
79        count = count || this.length;
80
81        var keys = [];
82        var values = [];
83       
84        var idx = this.MRU;
85        var c = 0;
86        for( var i = 0; i < this.length; i++ ) {
87            /* walk until it reaches the needed offset */
88            if ( i < offset ) {
89                idx = this.NEXT[ idx ];
90                continue;
91            }
92           
93            keys.push( this.KEY[ idx ] );
94            values.push( this.VALUE[ idx ] );
95           
96            c++;
97            /* keep pulling keys/values until count is reached, or end of array */
98            if ( c >= count )
99                break;
100           
101            idx = this.NEXT[ idx ];
102        }
103
104        return [ keys, values ];
105    },
106
107
108    getItems_: function() {
109        /* xxx does not account for order AND undef values due to deletion */
110        /* use getItemsOrdered if that behavior is needed */
111        return [ this.KEY, this.VALUE ];
112    },
113
114
115    deleteItem: function( key ) {
116        if ( !this.IDX.hasOwnProperty( key ) )
117            return undefined;
118        var value = this.VALUE[ this.IDX[ key ] ];
119        this.deleteNode( key );
120        return value;
121    },
122
123
124    setItem: function( key, value) {
125        if ( !defined( key ) || !defined( value ) )
126            return undefined;
127       
128        var idx;
129        if ( this.IDX.hasOwnProperty( key ) )
130            idx = this.deleteNode( key );
131        else if ( this.length >= this.maxLength && defined( this.KEY[ this.LRU ] ) )
132            idx = this.deleteNode( this.KEY[ this.LRU ] );
133        else if ( this.KEY.length && this.KEY[ this.LRU ] == undefined )
134            idx = this.LRU;
135           
136        this.insertNode( key, value, idx );
137        return value;
138    }, 
139 
140 
141    getItem: function( key ) {
142        /* index of node requested */
143        var idx = this.IDX[ key ];
144           
145        /* does this node exist */
146        if ( !this.IDX.hasOwnProperty( key ) || idx < 0 || idx >= this.length || !defined( this.VALUE[ idx ] ) ) {
147            /* cache miss stat */
148            this.misses++;
149            return undefined;
150        }
151       
152        /* cache hit stat */
153        this.hits++;
154
155        /* move it to the front */
156        this.setMRU( idx );
157       
158        return this.VALUE[ idx ];
159    },
160
161
162    getItems: function( ids ) {
163       
164        var items = [];
165        for ( var i = 0; i < ids.length; i++ ) {
166            var item = this.getItem( ids[ i ] );
167            if ( item )
168                items.push( item );
169        }
170       
171        return items;
172    },
173
174
175    touchItem: function( key ) {
176        /* index of node requested */
177        var idx = this.IDX[ key ];
178           
179        /* does this node exist */
180        if ( !this.IDX.hasOwnProperty(key) || idx < 0 || idx >= this.length || !defined( this.VALUE[ idx ] ) )
181            return undefined;
182       
183        /* move it to the front */
184        this.setMRU( idx );
185    },
186
187
188    /* private functions */
189
190    setMRU: function( idx ) {
191        var prevnode = this.PREV[ idx ];
192        var nextnode = this.NEXT[ idx ];
193       
194        if (prevnode == -1) {
195            /* this can happen if you select the mru */
196            if (this.MRU != idx)
197                log.error("LRUCache::setMRU idx:" + idx + " has an inconsistent PREV key (PREV:-1 but MRU != idx)");
198            return;
199        }
200       
201        this.connectNodes( prevnode, nextnode );
202           
203        /* make this node the mru by making the current mru a peer of this node */
204        this.PREV[ this.MRU ] = idx;
205        this.NEXT[ idx ] = this.MRU;
206       
207        this.PREV[ idx ] = -1;
208        this.MRU = idx;
209    },
210
211
212    setLRU: function( idx ) {
213        var nextnode = this.NEXT[ idx ];
214        var prevnode = this.PREV[ idx ];
215       
216        if (nextnode == -1) {
217            if (this.LRU != idx)
218                log.error("LRUCache::setLRU  idx:" + idx + " has an inconsistent NEXT key (NEXT:-1 but LRU != idx)");
219            return;
220        }
221       
222        this.connectNodes( prevnode, nextnode );
223       
224        /* move it to the end */
225        this.NEXT[ this.LRU ] = idx;
226        this.PREV[ idx ] = this.LRU;
227       
228        this.NEXT[ idx ] = -1;
229        this.LRU = idx;
230    },
231
232
233    connectNodes: function( prevnode, nextnode ) {
234        /* match peers to each other */
235       
236        if (prevnode == -1)
237            this.MRU = nextnode;
238        else
239            this.NEXT[ prevnode ] = nextnode;
240       
241       
242        if (nextnode == -1)
243            this.LRU = prevnode;
244        else
245            this.PREV[ nextnode ] = prevnode;
246
247    },
248
249
250    /* reposition a nodes peers and return the index */ 
251    deleteNode: function( key ) {
252        var idx = this.IDX[ key ];
253       
254        /* move it to the end */
255        this.setLRU( idx );
256       
257        delete this.IDX[ key ];
258       
259        this.KEY[ idx ] = undefined;
260        this.VALUE[ idx ] = undefined;
261       
262        /* the node isnt actually deleted, it is reused */
263        return idx;
264    },
265
266 
267    insertNode: function( key, value, idx ) {
268        /* insert new node */
269        if ( !defined( idx ) )
270            idx = this.length++;
271       
272        /* move it to the front */
273        this.setMRU( idx );
274       
275        this.VALUE[ idx ] = value;
276        this.KEY[ idx ] = key;
277        this.IDX[ key ] = idx;
278
279        return idx;
280    },
281
282
283    /* for debugging */
284    visualize: function() {
285        var c = this.LRU;
286        log.warn( "LRUCache::visualize MRU: " + c );
287        for( var i = 0; i < this.length; i++ ) {
288            log.warn( "LRUCache::visualize [ " + this.KEY[ c ] + " ] " + c +
289                ((this.LRU == c) ? " - LRU" : "") + ((this.MRU == c) ? " - MRU" : ""));
290            c = this.PREV[ c ];
291        }
292    }
293});
Note: See TracBrowser for help on using the browser.