საერთო კოდი ბრაუზერსა და node.js აპლკაციაში

წინა პოსტებში ვახსენე რომ ჯავასკრიპტზე მომიწია ანიმაციის ლოგიკის წერა. იგივე კოდი (მათ შორის გეომეტრიაც) ბექშიც მჭირდებოდა, ამიტომ ვიფიქრე – რატომ გადავწერო.. ბექს node.js-ით გავაკეთებ და იგივე კოდს გამოვიყენებ. შესაბამისად თავსებადობის ამოცანა გამიჩნდა.

ცხადია, პირველ რიგში საერთო ბიბლიოთეკებიდან უნდა გამომეყო ბრაუზერთან დაკავშირებული კოდი (DOM-ის მანიპულაცია, ვორკერების ურთიერთობა, ა.შ.). მეორე რიგში node.js-ს ცოტა განსხვავებული მეთოდი აქვს სკრიპტების იმპორტისთვის. თუ ბრაუზერში <script> ტეგს ვიყენებთ, node.js-ში მოდულები უნდა გავაკეთოთ და require მეთოდით ჩავტვირთოთ.

მაგალითად, მარტივი მოდული ასე გამოიყურება (ფაილის სახელია Square.js):

exports.Square = function (side){
	this.side = side;
	this.getArea = function(){
		return side * side;
	}
}

module.exports ან პირდაპირ exports მეთოდს ვიყენებთ იმისთვის რომ ობიექტები გარედან ხელმისაწვდომი გავხადოთ. ამ ფაილში აღწერილი სხვა დანარჩენი ფუნქციები და ცვლადები private იქნება გარე სამყაროსთვის.

მოდულის გამოყენების მაგალითი სხვა ფაილში:

var module = require('./Square');
var square = new module.Square(10);
console.log(square.getArea());

Square.js ფაილი ბრაუზერისთვის თავსებადი რომ გავხადოთ, exports მეთოდს რამე უნდა მოვუხერხოთ.
მაგალითად, შეიძლება შემოწმების ჩამატება:

var Square = function (side){
	this.side = side;
	this.getArea = function(){
		return side * side;
	}
}

if (typeof exports !== 'undefined') {
	exports.Square = Square;
}

აქ კიდევ ერთი მომენტია. როგორც ზევით აღვნიშნეთ, node.js-ში exports-ის გარეთ რაც რჩება, ხელმისაწვდომი არ არის გარედან. ბრაუზერის შემთხვევაში კი Square გლობალური ცვლადი გამოდის. სწორი იქნებოდა, რომ მისი წვდომაც შეგვეზღუდა. მაგალითად ასე:

(function(exports){
	var Square = function (side){
		this.side = side;
		this.getArea = function(){
			return side * side;
		}
	}

	if (typeof exports !== 'undefined') {
		exports.Square = Square;
	}	
})(typeof exports !== 'undefined' ? exports : this['module'] = {});

 

RequireJS + node

არის კიდევ ერთი გზა. ყოველთვის მინდოდა Require.js ბიბლიოთეკის დატესტვა. როგორც აღმოჩნდა მაგას ადაპტერიც აქვს nodejs და მისნაირებისთვის. ასე რომ, ავდექი და Require-ზე გადავაწყე პროექტი. შედეგად ერთნაირი ფორმატით შემიძლია ვწერო მოდულები ფრონტისთვისაც და ბექსითვისაც. თვითონ ბიბლიოთეკაც საკმაოდ მომეწონა. ძალიან მარტივად გადაეწყო ჩემი კოდი და პირველივე გაშვებაზე იმუშავა რაც არ უნდა უცნაური იყოს.

ზევით აღწერილი მოდულის მაგალითი Require.js-ით:

define(function(){
	return function Square(side){
		this.side = side;
		this.getArea = function(){
			return side * side;
		}
	}
});

მოდულის გამოყენების მაგალითი:

requirejs(['Square'], function(Square) {
    var square = new Square(10);
	console.log(square.getArea());
});

JavaScript animation / game loop (ნაწილი 2)

ამ პოსტის წინა ნაწილში requestAnimationFrame მეთოდზე ვისაუბრე, რომლის საშუალებით ბრაუზერის რენდერინგს შეგვიძლია ჩვენი ქოლბექი მივაბათ და შესაბამისად მაგ დროს განვაახლოთ ანიმაციის ან თამაშის მდგომარეობა.

