Pixel Envision Ltd.
Mobile Game Development Studio
  • Home
  • About Us
  • Our Games
    • Hyper-Casual
    • Casual Games
      • Don’t Get Caught
      • Hordes of Enemies
      • noded
      • kubic
      • Whip Swing
      • Witches’ Brew
    • Kids Apps
      • Coloring Book
      • Cars & Trucks Puzzle
      • Train Puzzles for Kids
      • Animal Puzzle
      • Fairy Tale Puzzles for Kids
      • Find the Differences
  • Support
  • Privacy Policy
Select Page ...

Monthly: August, 2011

CloudFlare vs. Incapsula - Benchmark Review

August 29, 2011 Tips & Tricks

As I have noted in my previous blog post, I’ve been testing out few CDN services for use with this website; CloudFlare, Incapsula & Torbit.

Currently, Torbit is in invite only beta. Josh Fraser was kind enough to provide me an access but since their system is not public yet, Torbit is not included in the benchmarks. But I have included a short review based on my recent experience. I’m planing to update this post with benchmark numbers in the future as soon as they are eligible…

How I tested

For the load time benchmarks, I have decided to use WebPageTest.org as it is free, providing actual browser results instead of simulated results and offering enough locations to see global timing. In the advanced settings, I have selected “First View Only” & 5 test runs to make sure content get in to the cache. I have repeated those test for multiple times at the different times of day to make sure server load won’t make a big difference.

Test Location
First View only>
Test runs: 5
InMotion
Business Class
East Coast
CloudFlare
Basic (Free) Account
Medium Security (Default)
Incapsula
Free Account
Trial period (All Features)
Dulles, VA – IE8 – DSL 1.624s 1.567s 1.792s
New York, NY – IE8 – DSL 1.685s 1.596s 1.675s
*Atlanta, GA – IE8 – DSL 1.853s 1.740s N/A
Chicago, IL – IE9 – DSL 3.130s 1.715s 2.032s
Kansas City, MO – IE8 – DSL 2.105s 1.730s 2.158s
San Jose, CA – IE8 – DSL 3.806s 1.704s 2.510s
San Francisco, CA – IE9 – DSL 2.620s 3.090s 2.571s
Los Angeles, CA – IE8 – DSL 2.953s 1.987s 2.150s
Montreal, Canada – IE8 – DSL 2.153s 2.003s 2.107s
São Paulo, Brasil – IE8 – DSL 3.700s 3.292s 3.773s
Dublin, Ireland – IE7 – DSL 4.185s 2.673s 2.604s
Gloucester, UK – IE7 – DSL 4.761s 3.306s 3.330s
London, UK – IE8 – DSL 3.833s 2.265s 2.038s
Paris, FR – IE8 – DSL 2.934s 1.921s 2.209s
Amsterdam, NL – IE8 – DSL 3.826s 2.967s 3.566s
Geneva, Switzerland – IE8 – DSL 3.350s 2.332s 3.239s
Frankfurt, DE – IE8 – DSL 4.536s 2.225s 3.591s
Stockholm, Sweden – IE8 – DSL 3.019s 1.903s 3.894s
**Israel – IE8 – DSL 4.446s 403 Error 2.189s
**Nairobi, Kenya – IE8 29.327s 403 Error 35.014s
Delhi, India – IE7 – DSL 8.699s 4.494s 9.532s
Bangalore, India – IE8 – DSL 10.470s 5.442s 5.825s
Hyderabad, India – IE8 – DSL 6.978s 4.787s 4.233s
Moscow, Russia – IE8 – DSL 3.447s 2.280s 3.531s
Singapore – IE8 – DSL 6.022s 4.712s 6.192s
Taipei, Taiwan – IE9 – DSL 5.045s 2.857s 4.009s
***Jiangsu, China – IE7 – DSL 3.728s 2.447s 8.083s
Seoul, Korea – IE 7 – DSL 7.080s 3.059s 7.240s
Tokyo, Japan – IE9 – DSL 4.288s 2.464s 3.803s
Sydney, AU – IE7 – DSL 8.211s 5.011s 7.412s
Wellington, NZ – IE8 – DSL 5.527s 4.072s 5.520s

Remarks

* Atlanta was not available when I did the Incapsula tests.
** CloudFlare returns 403 Error for those locations when using default (MEDIUM) security settings. Setting it to LOW solves that problem.
*** YouTube access blocked in China, so our current home page cannot fully load. Because of that I have used “Start Render” times instead of “Page Load”.

Torbit Review

http://torbit.com

Torbit works in a different way than the other two services. First of all, they do not offer any security features but purely focused on optimizing and speed.

They are offering some unique optimizations such as HTML5 “Local Storage” or “DOM Storage”. Check out the blog post by Josh or read more about Local Storage.

They have a similar DNS based setup as other two but their CDN system works in the old fashioned way. Once they have your JS, CSS & images loaded in to their cache, URL’s pointing those files will be re-written to use their domains. When the browser connection limits are low (for example, it was 2 for IE7) that is very useful as it allows more simultaneous downloads which means faster page loads. But that is not the case with newer browsers as they already have higher connection limits and using multiple domains may reduce performance (that’s my opinion) because of the extra DNS lookups.

They also have limited (since not all images and/or browsers are compatible) support for WebP images. If all the conditions are matched, it will kick in and Torbit will serve WebP versions on those images for further speed up.

Their current benchmark numbers are almost on par with others. And if your site can benefit from the unique optimizations such as using Local Storage you should check them out today!

Incapsula Review

http://www.incapsula.com

If you are primarily looking for security with the added benefit of some extra speed, you should check out Incapsula.

Their free account offers more protection than CloudFlare’s free account. Also they are offering few extra services such as uptime monitoring.

