1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18package android.filterfw.core;
19
20import android.filterfw.core.Frame;
21import android.filterfw.core.FrameFormat;
22import android.filterfw.core.SimpleFrameManager;
23
24import java.util.Map;
25import java.util.SortedMap;
26import java.util.TreeMap;
27
28/**
29 * @hide
30 */
31public class CachedFrameManager extends SimpleFrameManager {
32
33    private SortedMap<Integer, Frame> mAvailableFrames;
34    private int mStorageCapacity = 24 * 1024 * 1024; // Cap default storage to 24MB
35    private int mStorageSize = 0;
36    private int mTimeStamp = 0;
37
38    public CachedFrameManager() {
39        super();
40        mAvailableFrames = new TreeMap<Integer, Frame>();
41    }
42
43    @Override
44    public Frame newFrame(FrameFormat format) {
45        Frame result = findAvailableFrame(format, Frame.NO_BINDING, 0);
46        if (result == null) {
47            result = super.newFrame(format);
48        }
49        result.setTimestamp(Frame.TIMESTAMP_NOT_SET);
50        return result;
51    }
52
53    @Override
54    public Frame newBoundFrame(FrameFormat format, int bindingType, long bindingId) {
55        Frame result = findAvailableFrame(format, bindingType, bindingId);
56        if (result == null) {
57            result = super.newBoundFrame(format, bindingType, bindingId);
58        }
59        result.setTimestamp(Frame.TIMESTAMP_NOT_SET);
60        return result;
61    }
62
63    @Override
64    public Frame retainFrame(Frame frame) {
65        return super.retainFrame(frame);
66    }
67
68    @Override
69    public Frame releaseFrame(Frame frame) {
70        if (frame.isReusable()) {
71            int refCount = frame.decRefCount();
72            if (refCount == 0 && frame.hasNativeAllocation()) {
73                if (!storeFrame(frame)) {
74                    frame.releaseNativeAllocation();
75                }
76                return null;
77            } else if (refCount < 0) {
78                throw new RuntimeException("Frame reference count dropped below 0!");
79            }
80        } else {
81            super.releaseFrame(frame);
82        }
83        return frame;
84    }
85
86    public void clearCache() {
87        for (Frame frame : mAvailableFrames.values()) {
88            frame.releaseNativeAllocation();
89        }
90        mAvailableFrames.clear();
91    }
92
93    @Override
94    public void tearDown() {
95        clearCache();
96    }
97
98    private boolean storeFrame(Frame frame) {
99        synchronized(mAvailableFrames) {
100            // Make sure this frame alone does not exceed capacity
101            int frameSize = frame.getFormat().getSize();
102            if (frameSize > mStorageCapacity) {
103                return false;
104            }
105
106            // Drop frames if adding this frame would exceed capacity
107            int newStorageSize = mStorageSize + frameSize;
108            while (newStorageSize > mStorageCapacity) {
109                dropOldestFrame();
110                newStorageSize = mStorageSize + frameSize;
111            }
112
113            // Store new frame
114            frame.onFrameStore();
115            mStorageSize = newStorageSize;
116            mAvailableFrames.put(mTimeStamp, frame);
117            ++mTimeStamp;
118            return true;
119        }
120    }
121
122    private void dropOldestFrame() {
123        int oldest = mAvailableFrames.firstKey();
124        Frame frame = mAvailableFrames.get(oldest);
125        mStorageSize -= frame.getFormat().getSize();
126        frame.releaseNativeAllocation();
127        mAvailableFrames.remove(oldest);
128    }
129
130    private Frame findAvailableFrame(FrameFormat format, int bindingType, long bindingId) {
131        // Look for a frame that is compatible with the requested format
132        synchronized(mAvailableFrames) {
133            for (Map.Entry<Integer, Frame> entry : mAvailableFrames.entrySet()) {
134                Frame frame = entry.getValue();
135                // Check that format is compatible
136                if (frame.getFormat().isReplaceableBy(format)) {
137                    // Check that binding is compatible (if frame is bound)
138                    if ((bindingType == frame.getBindingType())
139                        && (bindingType == Frame.NO_BINDING || bindingId == frame.getBindingId())) {
140                        // We found one! Take it out of the set of available frames and attach the
141                        // requested format to it.
142                        super.retainFrame(frame);
143                        mAvailableFrames.remove(entry.getKey());
144                        frame.onFrameFetch();
145                        frame.reset(format);
146                        mStorageSize -= format.getSize();
147                        return frame;
148                    }
149                }
150            }
151        }
152        return null;
153    }
154
155}
156