მინდა აღვნიშნო, რომ ამ მეთოდში ჯობია მხოლოდ გადახატვის ლოგიკა იყოს, ხოლო ანიმაციის ან თამაშის ლოგიკის (მდგომარეობების გამოთვლა, ა.შ.) ადგილი იქ ცალსახად არ არის. ამას ბევრი მიზეზი აქვს: ბრაუზერი გვერდის რენდერის დროს სხვადასხვა ფაქტორს ითვალისწინებს – მაგალითად ტაბი აქტიურია თუ არა, ლეპტოპის აკუმულატორი რა დონეზეა, მონიტორი რა სიხშირით ახლდება, ა.შ. შესაბამისად შეიძლება ჩვენი ქოლბექების გამოძახების რაოდენობა შეამციროს ან გაზარდოს დროის კონკრეტულ პერიოდში.

აქედან გამომდინარე, უმჯობესია რომ ანიმაციის ან თამაშის მდგომარეობები ცალკე გამოითვალოს / დაგენერირდეს და requestAnimationFrame-ის ქოლბექში მხოლოდ მათი რენდერის კოდი ჩაიწეროს.

ცალკე გენერაციაც ბევრნაირად შეიძლება. რადგან ეგ მნიშვნელოვნად განსაზღვრავს ჩვენი ანიმაციის წარმადობას, ბევრი ვიფიქრე როგორ გამეკეთებინა და ახლაც არ ვარ დარწუნებული ჩემი კოდის სისწორეში. სხვის მიდგომებს მოვიყვან მაგალითად:

1. შეიძლება setInterval და setTimeout მეთოდებით დაგენერირდეს მდგომარეობები, ხოლო requestAnimationFrame-ის ქოლბექმა აარჩიოს იქიდან შესაბამისი მდგომარეობა და ის დაარენდეროს.

2. პირველი პუნქტის ანალოგიურად მდგომარეობები დროის რაღაც ინტერვალში დაგენერირდეს, თუმცა ეს ლოგიკა ცალკე ნაკადში – მუდმივად გაშვებულ web worker-ში იყოს გატანილი, რომ მთავარი ნაკადი ძალიან არ დაიტვირთოს (ჩემი აზრით ეს უკეთესია). requestAnimationFrame-მა კი worker-დან მიღებული მდგომარეობებიდან შეარჩიოს დროის შესაბამისად და დაარენდეროს გარემო.

3. ეს მეორე პუნქტის მსგავსია, თუმცა setInterval-ის ნაცვლად ინიციატორი requestAnimationFrame-ის ქოლბექი იყოს. მის გამოძახებაში დამატებით იყოს worker-თან შეტყობინების გაგზავნა, რომ ვორკერმა მომდევნო X რაოდენობის მდგომარეობა დააგენერიროს. ასეთ შემთხვევაში, თუ ბრაუზერი არ არენდერებს არაფერს, ჩვენი ნაკადი ტყუილად არ იქნება მუდმივად გაშვებული თავისი მძიმე კალკულაციებით.

ჩემს შემთხვევაში საქმე უფრო ანიმაციასთან მაქვს და არა real-time თამაშთან. მდგომარეობები შემიძლია წინასწარვე გამოვთვალო, ამიტომ ასეთი გზა ავირჩიე: worker-ს ვუშვებ რომელიც მთლიადანვე აგენერირებს ყველა მდგომარეობას და თითოეულ მათგანს უკან აბრუნებს მთავარ ნაკადთან. requestAnimationFrame-ის ქოლბექი ტაიმსტემპს უყურებს და მაგის შესაბამისად გავლილ მდგომარეობებს არენდერებს.

ამდენი მდგომარეობების გენერაცია, აქეთ იქით გზავნა და შენახვა დიდად არ მომწონს, რადგან აზრზე არ ვარ ობიექტების რაოდენობის ზრდასთან ერთად რა მოხდება, თუმცა ჯერ ჯერობით უკეთესი გზა არ ვიცი.

JavaScript animation / game loop (ნაწილი 1)

იმ დღეს ერთ-ერთ ვებ აპლიკაციაში არც თუ ისე მარტივი ანიმაციის გაკეთება მომინდა და ბოლოს ჯავასკრიპტამდე მივედი. თუმცა რამდენიმე გზის გამოკვლევა დამჭირდა და გადავწყვიტე ამ თემაზე პოსტი კარგი იქნებოდა. იქნებ ვინმეს უკეთესი იდეა ჰქონდეს…

ჩემი პირველი ნაბიჯი CSS3-ის გამოცდა იყო. მაგის ანიმაციების მოდული ძალიან მომწონს და ვიფიქრე იქნებ როგორმე მის ფარგლებში ჩავმჯდარიყავი, მაგრამ როცა ანიმირებულ ელემენტებს შორის კოლიზიების დადგენა დამჭირდა, გავიჭედე :)