But Incapsula is a bit slower when it comes to the speed. I think that’s because of using less data centers (3 according to that post) spread across the world.

I must also not that I have signed but for their free (Personal) plan but they also have a 30 days trial. So, I had access to all features and tests were done with full acceleration.

On a side note, their setup is a bit different. Instead of a full DNS change you’ll just need to point your A & CNAME to provided locations.

CloudFlare Review

http://www.cloudflare.com

As you can see from my tests CloudFlare provided the fastest access, period. At this time, they have 12 data centers spread around the globe and that information (along with datacenter status) is publicly accessible.

They were a bit slow (but just a bit) for the UK and AU/NZ region. But I’ve been told a new DC for UK is coming very soon.

Their setup is pretty straight forward but as they take over all of your DNS, you should be careful to not to miss any entries. Their system detected pretty much everything but I had to manually setup the TXT record for Domain Keys.

I should also mention about a problem I had, somehow my IP is locked out and I was receiving “502 Bad Gateway” error following a false “Web Page Not Available” message when I try to access my sites. But at the same time, sites were fully accessible to the rest of the world. That problem lasted for a while but eventually it returned to the normal.

There are also some reports of false errors over the web (Google “cloudflare 502”) but don’t forget CF is currently in beta.

Another thing to consider is the pages that is shown to users in case of a failure or blocking. Those pages contain CloudFlare links & logo and there are some who not fond of this. You may beg to differ but for a FREE service that is OK with me. And apparently PRO account will have some control over this very soon.

For me, CloudFlare’s WordPress plugin is better than the provided by Incapsula. Both resolves actual visitors IP’s so WordPress will show it correctly. But on top of that when you mark a comment as a spam, CloudFlare posts that information back to their system to prevent further access of that offending user.

Edit: CloudFlare also supports HTML LocalStorage and other some other tricks when their new (beta) Rocket Loader feature is enabled. Thanks to Matthew Prince of CloudFlare for letting me know.

Verdict

All three services are very good. All has their strengths and weaknesses so you should pick one based on your website & your own requirements. But for Pixel Envision, my pick is:

Next Generation (CDN) Content Delivery Networks

August 27, 2011 Tips & Tricks

While working to get our site ready for our first product, I’ve been looking for options to speed it up globally without spending a dime, if possible. My first tough was using a regular CDN, but then I realize there is a new & better (in my opinion) way to do it…

You may ask, why?

  • Website performance & security at the same time. Your content hosted on could network while getting protected from malicious attacks.
  • Simple configuration, they don’t require you to change a thing on your server. Most (if not all) configured by DNS.
  • Better SEO than regular CDN networks, no URL rewrites required to serve page assets. All your images stays on your master domain, not on another CDN domain or subdomain of yours.

For the last few days I’ve been testing & benchmarking. So, my next blog entry will be a review on those services.

  • CloudFlare
  • Incapsula
  • Torbit

For the record, there are other similar services but I left them out, because they were either too expensive for my needs or simply not available to test.

  • Google Page Speed Service (Access Requested)
  • Yottaa (Access Requested)
  • Cotendo Cloudlet
  • Strangeloop
  • Blaze

PHP Currency Converter

August 18, 2011 PHP, Programming

Here is another PHP code snippet from my library. This PHP function allows you to calculate exchange amounts between 49 countries and 33 currencies.

Function is based on the live conversion reference rates provided by European Central Bank.

Usage

  • First 3 parameters are required. First one is the origin currency and the second one is target currency.
  • First 2 parameters should be the same type. If you are doing a county code based conversion both must be country codes. If you are doing currency code based conversion, both should be currency codes.
  • 3rd parameter is the amount you want to be converted. Should be decimal digits without any currency signs or codes attached to it.
  • 4th parameter is optional and set as true by default. When enabled function will prepend or append the currency sign (or code) to the output amount. You may want to turn that off if you will use the returned amount in a calculation.
  • 5th and the last parameter is calculation precision. Controls the number of digits after the decimal point. Default is 5.

Usage Samples

  • currency_convert(“EUR”,”USD”,100)
  • currency_convert(“FR”,”GB”,98);
  • currency_convert(“AUD”,”CNY”,145,false,8);
  • currency_convert(“GR”,”DK”,3500,true,2);

Download

  • Download PHP code snippet
  • Code is PHP5 compatible but it should also work on PHP4 (Untested)

License

