Agroguía es una pequeña empresa que monté hace ya 7 años y medio que se dedica a hacer productos para agricultores. Siendo realistas solo se dedica a hacer un producto que es lo que vendemos, sin embargo hemos hecho y hacemos otras cosas.
Agroguía nació como un “y si probamos” y todos y cada uno de los años, al finalizar la temporada agrícola, sabía que aquel año sería el último. Cómo iba una empresa de 3 fulanos en un poblado de Valladolid a sobrevivir, vendiendo sólo por internet. Todos y cada uno de estos años me he confundido, lo que viene a significar que hemos superado las espectativas con creces.
Hay que tener en cuenta que hace 8 años lo de las startup y estas modernidades no se llevaba, aquí salías a pecho descubierto, sin tener ni puta idea pero con un par de huevos. Agroguía nunca ha sido una startup, cuando la cree no fue para crecer mucho, ni para venderla al cabo de 3 años, ni para captar un VC. Símplemente necesitaba dinero por que básicamente estábamos [muy] metidos en la mierda (es lo que tiene ser hijo de agricultor sin tierras).
Es cierto que este año no hemos dedicado mucho tiempo, pero algunas cosas hemos hecho:
· Estamos rematando el nuevo producto que vamos a sacar. Escribiré un post acerca de este tema, de momento os dejo un video:
Me gustaría destacar que sólo hay una persona dedicada 100% en Agroguía, los demás (@jatorre, @saleiva y yo) hacemos cosas puntuales y echamos un cable en momentos concretos pero nos dedicamos a @vizzuality la mayor parte del tiempo.
Este año vamos a trabajar duro en la parte de tracking, consiguiendo más distribuidores y dando el puto mejor servicio que puedas tener si compras un sistema de guiado GPS agrícola.
If you are developing a medium/big javascript frontend application you do want to have a isolation mechanism for views. This is the only way to keep your application working as expected as its size grows.
For the moment HTML does not provide a native way to do this so you have to rely on javascript side to do this. Ok, that’s not totally true, iframes
are a sandbox (spotify uses them to isolate views) and shadow DOM is not still there.
There are a lot of libraries to do this, in CartoDB we use Backbone, it’s simple, small enough to fully understand it, no magic, no extensions on top of HTML and provide a evented system to comunicate model and views.
The typical Backbone view looks like this:
var View = Backbone.View.extend({
initialize: function() {
this.model.bind('change:attr', this.render, this);
}
})
So every time attr
changes the view is rendered. In current Backbone version you have listenTo
method which tracks which objects are attached to a given one but when we started to use Backbone that method didn’t exist so we use the 3rd argument to know what object a signal is attached to. Easy but you have to remember to pass the this
always you link a signal.
When a view is removed all those links must be removed, if it’s not done those views are going to last forever. That’s “only” a memory use but imagine the view have some side effects like saving another model to the server…
During last chrismas I decided to do a big refactor in some part of CartoDB (wizards if you know it), and some views had binding leaks. Find them is really hard and sometimes it takes hours to find them even if you have tools. For example, we have a checker to detect leaks while the app is running, just execute cdb.core.View.runChecker int the console and it will show a list of “missing bindings”. It works but only with the views that are currently created.
I though it would be better to find them in testing stage so I created a jasmine (*) helper:
it("should not have leaks", function() {
expect(view).toHaveNoLeaks();
});
What basically does is:
_subviews
array to track them, but this is for another post)Really simple, easy to use in all the view tests. It saved me hours of in-app testing. The function itself is defined here if you want to take a look. It can be improved with a more smarter logic but it’s enough for the moment.
NOTE: this post is actually a mail I was going to send to frontend cartodb developers but I though it may result useful for someone else
(*) we use jasmine but I totally hate it
Ya casi hemos terminado el 2013 y toca la típica review y objetivos para el año que viene.
Este año ha sido intenso, algunas de las razones son las siguientes:
No creo ni en la suerte ni en el destino pero mi intuición me dice que el año que viene va a ser movidito. Sólo espero sacar los productos que tengo pendientes y ver como evolucionan, mejorar mi puto inglés de una vez por todas (objetivo recurrente durante los últimos 6 años), pasarmelo lo mejor posible y quizá cerrar alguna que otra herida del pasado.
Qué bonito es trabajar desde casa, leer el libro de 37signals y asentir, sacar pecho “yo trabajo desde casa, soy el puto amo” y tantas otras cosas. Pero como todo, generalizar es la receta perfecta para darte la hostia. Cada persona, empresa y situación es diferente.
Estas son algunas de las cosas que he sufrido y sufro durante el tiempo que he trabajado en remoto:
lo de trabajar asíncrono está muy bien, pero algunas cosas requieren sincronismo. Si a un usuario (que paga) no le funciona algo y necesitas de alguien o alguien de ti necesitas ser síncrono y rápido. Algunos servicios tardan días en darte respuesta, pero en general no me gusta hacer esperar si alguien paga.
te pierdes la mitad de la historia y tienes que recomponerla. Muchas veces las cosas se hablan, pero claro, no estás en la oficina y entonces la gente te empieza a hablar de cosas. Te toca entonces buscar las piezas que no encajan. Y esto es bastante difícil y require esfuerzo y tiempo (ese que ganas en el viaje de metro que no haces).
el músculo de trabajar con gente se pierde. Sí ganas el trabajar con gente en remoto, ahí cada uno valore lo que es más interesante para él.
el músculo de hablar con gente, no necesariamente de trabajo, se pierde igualmente. Es algo que he constatado con más trabajadores remotos, a menudo evitas hablar con la gente, pierdes el hilo de las conversaciones muy fácilmente, etc.
si no eres un poco autodisciplinado terminas por no salir de casa, trabajar cuando no debes, etc. El momento de irte de la oficina es un punto de inflexión difícil de reproducir en casa.
no ves el jeto de tu compañero de trabajo, no sabes si hoy está triste, ha venido tajado, está hasta las narices, concentrado, presionado, alegre, risueño o atontado. Y este es posiblemente el mayor problema.
Ahora que javascript está tan de moda, es tan potente y nos permite hacer lo que código nativo nos permitía hacer en un 486 los desarrolladores estamos empezando a hacer cosas algo más interesantes que poner texto y enlaces como juegos o animaciones. Lo cierto es que a pesar de que javascript sea aún lento para hacer cosas con mucha carga matemática, tenemos a nuestra disposición tarjetas gráficas muy potentes y gracias a las mejoras que últimamente han incluído en los navegadores, podemos usarlas directamente (con WebGL) o indirectamente (CSS3, SVG, canvas)
El objetivo de este artículo no es explicar como renderizar rápido ni de como aprovechar la GPU si no de como hacer una animación.
Lo primero que se nos ocurre es el típico setInterval. Seguro que conoces la función, pero básicamente lo que hace es llamar a una función que le especificas cada cierto tiempo. Por ejemplo, la animación sería tal que así:
setInterval(function () {
update()
render();
}, 20)
Esto llamará a la función cada 20ms, esto es 50 frames por segundo (FPS). Para la mayoría de casos cumple perfectamente. Pero qué pasa si la función update tarda 30ms en terminar? Realmente no sé que es lo que hace el navegador, supongo que cada uno tendrá su política, pero cabe pensar que con el tiempo se empezarán a aculumar eventos de llamada a esa función ya que no es capaz de terminar en menos de 20ms.
Aunque el navegador fuese muy listo y gestionase eso perfectamente no podemos hacer la animación correctamente porque no sabemos el tiempo que ha pasado de un frame a otro.
Para evitar que se nos acumulen llamadas por que la función es lenta vamos a pedir renderizar un frame solo cuando hayamos terminado:
var logic = function () {
update()
render();
setTimeout(logic, 20);
};
setTimeout(logic, 0);
Vale, ahora ya no se acumulan eventos y si la función update o render tarda más que esos 20ms no pasará nada, sólo actualizaré la animación cuando el frame anterior haya terminado. Mejor que antes pero aún estamos animando como unos tristes.
Imaginemos que estamos controlando una animación de una imagen moviendose por la pantalla. Queremos que en 1 segundo se mueva 500px:
var img = new Image();
img.src = 'image.png';
img.style['position'] = 'absolute';
var pos = 0;
img.style['left'] = pos + 'px';
var logic = function () {
// update
pos += 500*0.02;
img.style['left'] = pos + 'px';
// browser will render the img on style change
//render();
if(pos < 500) {
setTimeout(logic, 20);
}
};
setTimeout(logic, 0);
En un segundo, a 50FPS habremos dibujado 50 frames con lo cual pos será 500 al pasar un segundo. No está mal, pero imaginemos que tenemos una máquina lenta, tan lenta que no es capaz de actualizar a 50FPS. En ese caso tendremos que la imagen llegará a la posición 500px, pero en más de un segundo. No estamos controlando el tiempo.
Para ello podemos usar el tiempo transcurrido desde el último frame. Vamos, lo que se lleva aplicando en videojuegos simples toda la vida:
var t0 = new Date().getTime();
var logic = function () {
var t1 = new Date().getTime();
var dt = t1 - t0;
t0 = t1;
// update
pos += 500*dt;
img.style['left'] = pos + 'px';
// browser will render the img on style change
//render();
if(pos < 500) {
setTimeout(logic, 20);
}
};
setTimeout(logic, 0);
Ya está, ahora aunque la máquina sea lenta controlamos la animación correctamente
Ahora podemos controlar un poco mejor la animación gracias a requestAnimationFrame. Para variar es una función que no es standard y cada navegador la implementa con el nombre que le da la real gana.
En resumen, esta función llama a la función que nostros queramos cuando el navegador vaya a renderizar. Eso es bueno, por que si nosotros llamamos 1000 veces por segundo a esta función como mucho llamará a la animación el máximo que pueda actualizar. Asumamos que esto es bueno, aunque si estás haciendo algo medio serio la actualización de la lógica puede ir parcialmente desacoplada del renderizado (luego veremos un ejemplo).
Así que la cosa quedaría tal que así:
var logic = function () {
var dt = //calculate dt
uddate(dt);
render()
requestAnimationFrame(logic);
}
requestAnimationFrame(logic);
Perfecto, además podemos llamar a requestAnimationFrame desde cualquier lado para que comience la animación.
Normalmente (la ley del copy & paste así lo dice) se define requestAnimationFrame tal que así:
var requestAnimationFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(a){setTimeout(a,20);};
aunque en mi opinión debería ser algo así:
function createRequestAnimationFrame(fn) {
var rendered = true;
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(a) {
if(rendered) {
rendered = false;
setTimeout(function() {
fn();
rendered = true;
}, 20);
}
};
}
logic = function() {
update();
myAnimationRequestFrame();
}
myAnimationRequestFrame = createRequestAnimationFrame(logic);
myAnimationRequestFrame();
element.onclick = function() {
// logic
myAnimationRequestFrame();
}
De este modo aunque llamemos 1000 veces a myAnimationRequestFrame sólo se llamará a la animación cuando toque
Qué pasa si el navegador es muy lento. Bueno, lo ideal es quitar la animación, pero claro, no sabemos a priori si la máquina es lenta (bueno, puedes comprobar si es Firefox). Para evitar que todo se vaya al traste quizá lo mejor sea limitar la animación para que no se vaya de madre.
var logic = function () {
var dt = //calculate dt
dt = Math.min(0.05, dt);
update(dt);
render()
requestAnimationFrame(logic);
}
requestAnimationFrame(logic);
De esta forma si la animación es muy lenta la ralentizamos pero así estamos seguros de que la lógica no falla. También nos protege del caso en el que el usuario cambie de tab (requestAnimationFrame no asegura que se llame al callback si la pestaña donde se ejecuta el código no está activa), al volver a activarse el dt será muy grande y podría desestabilizar en caso de no controlar el dt. Imagino que habrá algún API para controlar si la pestaña es activa…
Imaginemos que la función update hace algo más complejo que sumar, por ejemplo una integración numérica. En ese caso si el dt es muy grande la integración se puede ir al traste, necesitamos dt suficientemente pequeños. Ese caso quedaría resuelto también.
Por ejemplo, queremos que en el ejemplo anterior la imagen vaya hasta 500 pero suavemente:
var smooth = 0.1;
var distance_to_target = 500 - pos;
pos += distance_to_target*smooth*dt;
Si dt es muy grande lo que pasará es que esa función empezará a oscilar haciendo un efecto muelle en vez de llegar suavemente o incluso oscilará hasta el infinito si dt es muy grande (a que os suena de de cuando érais jóvenes y estudiabais? si eres teleco/industrial y no te suena vuelve a la carrera).
Mejor que explicarlo aquí, id a este artículo del mítico Javier Arévalo y grabadlo en vuestra mente: