Skip to content

Commit 6a28540

Browse files
author
liply
authored
Merge pull request #13 from rpgtkoolmv/memory-bloat
fix memory bloat, and background preload.
2 parents 5a95385 + 3cf8526 commit 6a28540

File tree

18 files changed

+639
-67
lines changed

18 files changed

+639
-67
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,5 @@ jspm_packages
5151
.idea
5252
dist/
5353
*.iml
54-
game/
54+
game/**/*
5555
todo.md

js/rpg_core/Bitmap.js

Lines changed: 117 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,36 @@ function Bitmap() {
1111
this.initialize.apply(this, arguments);
1212
}
1313

14+
15+
/**
16+
* Bitmap states(Bitmap._loadingState):
17+
*
18+
* none:
19+
* Empty Bitmap
20+
*
21+
* pending:
22+
* Url requested, but pending to load until startRequest called
23+
*
24+
* requesting:
25+
* Requesting supplied URI now.
26+
*
27+
* requestCompleted:
28+
* Request completed
29+
*
30+
* decrypting:
31+
* requesting encrypted data from supplied URI or decrypting it.
32+
*
33+
* decryptCompleted:
34+
* Decrypt completed
35+
*
36+
* loaded:
37+
* loaded. isReady() === true, so It's usable.
38+
*
39+
* error:
40+
* error occurred
41+
*
42+
*/
43+
1444
Bitmap.prototype.initialize = function(width, height) {
1545
this._canvas = document.createElement('canvas');
1646
this._context = this._canvas.getContext('2d');
@@ -24,8 +54,8 @@ Bitmap.prototype.initialize = function(width, height) {
2454
this._paintOpacity = 255;
2555
this._smooth = false;
2656
this._loadListeners = [];
27-
this._isLoading = false;
28-
this._hasError = false;
57+
this._loadingState = 'none';
58+
this._decodeAfterRequest = false;
2959

3060
/**
3161
* Cache entry, for images. In all cases _url is the same as cacheEntry.key
@@ -92,17 +122,8 @@ Bitmap.prototype.initialize = function(width, height) {
92122
*/
93123
Bitmap.load = function(url) {
94124
var bitmap = new Bitmap();
95-
bitmap._image = new Image();
96-
bitmap._url = url;
97-
bitmap._isLoading = true;
98-
99-
if(!Decrypter.checkImgIgnore(url) && Decrypter.hasEncryptedImages) {
100-
Decrypter.decryptImg(url, bitmap);
101-
} else {
102-
bitmap._image.src = url;
103-
bitmap._image.onload = Bitmap.prototype._onLoad.bind(bitmap);
104-
bitmap._image.onerror = Bitmap.prototype._onError.bind(bitmap);
105-
}
125+
bitmap._decodeAfterRequest = true;
126+
bitmap._requestImage(url);
106127

107128
return bitmap;
108129
};
@@ -146,7 +167,7 @@ Bitmap.snap = function(stage) {
146167
* @return {Boolean} True if the bitmap is ready to render
147168
*/
148169
Bitmap.prototype.isReady = function() {
149-
return !this._isLoading;
170+
return this._loadingState === 'loaded' || this._loadingState === 'none';
150171
};
151172

152173
/**
@@ -156,7 +177,7 @@ Bitmap.prototype.isReady = function() {
156177
* @return {Boolean} True if a loading error has occurred
157178
*/
158179
Bitmap.prototype.isError = function() {
159-
return this._hasError;
180+
return this._loadingState === 'error';
160181
};
161182

162183
/**
@@ -229,7 +250,7 @@ Object.defineProperty(Bitmap.prototype, 'context', {
229250
*/
230251
Object.defineProperty(Bitmap.prototype, 'width', {
231252
get: function() {
232-
return this._isLoading ? 0 : this._canvas.width;
253+
return !this.isReady() ? 0 : this._canvas.width;
233254
},
234255
configurable: true
235256
});
@@ -242,7 +263,7 @@ Object.defineProperty(Bitmap.prototype, 'width', {
242263
*/
243264
Object.defineProperty(Bitmap.prototype, 'height', {
244265
get: function() {
245-
return this._isLoading ? 0 : this._canvas.height;
266+
return !this.isReady() ? 0 : this._canvas.height;
246267
},
247268
configurable: true
248269
});
@@ -690,7 +711,7 @@ Bitmap.prototype.blur = function() {
690711
* @param {Function} listner The callback function
691712
*/
692713
Bitmap.prototype.addLoadListener = function(listner) {
693-
if (this._isLoading) {
714+
if (!this.isReady()) {
694715
this._loadListeners.push(listner);
695716
} else {
696717
listner();
@@ -741,14 +762,44 @@ Bitmap.prototype._drawTextBody = function(text, tx, ty, maxWidth) {
741762
* @private
742763
*/
743764
Bitmap.prototype._onLoad = function() {
744-
if(Decrypter.hasEncryptedImages) {
745-
window.URL.revokeObjectURL(this._image.src);
765+
switch(this._loadingState){
766+
case 'requesting':
767+
this._loadingState = 'requestCompleted';
768+
if(this._decodeAfterRequest){
769+
this.decode();
770+
}
771+
break;
772+
773+
case 'decrypting':
774+
window.URL.revokeObjectURL(this._image.src);
775+
this._loadingState = 'decryptCompleted';
776+
if(this._decodeAfterRequest){
777+
this.decode();
778+
}
779+
break;
780+
}
781+
};
782+
783+
Bitmap.prototype.decode = function(){
784+
switch(this._loadingState){
785+
case 'requestCompleted': case 'decryptCompleted':
786+
this.resize(this._image.width, this._image.height);
787+
this._context.drawImage(this._image, 0, 0);
788+
this._loadingState = 'loaded';
789+
790+
this._setDirty();
791+
this._callLoadListeners();
792+
break;
793+
794+
case 'requesting': case 'decrypting':
795+
this._decodeAfterRequest = true;
796+
break;
797+
798+
case 'pending':
799+
this._decodeAfterRequest = true;
800+
this._requestImage(this._url);
801+
break;
746802
}
747-
this._isLoading = false;
748-
this.resize(this._image.width, this._image.height);
749-
this._context.drawImage(this._image, 0, 0);
750-
this._setDirty();
751-
this._callLoadListeners();
752803
};
753804

754805
/**
@@ -767,7 +818,7 @@ Bitmap.prototype._callLoadListeners = function() {
767818
* @private
768819
*/
769820
Bitmap.prototype._onError = function() {
770-
this._hasError = true;
821+
this._loadingState = 'error';
771822
};
772823

773824
/**
@@ -788,3 +839,43 @@ Bitmap.prototype.checkDirty = function() {
788839
this._dirty = false;
789840
}
790841
};
842+
843+
Bitmap.request = function(url){
844+
var bitmap = new Bitmap();
845+
bitmap._url = url;
846+
bitmap._loadingState = 'pending';
847+
848+
return bitmap;
849+
};
850+
851+
Bitmap.prototype._requestImage = function(url){
852+
this._image = new Image();
853+
this._url = url;
854+
this._loadingState = 'requesting';
855+
856+
if(!Decrypter.checkImgIgnore(url) && Decrypter.hasEncryptedImages) {
857+
this._loadingState = 'decrypting';
858+
Decrypter.decryptImg(url, bitmap);
859+
} else {
860+
this._image.src = url;
861+
this._image.onload = Bitmap.prototype._onLoad.bind(this);
862+
this._image.onerror = Bitmap.prototype._onError.bind(this);
863+
}
864+
};
865+
866+
Bitmap.prototype.isRequestOnly = function(){
867+
return !(this._decodeAfterRequest || this.isReady());
868+
};
869+
870+
Bitmap.prototype.isRequestReady = function(){
871+
return this._loadingState !== 'pending' &&
872+
this._loadingState !== 'requesting' &&
873+
this._loadingState !== 'decrypting';
874+
};
875+
876+
Bitmap.prototype.startRequest = function(){
877+
if(this._loadingState === 'pending'){
878+
this._decodeAfterRequest = false;
879+
this._requestImage(this._url);
880+
}
881+
};

js/rpg_core/Graphics.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,10 @@ Graphics._createRenderer = function() {
814814
this._renderer = PIXI.autoDetectRenderer(width, height, options);
815815
break;
816816
}
817+
818+
if(this._renderer && this._renderer.textureGC)
819+
this._renderer.textureGC.maxIdle = 1;
820+
817821
} catch (e) {
818822
this._renderer = null;
819823
}

js/rpg_core/ImageCache.js

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
function ImageCache(){
2+
this.initialize.apply(this, arguments);
3+
}
4+
5+
ImageCache.limit = 20 * 1000 * 1000;
6+
7+
ImageCache.prototype.initialize = function(){
8+
this._items = {};
9+
};
10+
11+
ImageCache.prototype.add = function(key, value){
12+
this._items[key] = {
13+
bitmap: value,
14+
touch: Date.now(),
15+
key: key
16+
};
17+
18+
this._truncateCache();
19+
};
20+
21+
ImageCache.prototype.get = function(key){
22+
if(this._items[key]){
23+
var item = this._items[key];
24+
item.touch = Date.now();
25+
return item.bitmap;
26+
}
27+
28+
return null;
29+
};
30+
31+
ImageCache.prototype.reserve = function(key, value, reservationId){
32+
if(!this._items[key]){
33+
this._items[key] = {
34+
bitmap: value,
35+
touch: Date.now(),
36+
key: key
37+
};
38+
}
39+
40+
this._items[key].reservationId = reservationId;
41+
};
42+
43+
ImageCache.prototype.releaseReservation = function(reservationId){
44+
var items = this._items;
45+
46+
Object.keys(items)
47+
.map(function(key){return items[key];})
48+
.forEach(function(item){
49+
if(item.reservationId === reservationId){
50+
delete item.reservationId;
51+
}
52+
});
53+
};
54+
55+
ImageCache.prototype._truncateCache = function(){
56+
var items = this._items;
57+
var sizeLeft = ImageCache.limit;
58+
59+
Object.keys(items).map(function(key){
60+
return items[key];
61+
}).sort(function(a, b){
62+
return b.touch - a.touch;
63+
}).forEach(function(item){
64+
if(sizeLeft > 0 || this._mustBeHeld(item)){
65+
var bitmap = item.bitmap;
66+
sizeLeft -= bitmap.width * bitmap.height;
67+
}else{
68+
delete items[item.key];
69+
}
70+
}.bind(this));
71+
};
72+
73+
ImageCache.prototype._mustBeHeld = function(item){
74+
// request only is weak so It's purgeable
75+
if(item.bitmap.isRequestOnly()) return false;
76+
// reserved item must be held
77+
if(item.reservationId) return true;
78+
// not ready bitmap must be held (because of checking isReady())
79+
if(!item.bitmap.isReady()) return true;
80+
// then the item may purgeable
81+
return false;
82+
};
83+
84+
ImageCache.prototype.isReady = function(){
85+
var items = this._items;
86+
return !Object.keys(items).some(function(key){
87+
return !items[key].bitmap.isRequestOnly() && !items[key].bitmap.isReady();
88+
});
89+
};
90+
91+
ImageCache.prototype.getErrorBitmap = function(){
92+
var items = this._items;
93+
var bitmap = null;
94+
if(!Object.keys(items).some(function(key){
95+
if(items[key].bitmap.isError()){
96+
bitmap = items[key].bitmap;
97+
return true;
98+
}
99+
return false;
100+
})) {
101+
return bitmap;
102+
}
103+
104+
return null;
105+
};

js/rpg_core/RequestQueue.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
function RequestQueue(){
2+
this.initialize.apply(this, arguments);
3+
}
4+
5+
RequestQueue.prototype.initialize = function(){
6+
this._queue = [];
7+
};
8+
9+
RequestQueue.prototype.enqueue = function(key, value){
10+
this._queue.push({
11+
key: key,
12+
value: value,
13+
});
14+
};
15+
16+
RequestQueue.prototype.update = function(){
17+
if(this._queue.length === 0) return;
18+
19+
var top = this._queue[0];
20+
if(top.value.isRequestReady()){
21+
this._queue.shift();
22+
if(this._queue.length !== 0){
23+
this._queue[0].value.startRequest();
24+
}
25+
}else{
26+
top.value.startRequest();
27+
}
28+
};
29+
30+
RequestQueue.prototype.raisePriority = function(key){
31+
for(var n = 0; n < this._queue.length; n++){
32+
var item = this._queue[n];
33+
if(item.key === key){
34+
this._queue.splice(n, 1);
35+
this._queue.unshift(item);
36+
break;
37+
}
38+
}
39+
};
40+
41+
RequestQueue.prototype.clear = function(){
42+
this._queue.splice(0);
43+
};

js/rpg_core/Utils.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,8 @@ Utils.rgbToCssColor = function(r, g, b) {
125125
b = Math.round(b);
126126
return 'rgb(' + r + ',' + g + ',' + b + ')';
127127
};
128+
129+
Utils._id = 1;
130+
Utils.generateRuntimeId = function(){
131+
return Utils._id++;
132+
};

0 commit comments

Comments
 (0)