This code is free to use, distribute, modify and study. When referencing please link back to this website / post in any way e.g. direct link, credits etc. If you find this useful, please leave a comment and share using the buttons below!

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
<?php
// Realtime currency converter by Pixel Envision (E.Gonenc)
// Version 1.0 - 18 August 2011
// Based on the live conversion reference rates provided by European Central Bank
// http://www.ecb.europa.eu/
function currency_convert($from, $to, $amount, $sign = true, $sensitivity = 5)
{
    $RATES = $BASE = $in = $out = $append = NULL;
 
    //Array of available countries & currencies
    $CURRENCY = array(
        "US" => "USD",
        "BE" => "EUR",
        "ES" => "EUR",
        "LU" => "EUR",
        "PT" => "EUR",
        "DE" => "EUR",
        "FR" => "EUR",
        "MT" => "EUR",
        "SI" => "EUR",
        "IE" => "EUR",
        "IT" => "EUR",
        "NL" => "EUR",
        "SK" => "EUR",
        "GR" => "EUR",
        "CY" => "EUR",
        "AT" => "EUR",
        "FI" => "EUR",
        "JP" => "JPY",
        "BG" => "BGN",
        "CZ" => "CZK",
        "DK" => "DKK",
        "EE" => "EEK",
        "GB" => "GBP",
        "HU" => "HUF",
        "LT" => "LTL",
        "LV" => "LVL",
        "PL" => "PLN",
        "RO" => "RON",
        "SE" => "SEK",
        "CH" => "CHF",
        "NO" => "NOK",
        "HR" => "HRK",
        "RU" => "RUB",
        "TR" => "TRY",
        "AU" => "AUD",
        "BR" => "BRL",
        "CA" => "CAD",
        "CN" => "CNY",
        "HK" => "HKD",
        "ID" => "IDR",
        "IN" => "INR",
        "KR" => "KRW",
        "MX" => "MXN",
        "MY" => "MYR",
        "NZ" => "NZD",
        "PH" => "PHP",
        "SG" => "SGD",
        "TH" => "THB",
        "ZA" => "ZAR"
    );
 
    if (strlen($from) == 2 && strlen($to) == 2)
    { //Operate using country code
        if (isset($CURRENCY[$from]))
        {
            $in = $CURRENCY[$from];
        }
        if (isset($CURRENCY[$to]))
        {
            $out = $CURRENCY[$to];
        }
    }
    elseif (strlen($from) == 3 && strlen($to) == 3)
    { //Operate using currency code
        if (in_array($from, $CURRENCY))
        {
            $in = $from;
        }
        if (in_array($to, $CURRENCY))
        {
            $out = $to;
        }
    }
    else
    {
        echo "Error: You should either use 2 digit country codes or 3 digit currency codes!";
    }
 
    if ($in && $out)
    { //Both currencies available, continue
        //Load live conversion rates and set as an array
        $XMLContent = file("http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml");
        if (is_array($XMLContent))
        {
            foreach ($XMLContent as $line)
            {
                if (ereg("currency='([[:alpha:]]+)'", $line, $currencyCode))
                {
                    if (ereg("rate='([[:graph:]]+)'", $line, $rate))
                    {
                        $RATES[$currencyCode[1]] = $rate[1];
                    }
                }
            }
        }
        if (is_array($RATES))
        {
 
            $RATES["EUR"] = 1; //Add EUR reference to array
            if ($in != "EUR")
            { //Normalize rate to given input
                $BASE = $RATES[$in];
                foreach ($RATES as $code => $rate)
                {
                    $RATES[$code] = round($rate / $BASE, $sensitivity);
                }
            }
 
            //Prepend or append the currency information
            if ($sign)
            {
                if ($out == "USD")
                {
                    echo "$";
                }
                elseif ($out == "EUR")
                {
                    echo "&euro;";
                }
                elseif ($out == "GBP")
                {
                    echo "&pound;";
                }
                elseif ($out == "JPY")
                {
                    echo "&yen;";
                }
                else
                {
                    $append = $out;
                }
            }
 
            echo round($amount * $RATES[$out], $sensitivity); //Output the converted amount
            if ($append)
            {
                echo " " . $append;
            }
 
        }
        else
        {
            echo "Error: Unable to load conversion rates!";
        }
 
    }
    else
    {
        echo "Error: Either one or both of given currencies are not available for conversion!";
    }
}
 
echo currency_convert("EUR", "USD", 1);
?>

Realtime lip-sync code snippet for Flash using AS3

August 17, 2011 Flash, Programming, Tips & Tricks

One of the flash projects I have been working on has required a lip sync talking. Dialogues were dynamic so frame by frame matching was out of the question. So, I ended up with the following action script 3 code which I’m sharing with you here…

It processes the input sound in real time, which returns the different values based on the sound volume and also outputs the sound itself.

You can use those values to match up mouth animation frames as provided in the sample. But you may also simulate the effect using a single mouth image and scaling it based on the values returned by the function…

Usage

  • FLA project is compatible with Flash CS4, contains sample speech & mouth animation images.
  • speech() is the the sound file linked from the library.
  • mouth is the animation clip that contains mouth drawings.

Downloads

  • Sample SWF file for preview.
  • Download FLA project

License

This code is free to use, distribute, modify and study. Mouth animation and the images provided in the sample FLA is NOT free to use in anyway. When referencing please link back to this website / post in any way e.g. direct link, credits etc. If you find this useful, please leave a comment and share using the buttons below!

ActionScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*
- Copyright 2011 Pixel Envision (E.Gonenc)
- http://www.pixelenvision.com/
- support@pixelenvision.com
*/
 
var left: Number;
 
function processSound(event: SampleDataEvent): void {
  var bytes: ByteArray = new ByteArray();
  playerObject.sourceSnd.extract(bytes, 4096);
  bytes.position = 0;
  while (bytes.bytesAvailable & gt; 0) {
    left = bytes.readFloat() * 128;
    if (left & lt; 0) {
      left = -left;
    }
    var scale: Number = left * 2;
  }
  event.data.writeBytes(bytes);
 
  //Define mouth animation here
  if (scale & lt; 1) {
    mouth.gotoAndStop(1);
  } else if (scale & lt; 10) {
    mouth.gotoAndStop(2);
  } else if (scale & lt; 25) {
    mouth.gotoAndStop(3);
  } else if (scale & lt; 50) {
    mouth.gotoAndStop(4);
  } else {
    mouth.gotoAndStop(5);
  }
  //Define mouth animation here
 
  trace(scale);
}
 
var playerObject: Object = new Object();
playerObject.sourceSnd = new speech();
playerObject.outputSnd = new Sound();
playerObject.outputSnd.addEventListener(SampleDataEvent.SAMPLE_DATA, processSound);
playerObject.outputSnd.play();
stop();

Witches' Brew Teaser Sketch

August 16, 2011 Coming Soon

Here is a teaser sketch of our main character, The Witch! Sorry about the quality, but the photo is taken directly from my wife’s sketchbook…