კოლიზიის შესახებ ინფორმაციას ბრაუზერი ვერ მაძლევს css ანიმაციის დროს (და არც მაქვს მაგის მოთხოვნის უფლება). შესაბამისად, როგორღაც მოძრავი ობიექტების ადგილსამყოფელის გაკონტროლება მჭირდებოდა. ეს ნიშნავს რომ რაღაც ინტერვალით ვამოწმო თითოეული მათგანის კოორდინატი და თანაკვეთა შევამოწმო, ამასთან ინტერვალებში არ გადავახტე კოლიზიის მომენტს.

როგორც აღმოჩნდა, მოძრავი ობიექტის კოორდინატებიც არ არის ზედაპირზე. თუ ვინმეს დაგჭირდებათ, ვებკიტში არის ერთი ადგილი საიდანაც მათი აღება შეიძლება – ეს არის ტრანსფორმაციის მატრიცა, რომელიც ახალი მდგომარეობის გამოსათვლელად გამოიყენება. ეს მატრიცა შეიცავს მიმდინარე კოორდინატების ინფორმაციასაც. მაგალითად:

var animatedElement = document.getElementById("animatedElement");
var transform = window.getComputedStyle(animatedElement).webkitTransform;
var curTransform = new WebKitCSSMatrix(transform);
console.log(animatedElement.offsetLeft + curTransform.m41);
console.log(animatedElement.offsetTop + curTransform.m42);

ამიტომ გადავწყვიტე ჩემს შემთხვევაში მხოლოდ CSS3-ის იმედზე ვერ ვიქნებოდი და ისევ ჯავასკრიპტს მოვკიდე ხელი. იდეაში საჭიროა შემდეგი: უნდა გავაკეთო კადრების ციკლი. დროის პატარა ინტერვალებში უნდა მოხდეს ელემენტების მდგომარეობის / მდებარეობის განახლება და შესაბამისად გადახატვა. კოლიზიების შემოწმებაც მანდ შეიძლება – მდგომარეობის გენერაციის დროს.

წინათ ჯავასკრიპტის ანიმაცია რომ გაგეკეთებინათ, ერთმანეთში ჩადგმული და გადაბმული setTimeout ან setInteval მეთოდებით მოგიწევდათ ანიმაციის ციკლის შეკვრა. მაგალითად, ასე:

function draw(){
    console.log('timestamp ' + +new Date());
    // update positions and redraw frame
}
setInterval(draw, 10);

ყოველ 10 მილიწამში მოხდება draw მეთოდის გამოძახება და მაგ მეთოდში იქნებოდა ანიმაციის ახალი მდგომარეობის გენერაცია.

ზოგი ახლაც იყენებს ამ გზას მარტივი ამოცანების გადასაჭრელად, თუმცა გასათვალისწინებელია მათი პრობლემები:

1. არაზუსტი დრო. დაყოვნება, რომელსაც ამ მეთოდებს (setTimeout ან setInteval) მილიწამების სახით გადავცემთ არგუმენტად, მხოლოდ იმას ნიშნავს, რომ ჩვენი ფუნქციები ამ დროის შემდეგ ბრაუზერის UI thread queue-ში ჩაემატებიან და თავის რიგს დაელოდებიან შესასრულებლად. შესაბამისად თუ UI ნაკადი სხვა საქმითაა დაკავებული, ჩვენი ანიმაციის ფრაგმენტი ზუსტად საჭირო დროს ვერ შესრულდება.

2. Browser timer resolution. მისი ქართული შესატყვისი ვერ მოვიფიქრე – ეს არის დრო, რამდენ ხანში ერთხელაც ახლდება საათი. მაგალითად, წინათ ბრაუზერები OS-ის სისტემურ დროს უყურებდნენ, რომელსაც ეს რეზოლუცია გაცილებით დიდი ჰქონდა (მაგალითად 10-15 მილიწამი), შესაბამისად რამდენიმე მილიწამის მითითების შემთხვევაში, შედეგის განსაზღვრა ვერ ხდებოდა. დღეს მთლად ასე აღარ არის, მაგრამ ამაზე ლაპარაკი შორს წამიყვანს.

მოკლედ რომ ვთქვათ, არ იყო გარანტია თუ როდის გადახატავდა ბრაუზერი ეკრანს.
შემდეგ მოზილას ერთ-ერთი თანამშრომელი მივიდა ახალ იდეამდე და Firefox-ში ჩაემატა მეთოდი mozRequestAnimationFrame, რომელიც შემდეგ სხვა ბრაუზერებმაც გადაიღეს და სპეციფიკაციაში requestAnimationFrame სახელით შევიდა.

ამ მეთოდს მხოლოდ ერთი პარამეტრი აქვს – თქვენი ფუნქცია, რომელსაც ბრაუზერი ეკრანის გადახატვამდე გამოიძახებს. ხოლო თქვენს ქოლბექს გამოძახების დროს გადმოეცემა მაღალი სიზუსტის ტაიმსტემპი – დრო როცა requestAnimationFrame-მა ქოლბექების გამოძახება დაიწყო მიმდინარე კადრისთვის.