Game is scheduled for release next month and will be available on this halloween season!

Finding visitors real IP using PHP

August 16, 2011 PHP, Programming

Third PHP snippet I’m sharing from my library is code that tries to get most accurate real IP of the visitor. There are few headers with the IP information but those can be populated with many different ip’s based on customers route to your server. This script evolved in time and I believe it returns a very accurate source IP based on the information given in the request headers. But if you find a bug or a way to enhance it more, let me know!

License

This code is free to use, distribute, modify and study. When referencing please link back to this website / post in any way e.g. direct link, credits etc. If you find this useful, please leave a comment and share using the buttons below!

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
function realip()
{
 
    $ip = trim($_SERVER['REMOTE_ADDR']);
 
    if ($_SERVER["HTTP_CLIENT_IP"])
    {
 
        if (preg_match("~\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}~", trim($_SERVER["HTTP_CLIENT_IP"])) && !preg_match("~^((0|10|127|169\.254|172\.(1[6-9]|2[0-9]|3[0-1])|192\.0\.2|192\.168|255\.255\.255)\.)~", trim($_SERVER["HTTP_CLIENT_IP"])))
        {
            $ip = trim($_SERVER["HTTP_CLIENT_IP"]);
        }
 
    }
    elseif ($_SERVER["HTTP_X_FORWARDED_FOR"])
    {
 
        foreach (explode(",", $_SERVER["HTTP_X_FORWARDED_FOR"]) as $key)
        {
 
            if (preg_match("~\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}~", trim($key)) && !preg_match("~^((0|10|127|169\.254|172\.(1[6-9]|2[0-9]|3[0-1])|192\.0\.2|192\.168|255\.255\.255)\.)~", trim($key)))
            {
                $ip = trim($key);
                break;
            }
 
        }
 
    }
    return ($ip);
}
 
$IP = realip();
?>

PHP e-mail validation code snippet

August 15, 2011 PHP, Programming

Validation of email

This is the second PHP snippet I’m sharing from my library. It is not very complicated but get’s the job done. It both checks for the email syntax (REGEX) and existence of the email domain by checking MX & A records…

License

This code is free to use, distribute, modify and study. When referencing please link back to this website / post in any way e.g. direct link, credits etc. If you find this useful, please leave a comment and share using the buttons below!

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
$email = "test@domain.com";
 
if (!preg_match("/^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)*\.\w{2,8}$/i", $email))
{
 
    //E-mail syntax is invalid
    
}
elseif (!checkdnsrr(array_pop(explode("@", $email)) , "MX") & amp; & amp;
!checkdnsrr(array_pop(explode("@", $email)) , "A"))
{
 
    //Email server is not valid
    
}
else
{
 
    //E-mail is valid
    
}
?>

ZIP (POSTAL) Code Validation Regex & PHP code for 12 Countries

August 14, 2011 PHP, Programming

For the last couple of years, I’ve been mostly doing PHP coding. As a result, I have small library of code snippets of my own. Those are simple but useful PHP codes that might save some time on your work!

First one I’m sharing here today is the combination of regular expression and the PHP code to do ZIP code (or Postal Code) validation for following 12 countries: United States of America, United Kingdom, Germany, Canada, France, Italy, Australia, Netherlands, Spain, Denmark, Sweden & Belgium.

Code is provided in PHP but the REGEX codes are universal and those can be used with other programming languages.

License

This code is free to use, distribute, modify and study. When referencing please link back to this website / post in any way e.g. direct link, credits etc. If you find this useful, please leave a comment and share using the buttons below!

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?php
$country_code = "US";
$zip_postal = "11111";
 
$ZIPREG = array(
    "US" => "^\d{5}([\-]?\d{4})?$",
    "UK" => "^(GIR|[A-Z]\d[A-Z\d]??|[A-Z]{2}\d[A-Z\d]??)[ ]??(\d[A-Z]{2})$",
    "DE" => "\b((?:0[1-46-9]\d{3})|(?:[1-357-9]\d{4})|(?:[4][0-24-9]\d{3})|(?:[6][013-9]\d{3}))\b",
    "CA" => "^([ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ])\ {0,1}(\d[ABCEGHJKLMNPRSTVWXYZ]\d)$",
    "FR" => "^(F-)?((2[A|B])|[0-9]{2})[0-9]{3}$",
    "IT" => "^(V-|I-)?[0-9]{5}$",
    "AU" => "^(0[289][0-9]{2})|([1345689][0-9]{3})|(2[0-8][0-9]{2})|(290[0-9])|(291[0-4])|(7[0-4][0-9]{2})|(7[8-9][0-9]{2})$",
    "NL" => "^[1-9][0-9]{3}\s?([a-zA-Z]{2})?$",
    "ES" => "^([1-9]{2}|[0-9][1-9]|[1-9][0-9])[0-9]{3}$",
    "DK" => "^([D-d][K-k])?( |-)?[1-9]{1}[0-9]{3}$",
    "SE" => "^(s-|S-){0,1}[0-9]{3}\s?[0-9]{2}$",
    "BE" => "^[1-9]{1}[0-9]{3}$"
);
 
if ($ZIPREG[$country_code])
{
 
    if (!preg_match("/" . $ZIPREG[$country_code] . "/i", $zip_postal))
    {
        //Validation failed, provided zip/postal code is not valid.
        
    }
    else
    {
        //Validation passed, provided zip/postal code is valid.
        
    }
 
}
else
{
 
    //Validation not available
    
}
?>

Witches' Brew HD Teaser Trailer

August 8, 2011 Coming Soon

Our first game is revealed! Here is the teaser trailer of our halloween game for both kids & adults. We hope you’ll like it!

Pixel Envision Logo Animaton

August 4, 2011 Blog

Here is our logo animation to be used in upcoming game videos. We hope you like it!

PHP Cache Warmer (Preloader) for W3 Total Cache

August 3, 2011 PHP, Programming

If you haven’t noticed yet, this website is powered by WordPress and speed is optimized using W3 Total Cache… Previously I was using WP Super Cache but recently decided to try W3 Total Cache as it’s feature set (minify, cdn, etc.) is looking better. For my setup it actually worked better so I kept it as my caching plugin.

But it took me a while to figure out it’s “Cache Preload” option is not working well or at least not working as I expected. “Automatically prime the page cache” was checked but pages were not in cache…

After searching a bit I found the culprit. W3 Total Cache relies on internal cron of WordPress and it’s only triggered by site activity. If your site has no visitors for a while loading the pages will be slower because: 1. W3TC will check if the current page is cache and if it’s expired. 2. Needs to clean up that expired page and re-generate a current copy. 3. Finally serve that file.

My first attempt to fix that was trying to put a real cron job for wp-cron.php but that didn’t worked well. Cron was working but W3TC still failed to prime the cache. There are couple of threads about that at the wordpress support forums.

Optimus Cache Prime (OCP) is a smart cache preloader for websites with XML sitemaps. It crawls all URLs in a given sitemap so the web server builds cached versions of the pages before visitors or search engine spiders arrive.

My next attempt was trying to use Optimus Cache Prime, a Phyton script written by Patrick Mylund Nielsen.

That was exactly what I needed… But…

My host had Python 2.4 installed and ocp.py required 2.5+

I have asked my  hosting (Inmotion) but they said they cannot change that on shared hosting and I should move to virtual private server (VPS) just for that… Yeah, sure!

Finally, I have decided to stop being lazy and created my own solution using PHP which I will be sharing here with you.

Idea

This PHP script has the same basic idea as Optimus Cache Prime and uses sitemap.xml as it’s source.

It will read the sitemap.xml file, parse the local URL’s listed in it. Then checks if the cache file for that url exists in W3TC. If the cache exists it will skip that url. But if not, it will visit the link using minimal resources causing the W3TC re-create cache for that page.

System Requirements

  • PHP5 (Required due to SimpleXML, you may change that to use with earlier versions of PHP)
  • W3 Total Cache
  • WP Super Cache (Not tested! But it should work)

Usage

  • Copy the code into a php file (warm.php) and place it in the site root where sitemap.xml exists.
  • Review/edit the configuration options inside the script.
  • Set a cron job to run that script every 5 minutes (or even every minute as the code is very easy on the system resources.)
  • Sample: */5 * * * * php -q /home/youraccount/public_html/warm.php
  • Thats it!

Features

  • Reads sitemap.xml as a file, saving a web server call.
  • Checks for the local cache file before trying to re-cache, saving resources.
  • Optionally uses priority tags in sitemap.xml
  • Configurable page limit per session, useful for larger sites.
  • Frees memory and stops executing as soon as possible to save further resources.
  • Failsafe to stop executing in case of an url or network problem.
  • New: Option to fix trailing slash cache creation problem.

Code & Download

  • Current version: Version 2.1 – 21 August 2011
  • Download warm.php

License

This code is free to use, distribute, modify and study. If you modify it please keep my copyright intact. When referencing please link back to this website / post in any way e.g. direct link, credits etc. If you find this useful, please leave a comment and share using the buttons below!

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<?php
// W3 TOTAL CACHE WARMER (PRELOADER) by Pixel Envision (E.Gonenc)
// Version 2.1 - 21 August 2011
//Configuration options
$priority = true; //Use priorities defined in sitemap.xml (true/false)
$ppi = 10; //Pages to be cached per interval
$delay = 0.5; // Delay in seconds between page checks, default is half a second
$quiet = true; // Do not output process log (true/false)
$trailing_slash = false; // Add trailing slash to URL's, that might fix cache creation problems (true/false)
$sitemap = "sitemap.xml"; //Path to sitemap file relative to the warm.php
// Defaults for W3TC
$index = "_index.html"; //Cache file to check
$rootp = "wp-content/w3tc/pgcache"; //Root of cache
//Do not change anything below this line unless you know what you are doing
ignore_user_abort(true);
set_time_limit(600);
 
$xml = simplexml_load_file($sitemap);
$UL = $UP = array();
foreach ($xml->url as $url_list)
{
    $UL[] = $url_list->loc;
    $UP[] = $url_list->priority;
}
unset($xml);
if ($priority == true)
{
    arsort($UP, $sort_flags = SORT_NUMERIC);
}
$i = 0;
foreach ($UP as $key => $val)
{
 
    $path = $rootp;
    $url = $UL[$key];
    $sub = explode("/", $url);
    if ($sub[3])
    {
        $path .= "/" . urldecode($sub[3]);
    }
    if ($sub[4])
    {
        $path .= "/" . urldecode($sub[4]);
    }
    if ($sub[5])
    {
        $path .= "/" . urldecode($sub[5]);
    }
    $path .= "/" . $index;
 
    if (file_exists($path))
    {
        if ($quiet != true)
        {
            echo "Priority: " . $val . " => Skipped: " . $path . "\n";
        }
    }
    else
    {
        if ($trailing_slash == true)
        {
            $url = rtrim($url, "/") . "/";
        }
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
        curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt($ch, CURLOPT_NOBODY, true);
        $ret = curl_exec($ch);
        curl_close($ch);
        if ($ret)
        {
            $i++;
        }
        else
        {
            echo "Unable to connect $url, exiting...";
            break;
        }
        usleep($delay * 1000000);
        if ($quiet != true)
        {
            echo "Priority: " . $val . " => Warmed: " . $path . " by visiting " . $url . "\n";
        }
    }
    if ($i < $ppi)
    {
        flush();
    }
    else
    {
        break;
    }
 
}
exit;
?>