მაგალითად:

function step(timestamp) {
    console.log(timestamp);
    requestAnimationFrame(step);
}
requestAnimationFrame(step);

requestAnimationFrame-ს გამოძახებით მხოლოდ მომდევნო გადახატვაზე გამოიძახება ქოლბექი. რადგან ჩვენ ანიმაციის ციკლი გვჭირდება, მოგვიწევს რომ ქოლბექში შიგნით ხელახლა მივებათ ბრაუზერის მოვლენას.

ეს მაღალი სიზუსტის ტაიმსტემპი არის DOMHighResTimeStamp ობიექტი და განსხვავდება ჩვეულებრივი Date.now() ფუნქციის დასაბრუნებელი მნიშვნელობისგან. თუ მაგ ფორმატის მიმდინარე ტაიმსტემპი გჭირდებათ, მისი მიღება შეიძლება performance.now() მეთოდით.

CNC მანქანების დაპროგრამება

ორი დღის წინ თსუ-ში მაგისტრატურა დავამთავრე. ჩემი ბოლო სემესტრში შესრულებული კვლევა CNC მანქანებს და მათ დაპროგრამებას ეხებოდა. თემა არც ისე ახლოს არის ჩემს საქმიანობასთან, მაგრამ მაინც საინტერესო იყო ჩემთვის. განსაკუთრებით კი ექსპერიმენტები ამ მანქანების ახალი პროგრამირების ენით, რომლის შესახებაც უფრო დაწვრილებით თემაში მიწერია.

რამდენადაც ვიცი, საქართველოშიც არის პატარა საწარმოები, რომლებიც ასეთი ტიპის მანქანებს იყენებენ, თუმცა პასუხი ვერ მივიღე მათგან.

შემთხვევით ვინმეს ხომ არ ჩაგიტარებიან ექსპერიმენტები ან სახლში აგიწყვიათ მსგავის მანქანა? თემის ბლოგზე გამოქვეყნება მე მგონი შეიძლება, რადგან თსუ-ს კონფერენციის საიტზე სახალხოდ დევს. თუ დაგაინტერესებთ ჩემი მიმოხილვა, ფაილი ამ ბმულზეა :)

lcamtuf_robot1

ყალიბის გაკეთება რობოტის ნაწილებისთვის. ფოტო აღებულია lcamtuf-ის საიტიდან.

othermill

Othermill კომპანია otherfab-ისგან

რჩევები საიტის ოპტიმიზაციისთვის (Front end)

დაახლოებით ერთი თვის წინ GeOlymp-ის საიტის გარეგნული მხარე განვაახლეთ თუმცა ბრაუზერის მხარეს სწრაფად ჩატვირთვისთვის საჭირო ოპტიმიზაცია არ გამიკეთებია. ამ პოსტში შევეცდები იმ თემებზე ვისაუბრო, რასაც ამ პროცესზე აქვს გავლენა; ამასთან, რეალურად შევასრულო ისინი საიტზე და ჩატვირთვის დროები შევადარო.

ახლა ქრომი მაჩვენებს, რომ ჯეოლიმპის ერთ-ერთი მთავარი გვერდის გახსნის დროს სერვერზე 42 მოთხოვნა იგზავნება, 307.9 კილობაიტი იტვირთება და 1.2 – 2.3 წამს ანდომებს გვერდის ჩატვირთვას. შევეცდები ამის გაუმჯობესებას..

1. ნაკლები რაოდენობის HTTP მოთხოვნის გაგზავნა სერვერზე
ერთ-ერთი ყველაზე მნიშვნელოვანი წესი, რომელსაც შესამჩნევი შედეგი აქვს, სერვერზე გასაგზავნი HTTP მოთხოვნების შემცირებაა. ეს მოთხოვნები იგზავნება თვითონ html ფაილის და ასევე თითოეული კომპონენტის წამოსაღებად, რასაც საიტი შეიცავს (სურათები, css და javascript ფაილები, ა.შ.).

http მოთხოვნა საკმაოდ მძიმე ოპერაციაა, რადგან მის შესასრულებლად საჭიროა DNS სერვერთან მისვლა, დომენის შესაბამისი ip მისამართის მიღება, მიღებული მისამართის საშუალებით ჰოსტ სერვერის მიგნება და ბოლო ბოლო ვებსერვერიდან რესურსის წამოღება. თუ ეს პუნქტები გეოგრაფიულადაც დაშორებულია (მაგალითად სხვადასხვა ქვეყნებში), მოთხოვნას უფრო მეტი სერვერის და ქსელის გავლა უწევს და დრო შესაბამისად იზრდება (ილუსტრაცია: როგორ მუშაობს ინტერნეტი).

Continue reading