CoronaSDK Enhanced UI Library (ui.lua)

August 3, 2011 CoronaSDK, Lua

While working with CoronaSDK I ended up using ui.lua for the buttons in our upcoming iPhone/iPad game. But I had hard time locating the latest version as many other developers had their own versions.

So, I have decided to make an ultimate latest version that will combine all previous enhancements along with my own. Copy of that Lua code is still available at Ansca code exchange.

NEW: Updated to eliminate the use of LUAs deprecated module() function. This is an internal change only, usage stays the same.
NEW: Added event support, now returns even.target, event.x & event.y values. You can use x/y values to provide different actions based on the coordinates of the touch event reative to the x/y size of the button image.
NEW: isActive state enhanced, so the button can be enabled/disabled without checking current isActive state with if-then.

ui.lua v2.4

Lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
-- ui.lua (currently includes Button class with labels, font selection and optional event model)
 
-- Version 2.4
-- Based on the folowing original provided by Ansca Inc.
-- Version 1.5 (works with multitouch, adds setText() method to buttons)
--
-- Copyright (C) 2010 ANSCA Inc. All Rights Reserved.
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
-- this software and associated documentation files (the "Software"), to deal in the
-- Software without restriction, including without limitation the rights to use, copy,
-- modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
-- and to permit persons to whom the Software is furnished to do so, subject to the
-- following conditions:
--
-- The above copyright notice and this permission notice shall be included in all copies
-- or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
-- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
-- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
-- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-- DEALINGS IN THE SOFTWARE.
 
-- Version 1.6 Works with Dynamic Scaling.
-- Based on the work edited by William Flagello, williamflagello.com
-- Original from https://developer.anscamobile.com/code/ui-library
--
-- Version 1.7 Dynamic Scaling text fixes by Jonathan Bebe
-- http://developer.anscamobile.com/forum/2010/12/17/easily-make-your-text-sharp-retina-displays#comment-18164
-- Provided in Ghosts &amp; Monsters Sample Project
--
-- Version 1.71 Retina Updates by Jonathan Bebe
-- http://developer.anscamobile.com/forum/2010/12/17/easily-make-your-text-sharp-retina-displays#comment-38284
-- Adapted to 1.7 base code by E. Gonenc, pixelenvision.com
--
-- Version 1.8 added support for providing already realized display-objects for use in Tiled/Lime
-- Based on the file changed by Frank Siebenlist
-- http://developer.anscamobile.com/forum/2011/02/19/enhanced-uilua-v15
-- Adapted to 1.7 base code by E. Gonenc, pixelenvision.com
--
-- Version 1.9
-- Added transparency &amp; scaling options to use as over state. newLabel updated to support retina text.
-- Edited by E. Gonenc, pixelenvision.com
--
-- Version 1.91
-- Added suggested fix for overlapping buttons by Jonathan Bebe
-- http://jonbeebe.net/to-return-true-or-not-to
-- Adapted by E. Gonenc, pixelenvision.com
--
-- Version 2.02
-- Button text will now follow scaling &amp; alpha states of over button
-- Edited by E. Gonenc, pixelenvision.com
--
-- Version 2.1
-- Added suggested .isActive update by monoxgas http://developer.anscamobile.com/code/enhanced-ui-library-uilua#comment-49272
-- Edited by E. Gonenc, pixelenvision.com
--
-- Version 2.2
-- Updated to eliminate the use of LUAs deprecated module() function. This is an internal change only, usage stays the same.
-- http://blog.anscamobile.com/2011/09/a-better-approach-to-external-modules/
-- Edited by E. Gonenc, pixelenvision.com
--
-- Version 2.3
-- Updated to use object.contentBounds instead of deprecated object.stageBounds
-- Added event support, now returns even.target, event.x &amp; event.y values. You can use x/y values to provide different actions
-- based on the coordinates of the touch event reative to the x/y size of the button image.
-- Edited by E. Gonenc, pixelenvision.com
--
-- Version 2.4
-- isActive state enhanced to button can be enabled/disabled without checking current isActive state with if-then.
-- ie. btn.isActive = true (Default state, button is enabled) btn.isActive = false (button is disabled, no animation and action)
-- Edited by E. Gonenc, pixelenvision.com
 
local M = {}
 
-------------
-- convenience test functions added by Frank.
 
local coronaMetaTable = getmetatable(display.getCurrentStage())
 
--- Test function that returns whether object is a Corona display object.
-- Note that all Corona types seem to share the same metatable...
local isDisplayObject = function(o)
return type(o) == "table" and getmetatable(o) == coronaMetaTable
end
 
-----------------
-- Helper function for newButton utility function below
local function newButtonHandler( self, event )
 
local result = true
 
local default = self[1]
local over = self[2]
local txt1,txt2,txt3
 
local OX,OY,SX,SY,SM
if self[3] then txt1 = self[3] end
if self[4] then txt2 = self[4] end
if self[5] then txt3 = self[5] end
if txt1 or txt2 or txt3 then
  if display.contentScaleX &lt; 1.0 or display.contentScaleY &lt; 1.0 then SM = 2 else SM = 1 end
  OX,OY = (over.xScale/default.xScale),(over.yScale/default.yScale)
  SX,SY = (default.xScale/over.xScale),(default.yScale/over.yScale)
end
 
-- General "onEvent" function overrides onPress and onRelease, if present
local onEvent = self._onEvent
 
local onPress = self._onPress
local onRelease = self._onRelease
 
local buttonEvent = {}
if (self._id) then
  buttonEvent.id = self._id
end
buttonEvent.isActive = self.isActive
buttonEvent.target = self
local phase = event.phase
if self.isActive then
  if "began" == phase then
    if over then
      default.isVisible = false
      over.isVisible = true
      if txt1 then txt1:scale(OX,OY);txt1.alpha = over.alpha end
      if txt2 then txt2:scale(OX,OY);txt2.alpha = over.alpha end
      if txt3 then txt3:scale(OX,OY);txt3.alpha = over.alpha end
    end
 
    if onEvent then
      buttonEvent.phase = "press"
      buttonEvent.x = event.x - self.contentBounds.xMin
      buttonEvent.y = event.y - self.contentBounds.yMin
      result = onEvent( buttonEvent )
    elseif onPress then
      result = onPress( event )
    end
 
    -- Subsequent touch events will target button even if they are outside the contentBounds of button
    display.getCurrentStage():setFocus( self, event.id )
    self.isFocus = true
 
  elseif self.isFocus then
    local bounds = self.contentBounds
    local x,y = event.x,event.y
    local isWithinBounds =
    bounds.xMin = x and bounds.yMin = y
 
    if "moved" == phase then
      if over then
        -- The rollover image should only be visible while the finger is within button's stageBounds
        default.isVisible = not isWithinBounds
        over.isVisible = isWithinBounds
        if txt1 and not isWithinBounds and txt1.xScale*SM == OX then txt1:scale(SX,SY);txt1.alpha = default.alpha
      elseif txt1 and isWithinBounds and txt1.xScale*SM ~= OX then txt1:scale(OX,OY);txt1.alpha = over.alpha end
        if txt2 and not isWithinBounds and txt2.xScale*SM == OX then txt2:scale(SX,SY);txt2.alpha = default.alpha
      elseif txt2 and isWithinBounds and txt2.xScale*SM ~= OX then txt2:scale(OX,OY);txt2.alpha = over.alpha end
        if txt3 and not isWithinBounds and txt3.xScale*SM == OX then txt3:scale(SX,SY);txt3.alpha = default.alpha
      elseif txt3 and isWithinBounds and txt3.xScale*SM ~= OX then txt3:scale(OX,OY);txt3.alpha = over.alpha end
      end
 
    elseif "ended" == phase or "cancelled" == phase then
      if over then
        default.isVisible = true
        over.isVisible = false
        if txt1 and txt1.xScale*SM == OX then txt1:scale(SX,SY);txt1.alpha = default.alpha end
        if txt2 and txt2.xScale*SM == OX then txt2:scale(SX,SY);txt2.alpha = default.alpha end
        if txt3 and txt3.xScale*SM == OX then txt3:scale(SX,SY);txt3.alpha = default.alpha end
      end
 
      if "ended" == phase then
        -- Only consider this a "click" if the user lifts their finger inside button's stageBounds
        if isWithinBounds then
          if onEvent then
            buttonEvent.phase = "release"
            buttonEvent.x = event.x - bounds.xMin
            buttonEvent.y = event.y - bounds.yMin
            result = onEvent( buttonEvent )
          elseif onRelease then
            result = onRelease( event )
          end
        end
      end
 
      -- Allow touch events to be sent normally to the objects they "hit"
      display.getCurrentStage():setFocus( self, nil )
      self.isFocus = false
    end
  end
end
return true
 
end
 
---------------
-- Button class
 
local function newButton( params )
local button, defaultSrc , defaultX , defaultY , overSrc , overX , overY , overScale , overAlpha , size, font, textColor, offset
 
local sizeDivide = 1
local sizeMultiply = 1
 
if display.contentScaleX &lt; 1.0 or display.contentScaleY &lt; 1.0 then
  sizeMultiply = 2
  sizeDivide = 0.5
end
 
if params.defaultSrc then
  button = display.newGroup()
  if isDisplayObject(params.defaultSrc) then
    default = params.defaultSrc
  else
    default = display.newImageRect ( params.defaultSrc , params.defaultX , params.defaultY )
  end
  button:insert( default, false )
end
 
if params.overSrc then
  if isDisplayObject(params.overSrc) then
    over = params.overSrc
  else
    over = display.newImageRect ( params.overSrc , params.overX , params.overY )
  end
  if params.overAlpha then
    over.alpha = params.overAlpha
  end
  if params.overScale then
    over:scale(params.overScale,params.overScale)
  end
  over.isVisible = false
  button:insert( over, false )
end
 
-- Public methods
function button:setText( newText )
 
  local labelText = self.text
  if ( labelText ) then
    labelText:removeSelf()
    self.text = nil
  end
 
  local labelShadow = self.shadow
  if ( labelShadow ) then
    labelShadow:removeSelf()
    self.shadow = nil
  end
 
  local labelHighlight = self.highlight
  if ( labelHighlight ) then
    labelHighlight:removeSelf()
    self.highlight = nil
  end
 
  if ( params.size and type(params.size) == "number" ) then size=params.size else size=20 end
  if ( params.font ) then font=params.font else font=native.systemFontBold end
  if ( params.textColor ) then textColor=params.textColor else textColor={ 255, 255, 255, 255 } end
 
  size = size * sizeMultiply
 
  -- Optional vertical correction for fonts with unusual baselines (I'm looking at you, Zapfino)
  if ( params.offset and type(params.offset) == "number" ) then offset=params.offset else offset = 0 end
 
  if ( params.emboss ) then
    -- Make the label text look "embossed" (also adjusts effect for textColor brightness)
    local textBrightness = ( textColor[1] + textColor[2] + textColor[3] ) / 3
 
    labelHighlight = display.newText( newText, 0, 0, font, size )
    if ( textBrightness &gt; 127) then
      labelHighlight:setTextColor( 255, 255, 255, 20 )
    else
      labelHighlight:setTextColor( 255, 255, 255, 140 )
    end
    button:insert( labelHighlight, true )
    labelHighlight.x = labelHighlight.x + 1.5; labelHighlight.y = labelHighlight.y + 1.5 + offset
    self.highlight = labelHighlight
 
    labelShadow = display.newText( newText, 0, 0, font, size )
    if ( textBrightness &gt; 127) then
      labelShadow:setTextColor( 0, 0, 0, 128 )
    else
      labelShadow:setTextColor( 0, 0, 0, 20 )
    end
    button:insert( labelShadow, true )
    labelShadow.x = labelShadow.x - 1; labelShadow.y = labelShadow.y - 1 + offset
    self.shadow = labelShadow
 
    labelHighlight.xScale = sizeDivide; labelHighlight.yScale = sizeDivide
    labelShadow.xScale = sizeDivide; labelShadow.yScale = sizeDivide
  end
 
  labelText = display.newText( newText, 0, 0, font, size )
  labelText:setTextColor( textColor[1], textColor[2], textColor[3], textColor[4] )
  button:insert( labelText, true )
  labelText.y = labelText.y + offset
  self.text = labelText
 
  labelText.xScale = sizeDivide; labelText.yScale = sizeDivide
end
 
if params.text then
  button:setText( params.text )
end
 
if ( params.onPress and ( type(params.onPress) == "function" ) ) then
  button._onPress = params.onPress
end
if ( params.onRelease and ( type(params.onRelease) == "function" ) ) then
  button._onRelease = params.onRelease
end
 
if (params.onEvent and ( type(params.onEvent) == "function" ) ) then
  button._onEvent = params.onEvent
end
 
-- set button to active (meaning, can be pushed)
button.isActive = true
 
-- Set button as a table listener by setting a table method and adding the button as its own table listener for "touch" events
button.touch = newButtonHandler
button:addEventListener( "touch", button )
 
if params.x then
  button.x = params.x
end
 
if params.y then
  button.y = params.y
end
 
if params.id then
  button._id = params.id
end
 
return button
end
M.newButton = newButton
 
--------------
-- Label class
 
local function newLabel( params )
local labelText
local size, font, textColor, align
local t = display.newGroup()
 
local sizeDivide = 1
local sizeMultiply = 1
 
if ( params.bounds ) then
  local bounds = params.bounds
  local left = bounds[1]
  local top = bounds[2]
  local width = bounds[3]
  local height = bounds[4]
 
  if ( params.size and type(params.size) == "number" ) then size=params.size else size=20 end
  if ( params.font ) then font=params.font else font=native.systemFontBold end
  if ( params.textColor ) then textColor=params.textColor else textColor={ 255, 255, 255, 255 } end
  if ( params.offset and type(params.offset) == "number" ) then offset=params.offset else offset = 0 end
  if ( params.align ) then align = params.align else align = "center" end
 
  if ( params.text ) then
    labelText = display.newText( params.text, 0, 0, font, size * 2 )
    labelText.xScale = 0.5; labelText.yScale = 0.5
    labelText:setTextColor( textColor[1], textColor[2], textColor[3], textColor[4] )
    t:insert( labelText )
    -- TODO: handle no-initial-text case by creating a field with an empty string?
 
    if ( align == "left" ) then
      labelText.x = left + labelText.contentWidth * 0.5
    elseif ( align == "right" ) then
      labelText.x = (left + width) - labelText.contentWidth * 0.5
    else
      labelText.x = ((2 * left) + width) * 0.5
    end
  end
 
  labelText.y = top + labelText.contentHeight * 0.5
 
  -- Public methods
  function t:setText( newText )
    if ( newText ) then
      labelText.text = newText
 
      if ( "left" == align ) then
        labelText.x = left + labelText.contentWidth * 0.5
      elseif ( "right" == align ) then
        labelText.x = (left + width) - labelText.contentWidth * 0.5
      else
        labelText.x = ((2 * left) + width) * 0.5
      end
    end
  end
 
  function t:setTextColor( r, g, b, a )
    local newR = 255
    local newG = 255
    local newB = 255
    local newA = 255
 
    if ( r and type(r) == "number" ) then newR = r end
    if ( g and type(g) == "number" ) then newG = g end
    if ( b and type(b) == "number" ) then newB = b end
    if ( a and type(a) == "number" ) then newA = a end
 
    labelText:setTextColor( r, g, b, a )
  end
end
 
-- Return instance (as display group)
return t
 
end
M.newLabel = newLabel
 
return M

12»
  • Tags

    3ds Max Coming Soon CoronaSDK Featured Flash Lua MAXScript PHP Programming Reviews Tips & Tricks Unity 3D Windows Phone
  • Recent Comments

    • Yogesh Singh on ZIP (POSTAL) Code Validation Regex & PHP code for 12 Countries
    • Admin on Maxscript – Vray Cubemap Generator for Unity
    • charlie on Maxscript – Vray Cubemap Generator for Unity
    • Mastan on PHP Currency Converter
    • Rakesh Vishnoi on ZIP (POSTAL) Code Validation Regex & PHP code for 12 Countries
    • Find us on

      amazonandroidapplefacebooklinkedintwitterwindowsyoutube
    • Company Information

      Lytchett House, 13 Freeland Park, Wareham Road, Poole, Dorset, BH16 6FA

      Pixel Envision Limited is a company registered in England, company number: 09558675. Registered Office: Preston Park House, South Road, Brighton, East Sussex, BN1 6SB, United Kingdom

    • Privacy Policy
    Copyright © 2011-2021 Pixel Envision Ltd, all rights reserved.