-Поиск по дневнику

Поиск сообщений в rss_rss_hh_new

 -Подписка по e-mail

 

 -Статистика

Статистика LiveInternet.ru: показано количество хитов и посетителей
Создан: 17.03.2011
Записей:
Комментариев:
Написано: 51

Habrahabr/New








Добавить любой RSS - источник (включая журнал LiveJournal) в свою ленту друзей вы можете на странице синдикации.

Исходная информация - http://habrahabr.ru/rss/new/.
Данный дневник сформирован из открытого RSS-источника по адресу http://feeds.feedburner.com/xtmb/hh-new-full, и дополняется в соответствии с дополнением данного источника. Он может не соответствовать содержимому оригинальной страницы. Трансляция создана автоматически по запросу читателей этой RSS ленты.
По всем вопросам о работе данного сервиса обращаться со страницы контактной информации.

[Обновить трансляцию]

Выходим в интернет за пределами РФ: (MikroTik<->Ubuntu) * GRE / IPsec

Воскресенье, 10 Сентября 2017 г. 22:57 + в цитатник
eov сегодня в 22:57 Администрирование

Выходим в интернет за пределами РФ: (MikroTik<->Ubuntu) * GRE / IPsec

    Позволю себе опубликовать свой опыт применения сетевых технологий в меру моей испорченности для выхода в интернет из-за пределов РФ. Не будем рассуждать о том, зачем это нужно. Надеюсь, что все всем и так понятно.

    Итак, у нас есть статический публичный IP адрес, который приходит Ethernet шнуром в MikroTik RouterBOARD 750G r3 (hEX). Пробуем собрать вот такую конструкцию.


    Настройку L2tp линка в рамках этой статьи я не описываю, а на схеме он нарисован только потому, что в ней упоминается.

    1. Поднимаем VPS


    Как вы уже догадались, нужна система, которая включена в интернет за пределами РФ. Большие деньги на это тратить не хотелось, и я остановился на Aruba Cloud. Здесь всего за 1 евро в месяц дается VM в локациях Италия, Чехия, Германия, Франция и Великобритания. Я свой выбор остановил на Чехии, потому что ping до наших ресурсов оказался на 20ms меньше, чем с Итальянского (50ms против 70ms). Ubuntu 16.04 LTS поднялась очень быстро. Войти в нее удалось раньше, чем «позеленел» статус в интерфейсе «облака».



    Сервер устанавливается в минимальной конфигурации. Не помешает установить утилитку traceroute.

    $ sudo apt install traceroute

    2. Настраиваем GRE между MikroTik и Ubuntu


    Выбор в пользу GRE был сделан по нескольким причинам:

    • просто и понятно настраивается;
    • легко траблешутится;
    • маршрутизация проще некуда;
    • элементарно отрисовывается график загрузки интерфейса в MikroTik.

    Итак, на стороне Ubuntu описываем интерфейс tun1 в /etc/network/interfaces

    $ sudo cat << EOF >> /etc/network/interfaces
     iface tun1 inet static
        address 192.168.10.1
        netmask 255.255.255.0
        pre-up iptunnel add tun1 mode gre local 80.211.x.x remote 188.x.x.x ttl 255
        up ifconfig tun1 multicast
        pointopoint 192.168.10.2
        post-down iptunnel del tun1
    EOF
    

    Здесь все просто:

    • 80.211.x.x — адрес VM с Ubuntu в Чехии;
    • 188.x.x.x — внешний адрес MikroTik;
    • 192.168.10.1 — адрес на туннельном интерфейсе tun1 на стороне Ubuntu;
    • 192.168.10.2 — адрес туннельного интерфейса на MikroTik;

    Активацию этой части конфигурации я по-старинке делаю так:

    $ sudo service networking restart

    У нас получился вот такой интерфейс:

    $ ifconfig tun1
    tun1      Link encap:UNSPEC  HWaddr 10-D3-29-B2-00-00-B0-8A-00-00-00-00-00-00-00-00
              inet addr:192.168.10.1  P-t-P:192.168.10.2  Mask:255.255.255.255
              inet6 addr: fe80::200:5ffa:50d3:c9c2/64 Scope:Link
              UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1476  Metric:1
              RX packets:379 errors:0 dropped:0 overruns:0 frame:0
              TX packets:322 errors:4 dropped:7 overruns:0 carrier:0
              collisions:0 txqueuelen:1
              RX bytes:103387 (103.3 KB)  TX bytes:159422 (159.4 KB)
    
    

    Со стороны MikroTik настройка тоже несложная. Я делал это из Web-интерфейса.



    Обращаю внимание на то, что не сконфигурирован keepalive. Мне так и не удалось включить ответную часть на Ubuntu. Если не будет трафика, то интерфейс на MikroTik будет «уходить» из running и подниматься, только если пойдет трафик со стороны Ubuntu.

    На этом этапе у нас должны подняться туннельные интерфейсы с двух сторон. Проверить это просто. Достаточно запустить ping c Ubuntu в сторону MikroTik.

    $ ping 192.168.10.2
    PING 192.168.10.2 (192.168.10.2) 56(84) bytes of data.
    64 bytes from 192.168.10.2: icmp_seq=1 ttl=64 time=56.0 ms
    64 bytes from 192.168.10.2: icmp_seq=2 ttl=64 time=59.9 ms
    64 bytes from 192.168.10.2: icmp_seq=3 ttl=64 time=56.3 ms
    64 bytes from 192.168.10.2: icmp_seq=4 ttl=64 time=56.1 ms
    --- 192.168.10.2 ping statistics ---
    4 packets transmitted, 4 received, 0% packet loss, time 3004ms
    rtt min/avg/max/mdev = 56.091/57.137/59.936/1.618 ms
    user@vps:~$ 

    Настраиваем маршрутизацию в сторону абонентских сетей в туннельный интрефейс

    Приватные IP адреса локальной сети, из которой осуществляется выход в интернет — 192.168.1.0/24. Сеть 192.168.6.0/24 — это сеть, выделенная для подключения к MikroTik по L2TP из «внешнего мира». Для того, чтобы работали компьютеры локальной сети и удаленные устройства, добавляем маршруты на обе сети в конец файла /etc/network/interfaces

    $ sudo cat << EOF >> /etc/network/interfaces
    #static route"
    up ip ro add 192.168.1.0/24 via 192.168.10.2
    up ip ro add 192.168.6.0/24 via 192.168.10.2
    EOF
    

    Еще раз просим систему обновить сетевые настройки

    $ sudo service networking restart

    Включаем ip_forward

    $ sudo echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
    $ sudo sysctl -p

    Прописываем маскарадинг

    $ sudo iptables -t nat -A POSTROUTING -j SNAT --to-source 80.211.x.x -o eth0

    Настаиваем маршрутизацию на MikroTik

    Поскольку у меня не стоит задача «развернуть» весь трафик в интернет через другую страну, то в туннельный интерфейс я буду маршрутизировать только интересующие меня ресурсы. В качестве такого ресурса я выбрал Linkedin.

    Итак, добавляем маршруты на MikroTik (через терминалку):

    /ip route
    add comment=linkedin distance=1 dst-address=91.225.248.0/22 gateway=gre-tunnel1
    add comment=linkedin distance=1 dst-address=108.174.0.0/20 gateway=gre-tunnel1
    add comment=linkedin distance=1 dst-address=185.63.144.0/22 gateway=gre-tunnel1
    

    К этому моменту у меня начал открываться заветный ресурс и на этом можно было бы и закончить, поскольку, даже несмотря на то что GRE трафик не шифрован и его прекрасно видно в Wireshark, далеко не все провайдеры DPI-ят трафик (а если и DPI-ят, то точно не заглядывают внутрь туннелей для отслеживания трафика от запрещенных ресурсов), да и АПК «Ревизор» не интересуется тем, какой реально абонентами потребляется трафик.

    На дальнейшие эксперименты меня натолкнул тот факт, что в настройках GRE интерфейса MikroTik есть опция IPsec Secret. Неужели действительно все так просто?!

    3. Зашифровываем туннель


    В качестве шифровальщика на стороне Ubuntu я выбрал strongSwan. Пример конфигурации я взял из материала Configure GRE over IPSec secured private channel, но сразу не заработало и пришлось внести некоторые правки.

    Устанаваливаем

    $ apt install strongswan

    Вносим в основной конфигурационный файл strongSwan вот это:

    $ cat << EOF > /etc/ipsec.conf 
    # ipsec.conf - strongSwan IPsec configuration file
    
    config setup
        charondebug="ike 2, knl 2, cfg 2, net 2, esp 2, dmn 2,  mgr 2"
    
    conn %default
    #    keyexchange=ikev2
    
    conn mikrotik
        # Try connect on daemon start
        auto=start
    
        # Authentication by PSK (see ipsec.secret)
        authby=secret
    
        # Disable compression
        compress=no
    
        # Re-dial setings
        closeaction=clear
        dpddelay=30s
        dpdtimeout=150s
        dpdaction=restart
    
        # ESP Authentication settings (Phase 2)
        esp=aes128-sha1
    
        # UDP redirects
        forceencaps=no
    
        # IKE Authentication and keyring settings (Phase 1)
        ike=aes128-sha1-modp2048,aes256-sha1-modp2048
        ikelifetime=86400s
        keyingtries=%forever
        lifetime=3600s
    
        # Internet Key Exchange (IKE) version
        # Default: Charon - ikev2, Pluto: ikev1
        keyexchange=ikev1
    
        # connection type
        type=transport
    
        # Peers
        left=188.x.x.x
        right=80.211.x.x
    
        # Protocol type. May not work in numeric then need set 'gre'
        leftprotoport=47
        rightprotoport=47
    EOF 
    

    Прописываем pre-shared-key (PSK) в /etc/ipsec.secrets

    $ echo "80.211.x.x 188.x.x.x : PSK VeryBigSecret" >> /etc/ipsec.secrets

    Перезапускаем strongSwan

    $ ipsec restart

    На этом настройка Ubuntu практически завершена. Приступаем к настройке MikroTik.

    Я выставил вот такие настройки дефалтового proposal



    Настало время вписать в поле IPsec Secret тот же PSK, что был указан в /etc/ipsec.secrets на сервере (VeryBigSecret).

    Если все получилось, то выполняем диагностику на обоих концах туннеля.

    MikroTik



    Если «провалиться» глубже, то должно быть вот так:



    Ubuntu

    $ ipsec status
    Security Associations (1 up, 0 connecting):
            mikrotik[2]: ESTABLISHED 60 minutes ago, 80.211.x.x[80.211.x.x]...188.x.x.x[188.x.x.x]
            mikrotik{2}:  INSTALLED, TRANSPORT, reqid 1, ESP SPIs: cc075de3_i 07620dfa_o
            mikrotik{2}:   80.211.x.x/32[gre] === 188.x.x.x/32[gre]
    

    Теперь GRE (protocol 47) между MikroTik и Ubuntu шифруется IPseс (ESP). Анализ pcap файла, снятого tcpdump-ом, это подтвердил.



    Вроде бы, все работает и заветный ресурс открывается. Однако после того, как я направил в туннель еще несколько ресурсов, оказалось, что не все так хорошо, как казалось изначально. Удалось выяснить, что перестали проходить пакеты большого размера (вернее, они перестали правильно фрагментироваться).

    Решение нашлось быстро. Оказалось, что достаточно уменьшить MTU до 1435 на обоих концах туннеля.

    MikroTik



    Ubuntu — добавляем mtu 1435 в описание интерфейса.

    auto tun1
    iface tun1 inet static
        address 192.168.10.1
        netmask 255.255.255.0
        pre-up iptunnel add tun1 mode gre local 80.211.x.x remote 188.x.x.x ttl 255
        up ifconfig tun1 multicast
        pointopoint 192.168.10.2
        mtu 1435
        post-down iptunnel del tun1
    

    Диагностика установления IPsec соединения не тривиальна. На Ubuntu логи читаются значительно проще, чем на MikroTik. По умолчанию на MikroTik выключено логирование IPsec. Включается просто, но проводить анализ проще через терминалку.

    Мне удалось добиться скорости скачивания 25 Мбит/с и отдачи 50 Мбит/с. На мой взгляд, этого вполне хватает для комфортной работы с ресурсом. Почему не разгоняется больше — это пока загадка. Загрузка процессоров на обоих концах туннеля не высока. Основная версия — это ограничение скорости на стороне облака. Как-нибудь на досуге погоняю файлы между серверами без шифрования.

    P.S.: В планах настроить IKEv2 туннель с моих устройств (я использую технику Apple) непосредственно на сервер с использованием сертификатов. Пока мне не удалось это сделать. Apple-девайсы почему-то не отвечают на запросы установления защищенного соединения со стороны сервера. Можно, конечно, сделать L2tp, но это уже не интересно: такой опыт у меня уже был.
    Original source: habrahabr.ru (comments, light).

    https://habrahabr.ru/post/337426/


    Метки:  

    Специфика использования Redux в Polymer и Vue

    Воскресенье, 10 Сентября 2017 г. 22:23 + в цитатник
    kolesoffac сегодня в 22:23 Разработка

    Специфика использования Redux в Polymer и Vue


      Как я уже писал в своих предыдущих статьях я работал и с polymer и с vue в связке с redux. Поэтому хотелось бы поделиться опытом, связанным со спецификой использования redux в данных библиотеках. Рассматривать будем на простейших атомарных контролах: нативных (input, checkbox) и обернутых, в виде компонентов данных библиотек.
      В статье я опуская описание настройки интеграции redux с polymer и vue, а так же описание азов самого redux, дабы не эту тему хочу раскрыть в статье.

      0. Введение


      Сначала вспомним один из основных принципов redux:
      The only way to change the state is to emit an action, an object describing what happened.

      Исходя из него ясно, что напрямую мы не можем изменить состояние, а сделать это можем только через диспатч экшена после наступление необходимого event'а.
      Схематично это можно изобразить так:

      Как видим наблюдается односторонний поток данных.

      1. Нативные контролы


      polymer


      Очень удобная вещь в polymer при связке с redux дак это односторонний биндинг.

      template:


      js-code:
      changeInput: function(e) {
        this.dispatch("setText", e.currentTarget.value);
      }


      Поэтому с input все, в принципе стандартно: при событии change диспатчим action и после чего измененное значение попадает в propFromReduxStore и контрол перерендерится уже с новым значением.

      vue


      C vue немного другая ситуация, в нем нет как такагого одностороннего биндинга, как в polymer. Но подобную функциональность можно достигнуть через модификатор sync

      template:


      js-code:
      changeInput: function(e) {
        this.actionsRedux("setText", e.currentTarget.value);
      }


      Остальное все как и в варианте с polymer.

      2. Компоненты


      С компонентами сложней, так как это совокупность методов, событий, «завернутые» в компоновку html-элементов в виде шаблона.
      Схематичное описание работы компонента:

      Как видим о событии компонента мы узнаем уже обо всем постфактум, что противоречит принципу redux описанному выше. И что бы избежать неконсистентной ситуации, когда контрол уже перерендерился в виду своего внутреннего состояния, а прибинденная модель еще не поменялась через action и не соответствует данному представлению необходимо производить дополнительные действия для блокировки прямого изменения state.

      polymer


      На примере компонента paper-checkbox

      template:


      js-code:
      changeCheck: function(e) { //Здесь ловим клик по компоненту
        //Предотвращаем bubbling события, что бы компонент сразу не перерендерил компонент 
        e.stopPropagation();
        this.dispatch("setChecked", !this.propFromReduxStore);
      }
      


      vue


      На примере компонента el-checkbox

      template:
      
      


      js-code:
      changeCheck: function() {
      this.actionsRedux("setChecked", !this.propFromReduxStore);
      }


      В компоненте может даже и не быть события click, а если и есть, то узнаем мы об его наступлении уже постфактум, не говоря уже об его подалении, но зато есть модификатор native, который позволяет получить доступ ко всем возможным нативным событиям. Так же есть модификатор stop, который даже позволит нам не писать e.stopPropagation(), как это было с polymer. Малость, а приятно.

      React не рассмотрел, так как на практике его не использовал, если не считать jsx-темплейтов в vue, но это совершенно не то. Но все-таки, насколько я наслышен от коллег по цеху, там таких проблем нет, в виду внутренней архитектуры обработки событий.
      Original source: habrahabr.ru (comments, light).

      https://habrahabr.ru/post/337564/


      Метки:  

      Считаем до трёх: четыре

      Воскресенье, 10 Сентября 2017 г. 22:05 + в цитатник
      haqreu сегодня в 22:05 Разработка

      Считаем до трёх: четыре

        Proof of concept: однотритный вычислитель



        Это уже четвёртая статья, по мере готовности будет продолжение. Оглавление:


        Вот так выглядит основная железка, о которой сегодня будет идти речь (больше перемычек богу перемычек!):



        Неблизкое будущее: система команд трёхтритного вычислителя


        Как я и говорил, моя цель — постройка хоть и примитивной, но осязаемой программируемой железки, работающей на троичной логике. Пока что я склоняюсь к следующей системе команд, предложенной тут:

        • каждая инструкция будет фиксированной длины (5 тритов)
        • два трита на идентификатор инструкции, три трита (одна триада) на её параметр

        NN (-4) — расширение на будущее (сложение/вычитание с переносом/заёмом, пропуск следующей команды в зависимости от флага знака и флага переноса и т.д.)
        NO (-3) — передать управление на ttt (в будущем можно переключать сегменты через регистр R13)
        NP (-2) — выполнить OPB ttt (универсальная бинарная команда) над R1 и R2 (две другие триады, задающие команду, берутся из R3 и R4) и установить флаг знака
        ON (-1) — выполнить OPA ttt (универсальная унарная команда) над R1 (результат записать в тот же R1) и установить флаг знака
        OO (0) — копирование регистров (см. далее)
        OP (+1) — записать триаду в регистр R1
        PN (+2) — записать триаду в регистр R2
        PO (+3) — записать триаду в регистр R3
        PP (+4) — записать триаду в регистр R4

        Копирование регистров:

        OONNN — скопировать R1 в R13
        OONNO — скопировать R1 в R12
        OONNP — скопировать R1 в R11
        OONON — скопировать R1 в R10
        OONOO — скопировать R1 в R9
        OONOP — скопировать R1 в R8
        OOPON — скопировать R1 в R7
        OOPOO — скопировать R1 в R6
        OOPOP — скопировать R1 в R5
        OOONN — скопировать R1 в R4
        OOONO — скопировать R1 в R3
        OOONP — скопировать R1 в R2
        OOOON — декремент R1 и установить флаг знака
        OOOOO — проверить R1 и установить флаг знака
        OOOOP — инкремент R1 и установить флаг знака
        OOOPN — скопировать R2 в R1
        OOOPO — скопировать R3 в R1
        OOOPP — скопировать R4 в R1
        OOPNN — скопировать R5 в R1
        OOPNO — скопировать R6 в R1
        OOPNP — скопировать R7 в R1
        OOPON — скопировать R8 в R1
        OOPOO — скопировать R9 в R1
        OOPOP — скопировать R10 в R1
        OOPPN — скопировать R11 в R1
        OOPPO — скопировать R12 в R1
        OOPPP — скопировать R13 в R1

        Обратите внимание, что копирование производится только из/в регистр R1. Копирование между другими регистрами придётся делать в две команды, но зато три трита параметра команды копирования OO мне позволяют адресовать аж 13 регистров.

        Память инструкций будет выполнена трёхпозиционными переключателями. Поскольку непосредственный параметр инструкции трёхтритный, то память команд будет адресоваться только тремя тритами, что даёт 27 инструкций, что мало. Посему будет несколько сегментов памяти инструкций, а регистр R13 будет использоваться для переключения между сегментами, что даёт шеститритную шину адреса, и реальное количество памяти команд будет ограничиваться только тем, насколько я устану эту память паять.

        Один сегмент памяти команд будет набран из пятнадцати вот таких платок:



        Каждая из таких платок несёт на себе 9 трёхпозиционных переключателей, для наглядности (особенно на фото) команды подсвечиваются двухцветными светодиодами.

        Но прежде чем бросаться в пайку гигантских количеств (несколько сотен) тримуксов, давайте сначала попробуем сделать какой-нибудь существенно более простой вычислитель просто в качестве подтверждения того, что на этом принципе можно что-то построить.

        Дело настоящего: однотритный вычислитель


        Итак, упростим до максимума. Наш вычислитель будет иметь всего девять команд, каждая из которых задаётся старшим тритом I и младшим тритом J:

        IJ
        NN — скопировать R1 в R4
        NO — скопировать R1 в R3
        NP — скопировать R1 в R2
        ON — декремент R1 и установить флаг знака
        OO — проверить R1 и установить флаг знака
        OP — инкремент R1 и установить флаг знака
        PN — скопировать R2 в R1
        PO — скопировать R3 в R1
        PP — скопировать R4 в R1

        Девятью командами получается адресовать максимум четыре регистра памяти, так что памяти будет четыре однотритных регистра и флаг знака. Памяти команд в вычислителе не будет вообще, а значит, и указатель текущей команды (счётчик) не нужен.

        Собираем регистры


        Железку я буду собирать и тестировать каждую её часть отдельно. Во-первых, при таком количестве и плотности проводов очень велика вероятность ошибки монтажа, а во вторых наши восточные партнёры зачастую продают не очень качественные перемычки :)

        Итак, у нас будут четыре одинаковые ячейки памяти с запоминанием по уровню, мы их подробно рассмотрели во второй статье:



        Выводить наружу доступ ко всем ячейкам затруднительно и нерационально, поэтому они будут адресоваться через мультиплексор один-к-четырём:



        Разумеется, это просто урезанная версия мультиплексора 1-к-9. В итоге схема нашей памяти выглядит следующим образом:



        То есть, память регистров — это железка, которая на вход берёт два трита адреса, а на выход предоставляет ноги C,A,Q соответствующей ячейки памяти. Вот видео тестирования доступа к регистрам:





        Адресуем правильные регистры в нужный момент


        Всё копирование регистров будет происходить через промежуточный буфер. То есть, сначала содержимое регистра копируется в буфер, а затем буфер копируется в конечный регистр.Мой однотритный компьютер будет тактироваться троичной пилой, по отрицательному уровню будет происходить копирование в буфер, а по положительному обратно из буфера в память.

        Вот эта схема позволяет адресовать нужный регистр памяти в нужный момент в зависимости от номера инструкции IJ и сигнала троичной пилы CLK.



        Красные мультиплексоры генерируют адрес ячейки, ИЗ которой копировать, зелёные мультиплексоры дают адрес ячейки, В которую происходит запись. Синие мультиплексоры переключают между ними в зависимости от сигнала CLK.

        Вот видео тестирования работы схемы адресации памяти:





        Непосредственно копирование


        Поскольку генерирование нужных адресов памяти происходит само при помощи вышеописанной схемы, то для того, чтобы не загромождать схему, мы можем забыть про то, что у нас четыре регистра, и представить, что памяти только один регистр + буфер.

        Копирование из регистра в буфер и обратно можно сделать крайне примитивно:



        По отрицательному сигналу CLK на ногу C буфера придёт -1, а на ноге C памяти будет 0, что означает, что буфер запомнит то, что будет ему подано на ногу A. По положительному сигналу CLK всё с точностью до наоборот, скопируем содержимое буфера назад в память.

        Теперь осталось вспомнить, что у нас есть не только команды копирования, но и команды увеличения/уменьшения регистра R1 на единицу. Конечная схема нашего компьютера выглядит так:



        Когда старший трит команды ненулевой, то выход Q буфера подаётся напрямую на вход A памяти. Когда же он нулевой, то выход Q буфера подаётся в память через полусумматор (схему полусумматора смотрите в первой статье). Аналогично происходит и установка флага знака.

        Ну а вот так выглядит железка в сборе:



        А вот видео её тестирования:





        Тестировать каждый кирпичик отдельно очень полезно, вот все дохлые перемычки, которые были найдены в процессе сборки только этой железки (привет нашим восточным партнёрам!)



        Подведём итог


        В принципе, как собирать конечный трёхтритный вычислитель вполне ясно, надо только собраться с духом и это сделать, причём, разумеется, не на макетке, так как по грубым оценкам мне на него понадобится сотни три-четыре тримуксов.

        Буду рад услышать новые идеи и замечания!
        Original source: habrahabr.ru (comments, light).

        https://habrahabr.ru/post/337606/


        Метки:  

        Intel научит дроны самостоятельности

        Воскресенье, 10 Сентября 2017 г. 21:08 + в цитатник
        1cloud сегодня в 21:08 Разработка

        Intel научит дроны самостоятельности

          На прошлой неделе мы рассказывали о том, как защитить облако на примерах осад средневековых замков. Сегодня расскажем, как Intel планирует начать обучение роботов.

          28 августа компания Intel представила визуальный процессор (VPU) Myriad X, который разработан специально для автономных интеллектуальных устройств. Главная особенность чипа — встроенный аппаратный ускоритель для нейросетей (Neural Compute Engine). Он отвечает за обучение искусственного интеллекта на периферийных устройствах, имея при этом низкое энергопотребление. Среди предложенных Intel сфер применения чипа числятся дроны, системы безопасности и наблюдения, VR/AR и другие устройства.


          / Flickr / Bidgee / CC

          По словам разработчиков, Myriad X — первая в мире однокристальная система, специально «заточенная» под ускоренное глубокое обучение. Neural Compute Engine помогает устройствам видеть, интерпретировать и реагировать на окружающую среду в реальном времени. С ускорителем производительность Myriad X достигает триллиона операций в секунду (TOPS).

          «Мы близки к тому, чтобы машинное зрение и глубокое обучение вошли в число стандартных требований к миллиардам ежедневно окружающих нас устройств», — сказал Реми Эль-Уаззани (Remi El-Ouazzane), вице-президент и генеральный менеджер Movidius, компании-разработчика чипов семейства Myriad.

          Архитектура VPU-чипа Movidius Myriad X VPU состоит из нескольких специализированных вычислительных устройств. Это несколько процессоров общего назначения, наряду с 16 процессорами SHAVE (Streaming Hybrid Architecture Vector Engine). Последние работают совместно с нейронным вычислительным движком.

          Процессоры SHAVE состоят из шестнадцати программируемых 128-битных векторных процессоров VLIW, конфигурируемых шин MIPI, аппаратных ускорителей и централизованной архитектуры встроенной памяти.

          В своем личном обращении Эль-Уаззани рассказывает, как устройства, наделенные искусственным интеллектом, сделают «наш мир безопаснее и продуктивнее». Автономные дроны-курьеры, камеры безопасности с полным пониманием происходящего и роботы с расширенным спектром реакций — в теории это возможно с Myriad X, поскольку глубокое обучение выполняется на уровне устройства без связи с облаком.

          «В этом и заключается мощь визуального процессора — платформы, специально разработанной для решения задач автономных устройств. VPU обеспечивает высокую вычислительную мощность там, где раньше она была недостижима», — заключает Реми.

          Применение VPU


          Однако наиболее вероятной сферой применения нового чипа в Intel видят беспилотники. Дроны становятся умнее. Например, Spark — мини-дрон от DJI — оборудован набором из камер и датчиков и процессором Vision Movivius Myriad 2, который помогает интеллектуальной системе бортового зрения обнаруживать и избегать столкновения с объектами, создавать 3D-карты, распознавать лица и жесты.

          И достигнутый уровень развития — не предел. По словам Кормака Брика (Cormac Brick), руководителя по бортовым системам искусственно интеллекта в Movidius, дроны учатся думать и реагировать.

          «В будущем вы сможете достать дрон из кармана, запустить его в воздух и оставить летать вокруг двора, пока вы жарите барбекю, — рассказывает Брик, — а через час он отправит на ваш телефон 45-секундный видеоклип или 10 лучших снимков, чтобы вы могли поделиться ими в социальных сетях».

          Movidius видят применение своим чипам и в сфере безопасности. В этом году их процессор Myriad 2 уже был интегрирован в камеры видеонаблюдения Dahua. Эти камеры далеко шагнули от традиционных технологий мониторинга и записи. Они обладают расширенными функциями анализа видео: мониторят плотность толпы, распознают лица, имеют стереоскопическое зрение, могут обнаруживать неправильно припаркованные автомобили.

          Есть и еще одно применение VPU нового поколения. В июле Intel представили мобильный модуль Movidius Neural Compute Stick с разъемом USB 3.0. Его задача — обучение искусственного интеллекта на различных устройствах. Характеристики и особенности те же: низкое энергопотребление при высокой производительности, компактность, офлайн-режим работы, нацеленность на приложения машинного зрения.

          Акцент на AI


          По данным Accenture, рынок AI в 12 развитых странах удвоит темпы роста к 2035 году. Например, только в Китае к 2020 году собираются установить 450 млн новых камер видеонаблюдения с системами распознавания лиц. К слову, компания Dahua Technology, которая уже экспериментировала с процессором Myriad 2, занимает второе место на рынке камер наблюдения и базируется в Ханчжоу.

          На AI-рынке у Intel до выхода Myriad X было несколько серьезных противников: Qualcomm Neural Processing Engine, процессор Google TPU 2.0, предназначенный для обучения нейронных сетей, и NVIDIA Tesla V100. По мнению аналитиков Seeking Alpha, самую серьезную конкуренцию Intel на рынке машинного обучения, который достигнет $5 млрд к 2021 году, составляет именно NVIDIA.

          Однако вскоре могут появиться и другие сильные конкуренты. В августе компания Microsoft представила проект Brainwave. Это платформа для глубокого обучения AI в реальном времени с низкой задержкой в обработке запросов. Архитектура Brainwave поддерживает выполнение более 130 тыс. вычислений за цикл. Работа в этом направлении вскоре позволит применять нейронные сети для выполнения бытовых задач.

          О компании Movidius

          Компания Movidius разрабатывает визуальные процессоры для интернета вещей. Штаб-квартира располагается в Сан-Матео, Калифорния. В сентябре 2016 года была приобретена компанией Intel.

          P.S. А вот еще несколько статей из нашего блога:

          Original source: habrahabr.ru (comments, light).

          https://habrahabr.ru/post/337570/


          Метки:  

          [Перевод] Spring: ваш следующий Java микрофреймворк

          Воскресенье, 10 Сентября 2017 г. 21:06 + в цитатник
          alek_sys сегодня в 21:06 Разработка

          Spring: ваш следующий Java микрофреймворк

          • Перевод

          В этой статье мы поговорим о новой концепции в готовящемся к выходу Spring Framework 5 которая называется функциональный веб-фреймворк и посмотрим, как она может помочь при разработке легковесных приложений и микросервисов.


          Вы, возможно, удивлены видеть Spring и микрофреймворк в одном предложении. Но все верно, Spring вполне может стать вашим следующим Java микрофреймворком. Чтобы избежать недоразумений, давайте определим, что им имеем в виду под микро:


          • Лаконичный — минимум бойлерплейта, минимум настройки
          • Простой — без магии
          • Простой в деплойменте — один артефакт для деплоймента
          • Простой в запуске — без дополнительных зависимостей
          • Легковесный — минимальное использование памяти / CPU
          • Неблокирующий — для написания конкуррентных неблокирующих приложений

          Несмотря на то, что некоторые из этих пунктов актуальны при использовании Spring Boot, он сам по себе добавляет дополнительную магию поверх самого Spring Framework. Даже такие базовые аннотации, как @Controller не совсем прямолинейны, что уж говорить про авто-конфигурации и сканирование компонентов. В общем-то, для крупномасштабных приложений, просто незаменимо то, что Spring берет на себя заботу о DI, роутинге, конфигурации и т.п. Однако, в мире микросервисов, где приложения это просто шестеренки в одной больной машине, вся мощь Spring Boot может быть немного лишней.


          Для решения этой проблемы, команда Spring представила новую фичу, которая называется функциональный веб-фреймворк — и именно о ней мы и будем говорить. В целом, это часть большего под-проекта Spring WebFlux, который раньше назывался Spring Reactive Web.


          Для начала, давайте вернемся к основам и посмотрим, что такое веб-приложение и какие компоненты мы ожидаем иметь в нем. Несомненно, есть базовая вещь — веб-сервер. Чтобы избежать ручной обработки запросов и вызова методов приложения, нам пригодится роутер. И, наконец, нам нужен обработчик — кусок кода, который принимает запрос и отдает ответ. По сути, это все, что нужно! И именно эти компоненты предоставляет функциональный веб-фреймворк Spring, убирая всю магию и фокусируясь на фундаментальном минимуме. Отмечу, что это вовсе не значит, что Spring резко меняет направление и уходит от Spring MVC, функциональный веб просто дает еще одну возможность создавать приложения на Spring.


          Обработчик


          Давайте рассмотрим пример. Для начала, пойдем на Spring Initializr и создадим новый проект используя Spring Boot 2.0 и Reactive Web как единственную зависимость. Теперь мы можем написать наш первый обработчик — функцию которая принимает запрос и отдает ответ.


          HandlerFunction hello = new HandlerFunction() {
              @Override
              public Mono handle(ServerRequest request) {
                  return ServerResponse.ok().body(fromObject("Hello"));
              }
          };

          Итак, наш обработчик это просто реализация интерфейса HandlerFunction который принимает параметр request (типа ServerRequest) и возвращает объект типа ServerResponse с текстом "Hello". Spring так же предоставляет удобные билдеры чтобы создать ответ от сервера. В нашем случае, мы используем ok() которые автоматически возвращают HTTP код ответа 200. Чтобы вернуть ответ, нам потребуется еще один хелпер — fromObject, чтобы сформировать ответ из предоставленного объекта.


          Мы так же можем сделать код немного более лаконичным и использовать лямбды из Java 8 и т.к. HandlerFunction это интерфейс одного метода (single abstract method interface, SAM), мы можем записать нашу функцию как:


          HandlerFunction hello = request -> ServerResponse.ok().body(fromObject("Hello"));

          Роутер


          Теперь, когда у нас есть хендлер, пора определить роутер. Например, мы хотим вызвать наш обработчик когда URL "/" был вызван с помощью HTTP метода GET. Чтобы этого добиться, определим объект типа RouterFunction который мапит функцию-обработчик, на маршрут:


          RouterFunction router = route(GET("/"), hello);

          route и GET это статические методы из классов RequestPredicates и RouterFunctions, они позволяют создать так называемую RouterFunction. Такая функция принимает запрос, проверяет, соответствует ли он все предикатам (URL, метод, content type, etc) и вызывает нужную функцию-обработчик. В данном случае, предикат это http метод GET и URL '/', а функция обработчик это hello, которая определена выше.


          Веб-сервер


          А сейчас пришло время собрать все вместе в единое приложение. Мы используем легковесный и простой сервер Reactive Netty. Чтобы интегрировать наш роутер с веб-сервером, необходимо превратить его в HttpHandler. После этого можно запустить сервер:


          HttpServer
              .create("localhost", 8080)
              .newHandler(new ReactorHttpHandlerAdapter(httpHandler))
              .block();

          ReactorHttpHandlerAdapter это класс предоставленный Netty, который принимает HttpHandler, остальной код, думаю, не требует пояснений. Мы создаем новые веб-сервер привязанный к хосту localhost и на порту 8080 и предоставляем httpHandler созданный из нашего роутера.


          И это все, приложение готово! И его полный код:


          public static void main(String[] args)
                    throws IOException, LifecycleException, InterruptedException {
          
              HandlerFunction hello = request -> ServerResponse.ok().body(fromObject("Hello"));
          
              RouterFunction router = route(GET("/"), hello);
          
              HttpHandler httpHandler = RouterFunctions.toHttpHandler(router);
          
              HttpServer
                      .create("localhost", 8080)
                      .newHandler(new ReactorHttpHandlerAdapter(httpHandler))
                      .block();
          
              Thread.currentThread().join();
          }

          Последняя строчка нужна только чтобы держать JVM процесс живым, т.к. сам HttpServer его не блокирует. Вы возможно сразу обратите внимание, что приложение стартует мгновенно — там нет ни сканирования компонентов, ни авто-конфигурации.


          Мы так же может запустить это приложение как обычное Java приложение, не требуется никаких контейнеров приложений и прочего.


          Чтобы запаковать приложение для деплоймента, мы можем воспользоваться преимуществами Maven плагина Spring и просто вызвать


          ./mvnw package


          Эта команда создаст так называемый fat JAR со всеми зависимостями, включенными в JAR. Это файл может быть задеплоен и запущен не имея ничего, кроме установленной JRE


          java -jar target/functional-web-0.0.1-SNAPSHOT.jar


          Так же, если мы проверим использование памяти приложением, то увидим, что оно держится примерно в районе 32 Мб — 22 Мб использовано на metaspace (классы) и около 10 Мб занято непосредственно в куче. Разумеется, наше приложение ничего и не делает — но тем не менее, это просто показатель, что фреймворк и рантайм сами по себе требуют минимум системных ресурсов.


          Поддержка JSON


          В нашем примере, мы возвращали строку, но вернуть JSON ответ так же просто. Давайте расширим наше приложение новым endpoint-ом, который вернет JSON. Наша модель будет очень простой — всего одно строковое поле под названием name. Чтобы избежать ненужного boilerplate кода, мы воспользуемся фичей из проекта Lombok, аннотацией @Data. Наличие этой аннотации автоматически создаст геттеры, сеттеры, методы equals и hashCode, так что нам не придется релизовывать их вручную.


          @Data
          class Hello {
              private final String name;
          }

          Теперь, нам нужно расширить наш роутер чтобы вернуть JSON ответ при обращении к URL /json. Это можно сделать вызвав andRoute(...) метод на существующем роуте. Также, давайте вынесем код роутер в отдельную функцию, чтобы отделить его от кода приложения и позволить использовать позже в тестах.


          static RouterFunction getRouter() {
              HandlerFunction hello = request -> ok().body(fromObject("Hello"));
          
              return
                  route(
                      GET("/"), hello)
                  .andRoute(
                      GET("/json"), req ->
                          ok()
                          .contentType(APPLICATION_JSON)
                          .body(fromObject(new Hello("world")));
          }

          После перезапуска, приложение вернет { "name": "world" } при обращении к URL /json при запросе контента с типом application/json.


          Контекст приложения


          Вы, возможно, заметили, что мы не определили контекст приложения — он нам просто не нужен! Несмотря на то, что мы можем объявить RouterFunction как бин (bean) в контексте Spring WebFlux приложения, и он точно так же будет обрабатывать запросы на определенные URL, роутер можно запустить просто поверх Netty Server чтобы создавать простые и легковесные JSON сервисы.


          Тестирование


          Для тестирования реактивных приложений, Spring предоставляет новый клиент под названием WebTestClient (подобно MockMvc). Его можно создать для существующего контекста приложения, но так же можно определить его и для RouterFunction.


          public class FunctionalWebApplicationTests {
          
              private final WebTestClient webTestClient =
                      WebTestClient
                          .bindToRouterFunction(
                              FunctionalWebApplication.getRouter())
                          .build();
          
              @Test
              public void indexPage_WhenRequested_SaysHello() {
                  webTestClient.get().uri("/").exchange()
                          .expectStatus().is2xxSuccessful()
                          .expectBody(String.class)
                          .isEqualTo("Hello");
              }
          
              @Test
              public void jsonPage_WhenRequested_SaysHello() {
                  webTestClient.get().uri("/json").exchange()
                          .expectStatus().is2xxSuccessful()
                          .expectHeader().contentType(APPLICATION_JSON)
                          .expectBody(Hello.class)
                          .isEqualTo(new Hello("world"));
              }
          }

          WebTestClient включает ряд assert-ов, которые можно применить к полученному ответу, чтобы провалидировать HTTP код, содержимое ответа, тип ответа и т.п.


          В заключение


          Spring 5 представляет новую парадигму для разработки маленьких и легковесных microservice-style веб-приложений. Такие приложения могут работать без контекста приложений, автоконфигурации и в целом использовать подход микрофреймворков, когда роутер и функции-обработчики и веб-сервер опеределены явно в теле приложения.


          Код


          Доступен на GitHub


          Ссылки



          От переводчика


          Я так же являюсь и автором оригинальной статьи, так что вопросы можно задавать в комментариях.

          Original source: habrahabr.ru (comments, light).

          https://habrahabr.ru/post/337604/


          Метки:  

          [Из песочницы] Stateless аутентификация при помощи Spring Security и JWT

          Воскресенье, 10 Сентября 2017 г. 20:07 + в цитатник
          AnarSultanov сегодня в 20:07 Разработка

          Stateless аутентификация при помощи Spring Security и JWT

          Недавно передо мной встала задача отказаться от statefull аутентификации с помощью сессий, в пользу stateless аутентификации и JWT. Так как это было мое первое знакомство с JSON Web Token, в первую очередь я начал искать полезную информацию на просторах интернета, но чем больше информации я находил, тем больше вопросов у меня появлялось.

          Я не буду рассказывать, что это за «волшебные» токены, как они работают и зачем они нужны. Я хочу сосредоточиться на вопросах, которые встают перед многими, но не имеют однозначного правильного ответа. Мое решение не претендует на лучшее и эта статья не является пошаговым руководством, я просто хочу поделиться своим опытом и постараться обьяснить, почему я сделал именно так и никак иначе.

          Для работы с токенами я выбрал библиотеку JJWT, хотя в принципе все реализации библиотек довольно похожи по своей функциональности. Также было решено не создавать сервер авторизации, а выдавать/обновлять/проверять токены непосредственно из приложения. Кстати немного о приложении, это одностраничное приложение (AngularJS) с RESTful Web-сервисом (Spring) в back-end.

          Выдавать токены на долгий срок не хотелось, так как в случае с JWT права пользователя хранятся в токене, и изменения в правах пользователя не подействуют до повторной авторизации и получения нового токена. Можно конечно хранить в токене только идентификатор пользователя, а остальные данные читать из базы данных при каждом запросе, но это повлечет слишком большое количество лишних запросов к базе данных, поэтому от этого варианта я сразу отказался. Нужно было задуматься об обновлении короткоживущих токенов.

          Для обновления access токена можно использовать refresh токен с более длительным временем до истечения, который получает новые данные пользователя по его идентификатору из базы данных и создает новый access токен на их основе. Но как быть с refresh токеном, если он также выдан на не очень долгое время и истечет во время сеанса пользователя? Повторный запрос авторизационных данных во время сеанса не очень хорошее решение в плане UX, а обновление истекшего токена плохо скажется на безопастности. Нужно было найти другое решение, но к этому вопросу я вернусь немного позже. Для начала нужно определиться с временем «жизни» для токенов и я остановился на 10 минутах для access токена и 60 минутах, с возможностью продления в перспективе, для refresh токена.

          С хранением и обновлением токенов на стороне пользователя, тоже все неоднозначно. Обычно для их хранения используeтся локальное хранилище или cookies. У каждого из этих вариантов есть свои плюсы и минусы, каждый из них подвержен разного вида уязвимостям, но мой выбор остановился на втором варианте.

          В случае с локальным хранилищем, обычно способом передачи токенов между back-end и front-end является добавление их в header или в тело запроса/ответа. То есть, мне пришлось бы вносить изминения и во front-end, для получения токенов, сохранения их в локальное хранилище, добавления к запросам, обновления и т.д., тогда как в случае с cookies их можно добавлять/изменять/удалять непосредственно из back-end.

          Думаю, самое время перейти от теории к практике и показать, как это все реализовано у меня. Не хочу загружать статью лишним кодом, выкладывая все классы полностью, поэтому постараюсь выделить только самое важное. Если вас заинтересуют какие-либо подробности, которые остались «за кадром», спрашивайте, и я с радостью отвечу вам в комментариях или дополню статью.

          Естественно, взаимодействие с приложением начинается с авторизации. Пользователь авторизуется с помощью метода в @RestController, в котором вызывается метод из TokenAuthenticationService для создания токенов и добавления их в ответ сервера в виде cookies (access_token и refresh_token).

              @RequestMapping(value = "/login", produces = "application/json", method = RequestMethod.GET)
              @ResponseStatus(value = HttpStatus.NO_CONTENT)
              public void login(HttpServletResponse response) {
                  SessionUser user = (SessionUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
                  tokenAuthenticationService.addAuthentication(response, user);
                  SecurityContextHolder.getContext().setAuthentication(null);
              }

          SessionUser — реализация UserDetails*

          Так выглядит этот метод в TokenAuthenticationService:

              public void addAuthentication(HttpServletResponse response, SessionUser user) {
                  Cookie access = new Cookie("access_token", tokenHandler.createAccessToken(user));
                  access.setPath("/");
                  access.setHttpOnly(true);
                  response.addCookie(access);
          
                  Cookie refresh = new Cookie("refresh_token", tokenHandler.createRefreshToken(user));
                  refresh.setPath("/");
                  refresh.setHttpOnly(true);
                  response.addCookie(refresh);
              }

          TokenHandler содержит стандартные методы для работы c JWT с использованием Jwts.builder() и Jwts.parser() из указанной выше библиотеки, поэтому не вижу смысла занимать место их кодом, но для ясности напишу что делает каждый из них:

          public String createRefreshToken(SessionUser user) {
              //возвращает токен, в котором хранится только username
          }
          public SessionUser parseRefreshToken(String token) {
              //парсит username из токена и получает данные пользователя из реализации UserDetailsService
          }
          public String createAccessToken(SessionUser user) {
              //возвращает токен, в котором хранятся все данные для воссоздания SessionUser
          }
          public SessionUser parseAccessToken(String token) {
              //использует данные из токена для создания нового SessionUser
          }

          UserDetailsService*

          Теперь немного о том, как происходит обработка последующих запросов. В конфигурационном файле, наследованном от WebSecurityConfigurerAdapter, я добавил два известных вам bean-a:

              @Bean
              public TokenAuthenticationService tokenAuthenticationService() {
                  return new TokenAuthenticationService();
              }
          
              @Bean
              public TokenHandler tokenHandler() {
                  return new TokenHandler();
              }

          Запретил создание/использование сессий и добавил фильтр для аутентификации с помощью токенов:

          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http.authorizeRequests()
                  //Ресурсы доступные анонимным пользователям
                  .antMatchers("/", "/login").permitAll()
                  //Все остальные доступны только после аутентификации
                  .anyRequest().authenticated()
                  .and()
                  .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                  .and()
                  .addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService()), UsernamePasswordAuthenticationFilter.class)             
          }

          При каждом запросе фильтр получает аутентификацию из токенов, обрабатывает запрос и обнуляет аутентификацию:

          public class StatelessAuthenticationFilter extends GenericFilterBean {
          
              private final TokenAuthenticationService tokenAuthenticationService;
          
              public StatelessAuthenticationFilter(TokenAuthenticationService tokenAuthenticationService) {
                  this.tokenAuthenticationService= tokenAuthenticationService;
              }
          
              @Override
              public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
                      throws IOException, ServletException {
                  HttpServletRequest httpRequest = (HttpServletRequest) request;
                  HttpServletResponse httpResponse = (HttpServletResponse) response;
                  Authentication authentication = tokenAuthenticationService.getAuthentication(httpRequest, httpResponse);
                  SecurityContextHolder.getContext().setAuthentication(authentication);
                  filterChain.doFilter(request, response);
                  SecurityContextHolder.getContext().setAuthentication(null);
              }
          }

          Как происходит создание аутентификации:
          — Присутствует access токен?
          — Нет? Отклонить запрос.
          — Да?
          — — Действительный и не истек?
          — — Да? Разрешить запрос.
          — — Нет? Попробовать получить новый access токен при помощи refresh токена.
          — — — Получилось?
          — — — Да? Разрешить запрос, и добавить новый access токен к ответу.
          — — — Нет? Отклонить запрос.
          За это отвечает еще один метод из уже известного вам TokenAuthenticationService:

          public Authentication getAuthentication(HttpServletRequest request, HttpServletResponse response) {
                  Cookie[] cookies = request.getCookies();
          
                  String accessToken = null;
                  String refreshToken = null;
                  if (cookies != null) {
                      for (Cookie cookie : cookies) {
                          if (("access_token").equals(cookie.getName())) {
                              accessToken = cookie.getValue();
                          }
                          if (("refresh_token").equals(cookie.getName())) {
                              refreshToken = cookie.getValue();
                          }
                      }
                  }
          
                  if (accessToken != null && !accessToken.isEmpty()) {
                      try {
                          SessionUser user = tokenHandler.parseAccessToken(accessToken);
                          return new UserAuthentication(user);
                      } catch (ExpiredJwtException ex) {
                          if (refreshToken != null && !refreshToken.isEmpty()) {
                              try {
                                  SessionUser user = tokenHandler.parseRefreshToken(refreshToken);
                                  Cookie access = new Cookie("access_token", tokenHandler.createAccessToken(user));
                                  access.setPath("/");
                                  access.setHttpOnly(true);
                                  response.addCookie(access);
                                  return new UserAuthentication(user);
                              } catch (JwtException e) {
                                  return null;
                              }
                          }
                          return null;
                      } catch (JwtException ex) {
                          return null;
                      }
                  }
                  return null;
              } 

          UserAuthentication — реализация Authentication*

          Вот и все, что требуется для совместной работы Spring Security и JWT.

          Но если вы не забыли, я собирался придумать что-то для продления refresh токенов. Как я уже говорил, я не хотел выдавать токены на долгий срок и в любом случае, это не спасло бы от истечения токена во время сеанса пользователя, будь это через час, день или месяц.

          Первое, что пришло в голову, убрать из токена время истечения, а так как в моем случае cookies хранятся только до закрытия браузера, то токен бы просто удалялся и больше не использовался. Но здесь были свои «подводные камни», во первых в случае кражи токен мог бы использоваться неограниченное количество времени и не было бы возможности его отозвать, во вторых я хотел продлевать токен только для активного пользователя, а если пользователь просто оставит окно браузера открытым, то токен должен был истечь спустя некоторое время.

          Чтобы обезопасить токен от кражи, нужно было реализовать возможность отзыва токена, поэтому я решил хранить все refresh токены в базе данных и перед выдачей новых access токенов, проверять наличие refresh токена в ней. Тогда в случае отзыва токена, достаточно удалить его из базы данных. Но это не решало моей проблемы с продлением токенов. И тогда я подумал, а что если хранить время до истечения токена не в самом токене, а в базе данных? И отодвигать это время на 60 минут вперед от каждого использования токена. Мне это показалось довольно не плохой идеей и я хотел бы поделиться с вами ее реализацией.

          Я создал таблицу (refresh_token) в базе данных для хранения refresh токенов со следующими столбцами:
          1. id (BIGINT)
          2. username (VARCHAR)
          3. token (VARCHAR)
          4. expires (TIMESTAMP)
          И создал класс RefreshTokenDao с двумя методами использующими JdbcTemplate для общения с этой таблицей:

              public void insert(String username, String token, long expires) {
                  String sql = "INSERT INTO refresh_token "
                          + "(username, token, expires) VALUES (?, ?, ?)";
                  jdbcTemplate.update(sql, username, token, new Timestamp(expires));
              }
          
              public int updateIfNotExpired(String username, String token, long expiration) {
                  String sql = "UPDATE refresh_token "
                          + "SET expires = ? "
                          + "WHERE username = ? "
                          + "AND token = ? "
                          + "AND expires > ?";
                  Timestamp now = new Timestamp(System.currentTimeMillis());
                  Timestamp newExpirationTime = new Timestamp(now.getTime() + expiration);
                  return jdbcTemplate.update(sql, newExpirationTime, username, token, now);
              }

          Методы довольно простые. Первый используется для добавления токена в базу данных при его создании. Второй для обновления токена, если он не истек и возвращения количества обновленных полей, в моем случае это количество всегда будет 0 или 1.

          Используются они в классе TokenHandler. При создании токена (createRefreshToken()) я добавляю запись о нем с помощью метода insert(). А в случае когда мне нужно получить информацию из токена (parseRefreshToken()), я сначала пытаюсь вызвать updateIfNotExpired() и если в ответе получаю значение не равное 0, значит токен валиден и его дата обновилась, можно продолжать выполнение метода; а в случае, если возвращенное значение равно 0, из чего следует что токен не найден или истек, я просто выбрасываю исключение (new JwtException(«Token is expired or missing»)).

          Это все что касалось продления refresh токенов. На просторах интернета я нигде не встречал такого способа (может плохо искал) и надеюсь что он будет кому-то полезен, как и вся статья в целом.

          Спасибо за внимание!
          Original source: habrahabr.ru (comments, light).

          https://habrahabr.ru/post/337600/


          Метки:  

          [Перевод] ggplot2: как легко совместить несколько графиков в одном, часть 2

          Воскресенье, 10 Сентября 2017 г. 19:44 + в цитатник
          qc-enior сегодня в 19:44 Разработка

          ggplot2: как легко совместить несколько графиков в одном, часть 2

          • Перевод
          • Tutorial
          Эта статья шаг за шагом покажет, как совместить несколько ggplot-графиков на одной или нескольких иллюстрациях, с помощью вспомогательных функций, доступных в пакетах R ggpubr, cowplot и gridExtra. Также опишем, как экспортировать полученные графики в файл.

          Изменение расположения по строкам или столбцам


          Используем пакет ggpubr


          Воспользуемся вложенными функциями ggarrange() для изменения расположения графиков по строкам или столбцам.

          Например, код ниже делает следующее:

          • диаграмма разброса (sp) будет находиться в первой строке и занимать две колонки
          • диаграмма рассеивания (bxp) и точечная диаграмма (dp) будут занимать вторую строку и две разных колонки

          ggarrange(sp,  # Первая строка с диаграммой разброса
                    ggarrange(bxp, dp, ncol = 2, labels = c("B", "C")), # Вторая строка с диаграммой рассеивания и точечной диаграммой
                    nrow = 2, 
                    labels = "A"   # Метки диаграммы разброса
                    ) 



          Используем пакет cowplot


          Следующую комбинацию функций [из пакета cowplot] можно использовать, чтобы расположить графики в определенных местах заданного размера: ggdraw() + draw_plot() + draw_plot_label().

          ggdraw(). Инициализируем пустое полотно:


          ggdraw()

          Обратите внимание, что по умолчанию координаты изменяются от 0 до 1, и точка (0, 0) находится в нижнем левом углу полотна (см. иллюстрацию ниже).


          draw_plot(). Располагает график где-то на полотне:


          draw_plot(plot, x = 0, y = 0, width = 1, height = 1)

          • plot: график для размещения (ggplot2 или gtable)
          • x, y: координаты x/y левого нижнего угла графика
          • width, height: ширина и высота графика

          draw_plot_label(). Добавляет метку в правом верхнем углу графика


          Может работать с векторами меток с ассоциированными координатами.

          draw_plot_label(label, x = 0, y = 1, size = 16, ...)

          • label: вектор меток
          • x, y: вектор с х/у координатами каждой метки соответственно
          • size: размер шрифта метки

          Например, так можно комбинировать несколько графиков разного размера с определенным расположением:

          library("cowplot")
          ggdraw() +
            draw_plot(bxp, x = 0, y = .5, width = .5, height = .5) +
            draw_plot(dp, x = .5, y = .5, width = .5, height = .5) +
            draw_plot(bp, x = 0, y = 0, width = 1, height = 0.5) +
            draw_plot_label(label = c("A", "B", "C"), size = 15,
                            x = c(0, 0.5, 0), y = c(1, 1, 0.5))



          Используем пакет gridExtra


          Функция arrangeGrop()gridExtra] помогает изменить расположение графиков по строкам или столбцам.

          Например, код ниже делает следующее:

          • диаграмма разброса (sp) будет находиться в первой строке и занимать две колонки
          • диаграмма рассеивания (bxp) и точечная диаграмма (dp) будут занимать вторую строку и две разных колонки

          library("gridExtra")
          grid.arrange(sp,                            # Первая строка с одним графиком на две колонки
                       arrangeGrob(bxp, dp, ncol = 2),# Вторая строка с двумя графиками в двух колонках
                       nrow = 2)                      # Количество строк



          В функции grid.arrange() можно также использовать аргумент layout_matrix для создания сложного взаимного расположения графиков.

          В коде ниже layout_matrix — матрица 2х2 (2 строки и 2 столбца). Первая строка — все единицы, там, где первый график, занимающий две колонки; вторая строка содержит графики 2 и 3, каждый из которых занимает свою колонку.

          grid.arrange(bp,                                # столбчатая диаграмма на две колонки
                       bxp, sp,                               # диаграммы рассеивания и разброса
                       ncol = 2, nrow = 2, 
                       layout_matrix = rbind(c(1,1), c(2,3)))



          Также можно добавить аннотацию к выводу функции grid.arrange(), используя вспомогательную функцию draw_plot_label()cowplot].

          Для того, чтобы легко добавлять аннотации к выводам функций grid.arrange() или arrangeGrob() (тип gtable), сначала нужно преобразовать их в тип ggplot с помощью функции as_ggplot()ggpubr]. После можно применять к ним фунцию draw_plot_label()cowplot].

          library("gridExtra")
          library("cowplot")
          # Упорядочиваем графики с arrangeGrob
          # возвращает тип gtable (gt)
          gt <- arrangeGrob(bp,                               # столбчатая диаграмма на две колонки
                       bxp, sp,                               # диаграммы рассеивания и разброса
                       ncol = 2, nrow = 2, 
                       layout_matrix = rbind(c(1,1), c(2,3)))
          # Добавляем метки к упорядоченным графикам
          p <- as_ggplot(gt) +                                # преобразуем в ggplot
            draw_plot_label(label = c("A", "B", "C"), size = 15,
                            x = c(0, 0, 0.5), y = c(1, 0.5, 0.5)) # Добавляем метки
          p



          В коде выше мы использовали arrangeGrob() вместо grid.arrange(). Основное отличие этих двух функций состоит в том, что grid.arrange() автоматически выводит упорядоченные графики. Поскольку мы хотели добавить аннотацию к графикам до того, как их нарисовать, предпочтительно в таком случае использовать функцию arrangeGrob().

          Используем пакет grid


          Пакет grid позволяет задать сложное взаимное расположение графиков с помощью функции grid.layout(). Он также предоставляет вспомогательную функцию viewport()для задания региона, или области видимости. Функция print() применяется для размещения графиков в заданном регионе.

          Шаги можно описать так:

          1. Создать графики: p1, p2, p3, ….
          2. Перейти на новую страницу с помощью функции grid.newpage()
          3. Создать расположение 2X2 — количество столбцов = 2; количество строк = 2
          4. Задать область видимости: прямоугольная область на графическом устройстве
          5. Напечатать график в области видимости

          library(grid)
          # Перейти на новую страницу
          grid.newpage()
          # Создать расположение: nrow = 3, ncol = 2
          pushViewport(viewport(layout = grid.layout(nrow = 3, ncol = 2)))
          # Вспомогательная функция для задания области в расположении
          define_region <- function(row, col){
            viewport(layout.pos.row = row, layout.pos.col = col)
          } 
          # Упорядочить графики
          print(sp, vp = define_region(row = 1, col = 1:2))   # Расположить в двух колонках
          print(bxp, vp = define_region(row = 2, col = 1))
          print(dp, vp = define_region(row = 2, col = 2))
          print(bp + rremove("x.text"), vp = define_region(row = 3, col = 1:2))



          Использование общей легенды для совмещенных ggplot-графиков


          Чтобы задать общую легенду для нескольких упорядоченных графиков, можно использовать функцию ggarrange()ggpubr] с такими аргументами:

          • common.legend = TRUE: сделать общую легенду
          • legend: задать положение легенды. Разрешенное значение — одно из c(“top”, “bottom”, “left”, “right”)

          ggarrange(bxp, dp, labels = c("A", "B"),
                    common.legend = TRUE, legend = "bottom")



          Диаграмма разброса с графиками плотности безусловного распределения


          # Диаграмма разброса, цвет по группе ("Species")
          sp <- ggscatter(iris, x = "Sepal.Length", y = "Sepal.Width",
                          color = "Species", palette = "jco",
                          size = 3, alpha = 0.6)+
            border()                                         
          # График плотности безусловного распределения по x (панель сверху) и по y (панель справа)
          xplot <- ggdensity(iris, "Sepal.Length", fill = "Species",
                             palette = "jco")
          yplot <- ggdensity(iris, "Sepal.Width", fill = "Species", 
                             palette = "jco")+
            rotate()
          # Почистить графики
          yplot <- yplot + clean_theme() 
          xplot <- xplot + clean_theme()
          # Упорядочить графики
          ggarrange(xplot, NULL, sp, yplot, 
                    ncol = 2, nrow = 2,  align = "hv", 
                    widths = c(2, 1), heights = c(1, 2),
                    common.legend = TRUE)



          В следующей части:

          • смешиваем таблицу, текст и ggplot2-графики
          • добавляем графический элемент в ggplot (таблицу, диаграмму рассеивания, фоновое изображение)
          • располагаем графики на нескольких страницах
          • вложенное взаиморасположение
          • экспорт графиков
          Original source: habrahabr.ru (comments, light).

          https://habrahabr.ru/post/337598/


          Метки:  

          Открытые проблемы в области распознавания речи. Лекция в Яндексе

          Воскресенье, 10 Сентября 2017 г. 18:57 + в цитатник
          Leono сегодня в 18:57 Разработка

          Открытые проблемы в области распознавания речи. Лекция в Яндексе

            Работа большинства специалистов по речевым технологиям состоит не в том, чтобы придумывать концептуально новые алгоритмы. Компании в основном фокусируются на существующих подходах. Машинный интеллект уже умеет распознавать и синтезировать голос, но не всегда в реальном времени, не всегда локально и не всегда «избирательно» — когда нужно реагировать только на ключевые фразы, робот может ошибаться. Подобными проблемами как раз и заняты разработчики. Муаммар Аль-Шедиват @Laytlas рассказывает об этих и других вопросах, которые пока не удаётся решить даже большим компаниям.




            — Сегодня я расскажу об открытых проблемах в области речевых технологий. Но прежде всего давайте поймем, что речевые технологии стали неотъемлемой частью нашей жизни. Идем ли мы по улице или едем в автомобиле — когда хочется нам задать тот или иной запрос в поисковую систему, естественно сделать это голосом, а не печатать или еще что-либо.

            Сегодня я поговорю в основном о распознавании речи, хотя есть множество других интересных задач. Рассказ мой будет состоять из трех частей. Для начала напомню в целом, как работает распознавание речи. Дальше расскажу, как люди стараются его улучшить и о том, какие в Яндексе стоят задачи, с которыми обычно не сталкиваются в научных статьях.

            Общая схема распознавания речи. Изначально на вход нам поступает звуковая волна.

            Ее мы дробим на маленькие кусочки, фреймы. Длина фрейма — обычно 25 мс, шаг — 10 мс. Они идут с некоторым захлестом.

            После этого из фреймов мы извлекаем наиболее важные признаки. Допустим, нам не важен тембр голоса или пол человека. Мы хотим распознавать речь вне зависимости от этих факторов, так что мы извлекаем самые важные признаки.

            Затем нейронная сеть натравливается на все это и выдает на каждом фрейме предсказание, распределение вероятностей по фонемам. Нейронка старается угадать, какая именно фонема была сказана на том или ином фрейме.

            Под конец все это запихивается в граф-декодирование, которое получает распределение вероятностей и учитывает языковую модель. Допустим, «Мама мыла раму» — более популярная фраза в русском языке, чем «Мама мыла Рому». Также учитывается произношение слов и выдаются итоговые гипотезы.

            В целом, именно так и происходит распознавание речи.

            Естественно, о метрике нужно пару слов сказать. Все используют метрику WER в распознавании речи. Она переводится как World Error Rate. Это просто расстояние по Левенштейну от того, что мы распознали, до того, что реально было сказано в фразе, поделить на количество слов, реально сказанных во фразе.

            Можно заметить, что если у нас было много вставок, то ошибка WER может получиться больше единицы. Но никто на это не обращает внимания, и все работают с такой метрикой.



            Как мы будем это улучшать? Я выделил четыре основных подхода, которые пересекаются друг с другом, но на это не стоит обращать внимания. Основные подходы следующие: улучшим архитектуру нейронных сетей, попробуем изменить Loss-функцию, почему бы не использовать подходы End to end, модные в последнее время. И в заключение расскажу про другие задачи, для которых, допустим, не нужно декодирование.

            Когда люди придумали использовать нейронные сети, естественным решением было использовать самое простое: нейронные сетки feed forward. Берем фрейм, контекст, сколько-то фреймов слева, сколько-то справа, и предсказываем, какая фонема была сказана на данном фрейме. После чего можно посмотреть на все это как на картинку и применить всю артиллерию, уже использованную для обработки изображений, всевозможные сверточные нейронные сети.

            Вообще многие статьи state of the art получены именно с помощью сверточных нейронных сетей, но сегодня я расскажу больше о рекуррентных нейронных сетях.

            Рекуррентные нейронные сети. Все знают, как они работают. Но возникает большая проблема: обычно фреймов намного больше, чем фонем. На одну фонему приходится 10, а то и 20 фреймов. С этим нужно как-то бороться. Обычно это зашивается в граф-декодирование, где мы остаемся в одном состоянии много шагов. В принципе, с этим можно как-то бороться, есть парадигма encoder-decoder. Давайте сделаем две рекуррентных нейронных сетки: одна будет кодировать всю информацию и выдавать скрытое состояние, а декодер будет брать это состояние и выдавать последовательность фонем, букв или, может быть, слов — это как вы натренируете нейронную сеть.

            Обычно в распознавании речи мы работаем с очень большими последовательностями. Там спокойно бывает 1000 фреймов, которые нужно закодировать одним скрытым состоянием. Это нереально, ни одна нейронная сеть с этим не справится. Давайте использовать другие методы.

            Димой Богдановым, выпускником ШАД, был придуман метод Attention. Давайте encoder будет выдавать скрытые состояния, и мы их не будем выкидывать, а оставим только последнее. Возьмем взвешенную сумму на каждом шаге. Декодер будет брать взвешенную сумму скрытых состояний. Таким образом, мы будем сохранять контекст, то, на что мы в конкретном случае смотрим.

            Подход прекрасный, работает хорошо, на некоторых датасетах дает результаты state of the art, но есть один большой минус. Мы хотим распознавать речь в онлайне: человек сказал 10-секундную фразу, и мы сразу ему выдали результат. Но Attention требует знать фразу целиком, в этом его большая проблема. Человек скажет 10-секундную фразу, 10 секунд мы ее будем распознавать. За это время он удалит приложение и никогда больше не установит. Нужно с этим бороться. Совсем недавно с этим поборолись в одной из статей. Я назвал это online attention.

            Давайте поделим входную последовательность на блоки какой-то небольшой фиксированной длины, внутри каждого блока устроим Attention, затем будет декодер, который выдает на каждом блоке соответствующие символы, после чего в какой-то момент выдает символ end of block, перемещается к следующему блоку, поскольку мы тут исчерпали всю информацию.

            Тут можно серию лекций прочитать, я постараюсь просто сформулировать идею.

            Когда начали тренировать нейронные сети для распознавания речи, старались угадывать фонему. Для этого использовали обычную кросс-энтропийную функцию потерь. Проблема в том, что даже если мы соптимизируем кросс-энтропию, это еще не будет значить, что мы хорошо соптимизировали WER, потому что у этих метрик корреляция не 100%.

            Чтобы с этим побороться, были придуманы функции Sequence Based Loss: давайте саккумулируем всю информацию на всех фреймах, посчитаем один общий Loss и пропустим градиент обратно. Не буду вдаваться в детали, можете прочитать про CTC или SNBR Loss, это очень специфичная тема для распознавания речи.

            В подходах End to end два пути. Первый — делать более «сырые» фичи. У нас был момент, когда мы извлекали из фреймов фичи, и обычно они извлекаются, стараясь эмулировать ухо человека. А зачем эмулировать ухо человека? Пусть нейронка сама научится и поймет, какие фичи ей полезны, а какие бесполезны. Давайте в нейронку подавать все более сырые фичи.

            Второй подход. Мы пользователям выдаем слова, буквенное представление. Так зачем нам предсказывать фонемы? Хотя их предсказывать очень естественно, человек говорит в фонемах, а не буквах, — но итоговый результат мы должны выдать именно в буквах. Поэтому давайте предсказывать буквы, слоги или пары символов.

            Какие еще есть задачи? Допустим, задача фреймспоттинга. Есть какой-нибудь кусок звука, откуда надо извлечь информацию о том, была ли сказана фраза «Слушай, Яндекс» или не была. Для этого можно фразу распознать и грепнуть «Слушай, Яндекс», но это очень брутфорсный подход, причем распознавание обычно работает на серверах, модели очень большие. Обычно звук отсылается на сервер, распознается, и распознанная форма высылается обратно. Грузить 100 тыс. юзеров каждую секунду, слать звук на сервер — ни одни сервера не выдержат.

            Надо придумать решение, которое будет маленьким, сможет работать на телефоне и не будет жрать батарейку. И будет обладать хорошим качеством.

            Для этого давайте всё запихнем в нейронную сеть. Она просто будет предсказывать, к примеру, не фонемы и не буквы, а целые слова. И сделаем просто три класса. Сеть будет предсказывать слова «слушай» и «Яндекс», а все остальные слова замапим в филлер.

            Таким образом, если в какой-то момент сначала шли большие вероятности для «слушай», потом большие вероятности для «Яндекс», то с большой вероятностью тут была ключевая фраза «Слушай, Яндекс».

            Задача, которая не сильно исследуется в статьях. Обычно, когда пишутся статьи, берется какой-то датасет, на нем получаются хорошие результаты, бьется state of the art — ура, печатаем статью. Проблема этого подхода в том, что многие датасеты не меняются в течение 10, а то и 20 лет. И они не сталкиваются с проблемами, с которыми сталкиваемся мы.

            Иногда возникают тренды, хочется распознавать, и если этого слова нет в нашем графе декодирования в стандартном подходе, то мы никогда его не распознаем. Нужно с этим бороться. Мы можем взять и переварить граф декодирования, но это трудозатратный процесс. Может, утром одни трендовые слова, а вечером другие. Держать утренний и вечерний граф? Это очень странно.

            Был придуман простой подход: давайте к большому графу декодирования добавим маленький граф декодирования, который будет пересоздаваться каждые пять минут из тысячи самых лучших и трендовых фраз. Мы просто будем параллельно декодировать по этим двум графам и выбирать наилучшую гипотезу.

            Какие задачи остались? Там state of the art побили, тут задачи решили… Приведу график WER за последние несколько лет.

            Как видите, Яндекс улучшился за последние несколько лет, и тут приводится график для лучшей тематики — геопоиска. Вы можете понять, что мы стараемся и улучшаемся, но есть тот маленький разрыв, который нужно побить. И даже если мы сделаем распознавание речи — а мы его сделаем, — которое сравнится со способностями человека, то возникнет другая задача: это сделалось на сервере, но давайте перенесем это на устройство. Речь идет об отдельной, сложной и интересной задаче.

            У нас есть и множество других задач, о которых меня можно спросить. Спасибо за внимание.
            Original source: habrahabr.ru (comments, light).

            https://habrahabr.ru/post/337572/


            Java 8 и Android или Повесть о земле и небе

            Воскресенье, 10 Сентября 2017 г. 17:16 + в цитатник
            image

            Здравствуйте уважаемые читатели. Сегодня мы поговорим о такой теме как Java 8 и Android. О плюсах и минусах, и прочих интересных темах.

            Уже прошло не мало времени со времен выхода 8 Явы, а на андроиде она появилась не так уж давно. Однажды эта история повторялась с переходом на Java 7. И вот мы снова на том же месте. Читать дальше ->

            https://habrahabr.ru/post/337596/


            Метки:  

            [Из песочницы] Сбалансированное дерево поиска B-tree (t=2)

            Воскресенье, 10 Сентября 2017 г. 17:09 + в цитатник
            AnitaChess сегодня в 17:09 Разработка

            Сбалансированное дерево поиска B-tree (t=2)

            Введение и постановка задачи


            На 3-м курсе обучения в своем университете передо мной встала задача реализовать B-дерево (со степенью t=2) на языке c++ (с возможностью добавления, удаления, поиска элементов и соответственно, перестройкой дерева).

            Перечитав несколько статей на habrahabr.ru (например, B-tree, 2-3-дерево. Наивная реализация и другие), казалось бы, все было ясно. Только теоретически, а не практически. Но и с этими трудностями мне удалось справиться. Цель моего поста — поделиться полученным опытом с пользователями.

            Реализация


            Для начала создадим структуру узла нашего дерева.

            const int t=2;
            struct BNode {
                int keys[2*t];
                BNode *children[2*t+1];	
                BNode *parent;
                int count; //количество ключей
                int countSons; //количество сыновей
                bool leaf;
                bool is_leaf() {
                    if (this->children[0]==NULL) return true;
                    else return false;
                }
            };

            Теперь создаём класс Tree, включающий в себя соответствующие методы.

            class Tree {
                private:
                BNode *root;
                void insert_to_node(int key, BNode *node);
                void sort(BNode *node);
                void restruct(BNode *node);
                void print(BNode *node, ofstream& fout);
                void deletenode(BNode *node);
                bool searchKey(int key, BNode *node);	
                void remove(int key, BNode *node);
                void removeFromNode(int key, BNode *node);
                void removeLeaf(int key, BNode *node);
                void lconnect(BNode *node, BNode *othernode);
                void rconnect(BNode *node, BNode *othernode);
                void repair(BNode *node);
            
                public:
                Tree();
                ~Tree();
                void insert(int key);
                bool search(int key);
                void remove(int key);
                void print();
            };

            Сразу описываем конструктор и деструктор. В деструкторе вызывается рекурсивный метод удаления элементов из дерева.

            Tree::Tree() { root=NULL; }
            
            Tree::~Tree(){ if(root!=NULL) deletenode(root); }
            
            void Tree::deletenode(BNode *node){
                if (node!=NULL){
                    for (int i=0; i<=(2*t-1); i++){
                        if (node->children[i]!=NULL) deletenode(node->children[i]);	
                        else {
                            delete(node);
                            break;
                        }
                    }
                }
            }

            Для добавления элемента в дерево необходимо реализовать несколько методов.
            Первый – простое добавление в узел. В данном методе вызывается метод сортировки,
            необходимый для выполнения условия о возрастающих значениях дерева.Второй метод –
            добавления значения в узел, в котором предварительно ищется нужная позиция и при
            необходимости вызывается третий метод – метод разбиения узла на три: родителя и двух
            сыновей (используется при переполнении узла).

            Первый метод – метод простого добавления:

            void Tree::insert_to_node(int key, BNode *node){
                node->keys[node->count]=key;
                node->count=node->count+1;
                sort(node);
            }

            Метод сортировки чисел в узле:
            void Tree::sort(BNode *node) { 
                int m;
                for (int i=0; i<(2*t-1); i++){
                    for (int j=i+1; j<=(2*t-1); j++){
                        if ((node->keys[i]!=0) && (node->keys[j]!=0)){
                            if ((node->keys[i]) > (node->keys[j])){
                                m=node->keys[i];
                                node->keys[i]=node->keys[j];
                                node->keys[j]=m;
                            }
                        } else break;
                    }
                }
            }

            Второй метод – метод добавления значения в узел с предварительным поиском
            позиции:

            void Tree::insert(int key){
                if (root==NULL) {
                    BNode *newRoot = new BNode;
                    newRoot->keys[0]=key;
                    for(int j=1; j<=(2*t-1); j++) newRoot->keys[j]=0;
                    for (int i=0; i<=(2*t); i++) newRoot->children[i]=NULL;
                    newRoot->count=1;
                    newRoot->countSons=0;
                    newRoot->leaf=true;
                    newRoot->parent=NULL;
                    root=newRoot;
                } else {
                    BNode *ptr=root;
                    while (ptr->leaf==false){ //выбор ребенка до тех пор, пока узел не будет являться листом
                        for (int i=0; i<=(2*t-1); i++){ //перебираем все ключи
                            if (ptr->keys[i]!=0) {
                                if (keykeys[i]) { 
                                    ptr=ptr->children[i];
                                    break;
                                } 					
                                if ((ptr->keys[i+1]==0)&&(key>ptr->keys[i])) {
                                    ptr=ptr->children[i+1]; //перенаправляем к самому последнему ребенку, если цикл не "сломался"
                                    break;
                                } 
                            } else break;
                        }
                    }
                    insert_to_node(key, ptr);
            
                    while (ptr->count==2*t){
                        if (ptr==root){
                            restruct(ptr);
                            break;
                        } else {
                            restruct(ptr);
                            ptr=ptr->parent;
                        }
                    }
                }
            }

            Третий метод – метод разбиения узла:

            void Tree::restruct(BNode *node){
                if (node->count<(2*t-1)) return;
            	
                //первый сын
                BNode *child1 = new BNode;
                int j;
                for (j=0; j<=t-2; j++) child1->keys[j]=node->keys[j];
                for (j=t-1; j<=(2*t-1); j++) child1->keys[j]=0;
                child1->count=t-1; //количество ключей в узле
                if(node->countSons!=0){
                    for (int i=0; i<=(t-1); i++){
                        child1->children[i]=node->children[i];	
                        child1->children[i]->parent=child1;
                    } 
                    for (int i=t; i<=(2*t); i++) child1->children[i]=NULL;
                    child1->leaf=false;
                    child1->countSons=t-1; //количество сыновей
                } else {
                    child1->leaf=true;
                    child1->countSons=0;
                    for (int i=0; i<=(2*t); i++) child1->children[i]=NULL;
                }
            	
                //второй сын
                BNode *child2 = new BNode;
                for (int j=0; j<=(t-1); j++) child2->keys[j]=node->keys[j+t];
                for (j=t; j<=(2*t-1); j++) child2->keys[j]=0;
                child2->count=t; //количество ключей в узле
                if(node->countSons!=0) {
                    for (int i=0; i<=(t); i++){
                        child2->children[i]=node->children[i+t];
                        child2->children[i]->parent=child2;
                    }
                    for (int i=t+1; i<=(2*t); i++) child2->children[i]=NULL;
                    child2->leaf=false;
                    child2->countSons=t; //количество сыновей
                } else {
                    child2->leaf=true;
                    child2->countSons=0;
                    for (int i=0; i<=(2*t); i++) child2->children[i]=NULL;
                }
            	
                //родитель
                if (node->parent==NULL){ //если родителя нет, то это корень
                    node->keys[0]=node->keys[t-1];
                    for(int j=1; j<=(2*t-1); j++) node->keys[j]=0;
                    node->children[0]=child1;
                    node->children[1]=child2;
                    for(int i=2; i<=(2*t); i++) node->children[i]=NULL;
                    node->parent=NULL;
                    node->leaf=false;
                    node->count=1;
                    node->countSons=2;
                    child1->parent=node;
                    child2->parent=node;		
                } else {
                    insert_to_node(node->keys[t-1], node->parent);
                    for (int i=0; i<=(2*t); i++){
                        if (node->parent->children[i]==node) node->parent->children[i]=NULL;
                    }	
                    for (int i=0; i<=(2*t); i++){
                        if (node->parent->children[i]==NULL) {	
                            for (int j=(2*t); j>(i+1); j--) node->parent->children[j]=node->parent->children[j-1];
                                node->parent->children[i+1]=child2;
                                node->parent->children[i]=child1;
                                break;
                            }
                        }
                    child1->parent=node->parent;
                    child2->parent=node->parent;
                    node->parent->leaf=false;
                    delete node;	
                }
            }

            Для поиска были реализованы следующие методы, возвращающие true или false
            (второй метод рекурсивный):

            bool Tree::search(int key){
                return searchKey(key, this->root);
            }
            
            bool Tree::searchKey(int key, BNode *node){
                if (node!=NULL){
                    if (node->leaf==false){
                        int i;
                        for (i=0; i<=(2*t-1); i++){
                            if (node->keys[i]!=0) {		
                                if(key==node->keys[i]) return true;
                                if ((keykeys[i])){
                                    return searchKey(key, node->children[i]);
                                    break;
                                }
                            } else break;
                        }
                        return searchKey(key, node->children[i]);
                    } else {
                        for(int j=0; j<=(2*t-1); j++)
                            if (key==node->keys[j]) return true;
                        return false;
                    }
                } else return false;
            }

            Реализация метода для удаления ключа из узла была самой сложной. Ведь в
            некоторых случаях удаления нужно склеивать соседние узлы, а в некоторых брать
            значения из узлов «братьев».

            Первый метод представляет собой простой метод удаления ключа из узла:

            void Tree::removeFromNode(int key, BNode *node){
                int i;
                for (i=0; i<=(node->count-1); i++){
                    if (node->keys[i]==key){
                        if (i==0) {
                            node->keys[0]=node->keys[1];
                            node->keys[1]=node->keys[2];
                            node->keys[2]=0;
                            node->children[0]=node->children[1];
                            node->children[1]=node->children[2];
                            node->children[2]=node->children[3];
                            node->children[3]=NULL;
                            break;
                        }
                        if (i==1) {
                            node->keys[1]=node->keys[2];
                            node->keys[2]=0;
                            node->children[1]=node->children[2];
                            node->children[2]=node->children[3];
                            node->children[3]=NULL;
                            break;
                        }
                        if (i==2) {
                            node->keys[2]=0;
                            node->children[2]=node->children[3];
                            node->children[3]=NULL;
                            break;
                        }
                    }
                }
                node->count=node->count-1;
            }

            Второй и третий методы – методы соединения узлов:

            void Tree::lconnect(BNode *node, BNode *othernode){
                if (node==NULL) return;
                for (int i=0; i<=(othernode->count-1); i++){
                    node->keys[node->count]=othernode->keys[i];
                    node->children[node->count]=othernode->children[i];
                    node->count=node->count+1;
                }
                node->children[node->count]=othernode->children[othernode->count];
                for (int j=0; j<=node->count; j++){
                    if (node->children[j]==NULL) break;
                    node->children[j]->parent=node;
                }
                delete othernode;
            }
            
            void Tree::rconnect(BNode *node, BNode *othernode){
                if (node==NULL) return;
                for (int i=0; i<=(othernode->count-1); i++){
                    node->keys[node->count]=othernode->keys[i];
                    node->children[node->count+1]=othernode->children[i+1];
                    node->count=node->count+1;
                }
                for (int j=0; j<=node->count; j++){
                    if (node->children[j]==NULL) break;
                    node->children[j]->parent=node;
                }
                delete othernode;
            }

            Четвертый метод – метод «починки» узла:
            void Tree::repair(BNode *node){
                if ((node==root)&&(node->count==0)){
                    if (root->children[0]!=NULL){
                        root->children[0]->parent=NULL;
                        root=root->children[0];
                    } else {
                        delete root;	
                    }
                     return;		
                } 
                BNode *ptr=node;
                int k1;
                int k2;
                int positionSon;
                BNode *parent=ptr->parent;
                for (int j=0; j<=parent->count; j++){
                    if (parent->children[j]==ptr){
                        positionSon=j; //позиция узла по отношению к родителю
                        break;
                    }
                }
                //если ptr-первый ребенок (самый левый)
                if (positionSon==0){	
                    insert_to_node(parent->keys[positionSon], ptr);		
                    lconnect(ptr, parent->children[positionSon+1]);
                    parent->children[positionSon+1]=ptr;
                    parent->children[positionSon]=NULL;
                    removeFromNode(parent->keys[positionSon], parent);	
                    if(ptr->count==2*t){
                        while (ptr->count==2*t){
                            if (ptr==root){
                                restruct(ptr);
                                break;
                            } else {
                                restruct(ptr);
                                ptr=ptr->parent;
                            }
                        }
                    } else 		
                        if (parent->count<=(t-2)) repair(parent);	
                } else {
                    //если ptr-последний ребенок (самый правый)
                    if (positionSon==parent->count){					
                        insert_to_node(parent->keys[positionSon-1], parent->children[positionSon-1]);
                        lconnect(parent->children[positionSon-1], ptr);
                        parent->children[positionSon]=parent->children[positionSon-1];
                        parent->children[positionSon-1]=NULL;
                        removeFromNode(parent->keys[positionSon-1], parent);
                        BNode *temp=parent->children[positionSon];
                        if(ptr->count==2*t){
                            while (temp->count==2*t){
                                if (temp==root){
                                    restruct(temp);
                                    break;
                                } else {
                                    restruct(temp);
                                    temp=temp->parent;
                                }
                            }
                        } else 
                        if (parent->count<=(t-2)) repair(parent);	
                    } else { //если ptr имеет братьев справа и слева
                        insert_to_node(parent->keys[positionSon], ptr);		
                        lconnect(ptr, parent->children[positionSon+1]);
                        parent->children[positionSon+1]=ptr;
                        parent->children[positionSon]=NULL;
                        removeFromNode(parent->keys[positionSon], parent);	
                        if(ptr->count==2*t){
                            while (ptr->count==2*t){
                                if (ptr==root){
                                    restruct(ptr);
                                    break;
                                } else {
                                    restruct(ptr);
                                    ptr=ptr->parent;
                                }
                            }
                        } else 		
                        if (parent->count<=(t-2)) repair(parent);	
                    }
                }	
            }

            Пятый метод – метод удаления ключа из листа:

            void Tree::removeLeaf(int key, BNode *node){
                if ((node==root)&&(node->count==1)){
                    removeFromNode(key, node);
                    root->children[0]=NULL;
                    delete root;
                    root=NULL;
                    return;		
                } 
                if (node==root) {
                    removeFromNode(key, node);
                    return;
                }
                if (node->count>(t-1)) {
                    removeFromNode(key, node);
                    return;
                }
                BNode *ptr=node;
                int k1;
                int k2;
                int position;
                int positionSon;
                int i;
                for (int i=0; i<=node->count-1; i++){
                    if (key==node->keys[i]) {
                        position=i; //позиция ключа в узле
                        break;
                    }
                }
                BNode *parent=ptr->parent;
                for (int j=0; j<=parent->count; j++){
                    if (parent->children[j]==ptr){
                        positionSon=j; //позиция узла по отношению к родителю
                        break;
                    }
                }
                //если ptr-первый ребенок (самый левый)
                if (positionSon==0){
                    if (parent->children[positionSon+1]->count>(t-1)){ //если у правого брата больше, чем t-1 ключей
                        k1=parent->children[positionSon+1]->keys[0]; //k1 - минимальный ключ правого брата
                        k2=parent->keys[positionSon]; //k2 - ключ родителя, больше, чем удаляемый, и меньше, чем k1
                        insert_to_node(k2, ptr);
                        removeFromNode(key, ptr);
                        parent->keys[positionSon]=k1; //меняем местами k1 и k2
                        removeFromNode(k1, parent->children[positionSon+1]); //удаляем k1 из правого брата
                    } else { //если у правого единственного брата не больше t-1 ключей		
                        removeFromNode(key, ptr);	
                        if (ptr->count<=(t-2)) repair(ptr);		
                    }				
                } else {
                    //если ptr-последний ребенок (самый правый)
                    if (positionSon==parent->count){		
                        //если у левого брата больше, чем t-1 ключей
                        if (parent->children[positionSon-1]->count>(t-1)){ 
                            BNode *temp=parent->children[positionSon-1];
                            k1=temp->keys[temp->count-1]; //k1 - максимальный ключ левого брата
                            k2=parent->keys[positionSon-1]; //k2 - ключ родителя, меньше, чем удаляемый и больше, чем k1
                            insert_to_node(k2, ptr);
                            removeFromNode(key, ptr);
                            parent->keys[positionSon-1]=k1;
                            removeFromNode(k1, temp);
                        } else { //если у единственного левого брата не больше t-1 ключей
                            removeFromNode(key, ptr);
                            if (ptr->count<=(t-2)) repair(ptr);
                        }	
                    } else { //если ptr имеет братьев справа и слева
                        //если у правого брата больше, чем t-1 ключей
                        if (parent->children[positionSon+1]->count>(t-1)){ 
                            k1=parent->children[positionSon+1]->keys[0]; //k1 - минимальный ключ правого брата
                            k2=parent->keys[positionSon]; //k2 - ключ родителя, больше, чем удаляемый и меньше, чем k1
                            insert_to_node(k2, ptr);
                            removeFromNode(key, ptr);
                            parent->keys[positionSon]=k1; //меняем местами k1 и k2
                            removeFromNode(k1, parent->children[positionSon+1]); //удаляем k1 из правого брата
                        } else {
                            //если у левого брата больше, чем t-1 ключей
                            if (parent->children[positionSon-1]->count>(t-1)){ 
                                BNode *temp=parent->children[positionSon-1];
                                k1=temp->keys[temp->count-1]; //k1 - максимальный ключ левого брата
                                k2=parent->keys[positionSon-1]; //k2 - ключ родителя, меньше, чем удаляемый и больше, чем k1
                                insert_to_node(k2, ptr);
                                removeFromNode(key, ptr);
                                parent->keys[positionSon-1]=k1;
                                removeFromNode(k1, temp);
                            } else { //если у обоих братьев не больше t-1 ключей
                                removeFromNode(key, ptr);
                                if (ptr->count<=(t-2)) repair(ptr);
                            }			
                        }	
                    }
                }
            }

            Шестой метод – метод удаления из произвольного узла:

            void Tree::remove(int key, BNode *node){
                BNode *ptr=node;
                int position; //номер ключа
                int i;
                for (int i=0; i<=node->count-1; i++){
                    if (key==node->keys[i]) {
                        position=i;
                        break;
                    }
                }	
                int positionSon; //номер сына по отношению к родителю
                if (ptr->parent!=NULL){
                    for(int i=0; i<=ptr->parent->count; i++){
                        if (ptr->children[i]==ptr){
                            positionSon==i;
                            break;
                        }
                    }							
                }
                //находим наименьший ключ правого поддерева
                ptr=ptr->children[position+1];
                int newkey=ptr->keys[0];
                while (ptr->is_leaf()==false) ptr=ptr->children[0];
                //если ключей в найденном листе не больше 1 - ищем наибольший ключ в левом поддереве
                //иначе - просто заменяем key на новый ключ, удаляем новый ключ из листа
                if (ptr->count>(t-1)) {
                    newkey=ptr->keys[0];
                    removeFromNode(newkey, ptr);
                    node->keys[position]=newkey;
                } else {
                    ptr=node;
                    //ищем наибольший ключ в левом поддереве
                    ptr=ptr->children[position];
                    newkey=ptr->keys[ptr->count-1];
                    while (ptr->is_leaf()==false) ptr=ptr->children[ptr->count];
                    newkey=ptr->keys[ptr->count-1];	
                    node->keys[position]=newkey;	
                    if (ptr->count>(t-1)) removeFromNode(newkey, ptr);
                    else {
                        //если ключей не больше, чем t-1 - перестраиваем
                        removeLeaf(newkey, ptr);
                    }
                }
            }

            И седьмой метод – сам метод удаления, принимающий в качестве входных данных
            — значение ключа для удаления из дерева.

            void Tree::remove(int key){
                BNode *ptr=this->root;
                int position;
                int positionSon;
                int i;
                if (searchKey(key, ptr)==false) {
                    return;
                } else {
                    //ищем узел, в котором находится ключ для удаления
                    for (i=0; i<=ptr->count-1; i++){
                        if (ptr->keys[i]!=0) {	
                            if(key==ptr->keys[i]) {
                                position=i;
                                break;	
                            } else {
                                if ((keykeys[i])){
                                    ptr=ptr->children[i];
                                    positionSon=i;
                                    i=-1;
                                } else {
                                    if (i==(ptr->count-1)) {
                                        ptr=ptr->children[i+1];
                                        positionSon=i+1;
            	                    i=-1;
                                    }
                                }
                            }
                        } else break;	
                    }		
                }	
                if (ptr->is_leaf()==true) {
                    if (ptr->count>(t-1)) removeFromNode(key,ptr);
                    else removeLeaf(key, ptr);
                } else remove(key, ptr);
            }

            Вот, как-то так. Надеюсь, кому-то статья будет полезной. Спасибо за внимание.
            Original source: habrahabr.ru (comments, light).

            https://habrahabr.ru/post/337594/


            Метки:  

            Управление сертификатами с помощью протокола ACME

            Воскресенье, 10 Сентября 2017 г. 16:45 + в цитатник
            dernasherbrezon сегодня в 16:45 Разработка

            Управление сертификатами с помощью протокола ACME

              Возникла передо мной такая задача: автоматический выпуск сертификатов для Web приложения. И требования:


              • CA должны доверять все браузеры т.е. самоподписанные сертификаты не подходят;
              • желательно бесплатно;
              • Выпуск надо делать программно с помощью Java Embedded compact1 profile. Это всё по следам Java и без 16Gb памяти?.

              Наверное многие уже слышали про бесплатные сертификаты от LetsEncrypt и certbot. А можно ли certbot заменить Java?



              ACME


              Многие, конечно, любят LetsEncrypt за бесплатные сертификаты, которые, фактически, позволят перевести весь вэб на https. Но не многие
              знают, что для этого они придумали специальный протокол — ACME. И для меня он по значимости чуть ли не выше самих бесплатных сертификатов.


              Основные особенности протокола:


              • Описывает взаимодействие клиента и REST сервера;
              • Есть поддержка как платных сертификатов, так и бесплатных;
              • Несколько способов авторизации владения доменом;
              • Внесен на принятие в IETF. Сейчас находится в состоянии draft;
              • Все сообщения передаются в формате JSON Web Token.

              Для тех, кому интересны детали и все возможные варианты взаимодействия, можно почитать официальную документацию. Она действительно простая и легко читается в отличии от наших гостов. Здесь же я приведу диаграмму последовательности при выдаче сертификата, которую нужно представлять, если Вы решили интегрироваться.



              Использование


              На официальном сайте LetsEncrypt есть множество клиентов работающих по протоколу ACME. Я взял acme4j. Эта библиотека достаточно компактная и работает в compact1 profile!


              У неё есть вполне рабочий пример использования, с помощью которого я смог выпустить себе сертификат. Для того, чтобы интегрировать библиотеку достаточно с минимальными изменениями скопировать этот код.


              Для продления сертификата необходимо авторизоваться, взять уже готовый CSR и отправить его в CA. После чего скачать новый сертификат.


              Единственная проблема, которая у меня возникла — это подкладывание сертификата в nginx. Поясню на примере:


              • приложение стартует в первый раз;
              • nginx стартует. Поскольку приложение стартует в первый раз, то сертификата ещё нет, и nginx слушает на 80 порту;
              • пользователь заходит в приложение, соглашается с правилами использования сертификатов LetsEncrypt и нажимает кнопку "выдать сертификат";
              • сертификат скачивается.

              И вот тут проблема: для того чтобы включить 443 порт с новым сертификатом, nginx должен перезачитать конфигурацию. Но чтобы это сделать нужен root. Запускать приложение из под root — плохая идея. Запускать nginx из под пользователя тоже — нельзя будет слушать 80/443 порты.


              Я добавил правило для пользователя в sudoers, чтобы можно было делать sudo nginx -s reload. Но это выглядит как костыль. Может кто-нибудь знает как это сделать красивее?


              Итого


              Получение сертификата в автоматическом режиме оказалось достаточно простой процедурой. А если использовать ACME сервер boulder, то можно даже развернуть такую схему у себя в большой организации! Если у вас есть собственный CA для внутренних сервисов, то ACME должен сильно упростить работу с сертификатами.

              Original source: habrahabr.ru (comments, light).

              https://habrahabr.ru/post/337592/


              Метки:  

              Дайджест интересных материалов для мобильного разработчика #220 (4 сентября — 10 сентября)

              Воскресенье, 10 Сентября 2017 г. 15:31 + в цитатник
              EverydayTools сегодня в 15:31 Разработка

              Дайджест интересных материалов для мобильного разработчика #220 (4 сентября — 10 сентября)

                Пока мы готовимся к премьере новых iPhone, давайте посмотрим на последний отсчет Google, дизайн ВКонтакте, использование CoreML и преимущества ARCore, посчитаем математику кликеров и иллюзию выбора. Все это и многое другое в новом дайджесте!



                Последний отсчёт — Гугл развлекается

                Некоторое время назад я писал о смешном методе-проверке «А не козёл ли ты, пользователь?». Сегодня обнаружил ещё один забавный метод для новенькой Android 8.0.

                VK by design

                ВКонтакте — это множество интерфейсов для разных платформ. Сегодня Вы узнаете о том, как мы работаем над этими интерфейсами, создавая дизайн своих продуктов.

                Цвет в дизайне интерфейсов: инструкция по применению

                Будучи по большому счёту дизайнером-самоучкой, мне всегда было интересно, почему так много статей и сайтов говорят о теории цвета и цветовых палитрах. По моему опыту, вероятность сделать красивый дизайн, использую «сплит-комплиментарную палитру», стремится к нулю.

                Дайджест доступен и в виде рассылки. Подписаться вы можете тут.

                iOS


                Android


                Разработка


                Аналитика, маркетинг и монетизация


                Устройства, IoT, AI



                <- Предыдущий дайджест. Если у вас есть другие интересные материалы или вы нашли ошибку — пришлите, пожалуйста, в почту.
                Original source: habrahabr.ru (comments, light).

                https://habrahabr.ru/post/337586/


                Валидация React компонентов с помощью Livr.js

                Воскресенье, 10 Сентября 2017 г. 14:10 + в цитатник
                one_more сегодня в 14:10 Разработка

                Валидация React компонентов с помощью Livr.js

                  Пару лет назад я увидел на хабре статью про LIVR и с тех пор использую библиотеку на всех проектах. С переходом на React я адаптировал для валидации ее же, т.к. существующие решения не предлагали гибкости которой мне хотелось. Свое решение я уже использую на двух проектах и решил выложить в npm — может кому-то еще оно покажетсяя удобным.
                  Пакет называется react-livr-validation.

                  Пример базового использвания
                  import React from 'react';
                  import Validation, {DisabledOnErrors, ValidationInput} from 'react-livr-validation';
                  
                  const schema = {
                      login: ['required', 'not_empty'],
                      password: ['required', 'not_empty']
                  };
                  
                  const data = {
                      login: '',
                      password: ''
                  };
                  
                  export default function() {
                      return (
                          
                              
                  ); }

                  Компонент принимает валидационную схему и первоначальные данные(если данные не валидны, кнопка submit сразу будет неактивна), так же можно передать custom rules и aliased rules
                  const customRules = {
                      alpha_chars: function() {
                          return function(value) {
                              if (typeof value === 'string') {
                                  if (!/[a-z,A-Z]+/.test(value)) {
                                      return 'WRONG_FORMAT';
                                  }
                              }
                          };
                      }
                  };
                  const aliasedRules = [
                      {
                          name: 'strong_password',
                          rules: { min_length: 6 },
                          error: 'TOO_SHORT'
                      }
                  ];
                  
                        // ... form
                  
                  

                  Обертка ValidationInput добавляет в инпут свои обработчики событий, на которые будет происходить валидация. По умолчанию это события change, blur, keyup.
                  
                          
                  
                  

                  Есть возможность реализовать свою обертку — пакет экпортит HOC, который прокидывает в пропсы api
                  // @flow
                  
                  import React, {Component} from 'react'
                  import {ValidationComponent} from 'react-livr-validation'
                  import get from 'lodash/get'
                  import noop from 'lodash/noop'
                  import compose from 'ramda/src/compose'
                  import styled from 'styled-components'
                  
                  type DataChunk = {
                      name: string,
                      value: any
                  }
                  
                  type State = {
                      touched: boolean
                  }
                  
                  type Props = {
                      // will be passed by HOC
                      setData: (data: DataChunk) => void,
                      getError: (name: string) => ?string,
                      getErrors: () => Object,
                      className: string, // for the error block
                      style: Object // for the error block
                      errorCodes: Object,
                      
                      name: string,
                      field: string
                  }
                  
                  class NestedError extends Component {
                      props: Props;
                      
                      isTouched() {
                          const {children} = this.props;
                          return get(children, 'props.value')
                      }
                      
                      state: State = {
                          touched: this.isTouched()
                      }
                      
                      setTouched() {
                          this.setState({
                              touched: true
                          })
                      }
                      
                      cloneElement() {
                          const {children} = this.props;
                          const onBlur = get(children, 'props.onBlur', noop);
                          return React.cloneElement(
                              children,
                              {
                                  onBlur: compose(this.setTouched, onBlur)
                              }
                          )
                      }
                      
                      render() {
                          const {touched} = this.state;
                          const {
                              children, 
                              field, 
                              name, 
                              getError,
                              errorCodes,
                              style,
                              className
                          } = this.props;
                          const errors = getErrors();
                          const error = get(errors, `${field}`.${name});
                          return (
                              
                  {touched ? children : this.cloneElement()} {error && {errorCodes[error] || error} }
                  ); } } const Error = styled.div` color: red; `; export default ValidationComponent(NestedError)
                  Original source: habrahabr.ru (comments, light).

                  https://habrahabr.ru/post/337582/


                  Метки:  

                  Задача о премировании: почувствуй себя менеджером

                  Воскресенье, 10 Сентября 2017 г. 13:30 + в цитатник
                  1. Менеджмент некой компании уделяет большое внимание мотивации сотрудников.
                  2. Для поощрения высоких результатов было решено выдавать премии командам по результатам соблюдения сроков и бюджетов проектов.
                  3. Решение было доведено до сотрудников.
                  4. Одна из команд занималась доработками зрелого и стабильного проекта, успешно выполнила все условия, получила премию.
                  5. Другой команде достался новый сложный проект, люди работали над ним с неподдельным энтузиазмом.
                  6. К сожалению, и запланированные сроки, и бюджет оказались превышены в разы.
                  7. Получившийся в результате продукт дал компании рекордную прибыль.

                  Если вы менеджер в этой компании, то станете ли премировать вторую команду и почему?

                  https://habrahabr.ru/post/337580/


                  Метки:  

                  Как воскресить Ягуара за тысячу часов?

                  Воскресенье, 10 Сентября 2017 г. 13:25 + в цитатник
                  imageБывает меня спрашивают — как я пишу эмуляторы? Попробую ответить на примере одной провалившейся консоли.

                  Эмуляция — почти бесконечное занятие, всегда остаются неточности, и если меня спросят сколько я потратил на эмуляцию 3DO, то я лишь пожму плечами, но одно знаю точно — с эмуляцией 3DO все очень хорошо. Поэтому пришло время найти новую жертву и ей оказался Atari Jaguar. 1000 часов — примерно столько я потратил на разработку ядра эмуляции данной консоли в проекте «Феникс», и вероятно еще столько же понадобится, чтобы поднять совместимость с текущих 95% до 99%, а оставшийся 1% потребует еще, возможно не одну тысячу часов, но это уже отдельные скучные истории про отладку едва уловимых глюков.
                  Читать дальше ->

                  https://habrahabr.ru/post/337566/


                  Метки:  

                  [Из песочницы] Как я перестал любить Angular

                  Воскресенье, 10 Сентября 2017 г. 13:17 + в цитатник
                  MooooM сегодня в 13:17 Разработка

                  Как я перестал любить Angular

                  Вступление


                  Много лет я работал с AngularJS и по сей день использую его в продакшене. Несмотря на то, что идеальным, в силу своей исторически сложившейся архитектуры, его назвать нельзя — никто не станет спорить с тем, что он стал просто вехой в процессе эволюции не только JS фреймворков, но и веба в целом.


                  На дворе 2017ый год и для каждого нового продукта/проекта встает вопрос выбора фреймворка для разработки. Долгое время я был уверен, что новый Angular 2/4 (далее просто Angular) станет главным трендом enterprise разработки еще на несколько лет вперед и даже не сомневался что буду работать только с ним.


                  Сегодня я сам отказываюсь использовать его в своем следующем проекте.


                  Дисклеймер: данная статья строго субъективна, но таков мой личный взгляд на происходящее и касается разработки enterprise-level приложений.


                  AngularJS



                  За годы эволюции большинство недостатков фреймворка были устранены, библиотеки доведены до весьма стабильного состояния, а размеры комьюнити стремятся к бесконечности. Пожалуй, можно сказать, что в первом ангуляре мало что можно сильно улучшить не сломав при этом сотни существующих приложений.


                  Несмотря на колоссальные труды и общее ускорение фреймворка (особенно после 1.5),
                  основным его недостатком я бы все же назвал скорость работы. Конечно это простительно, учитывая что спустя столько лет, худо-бедно до сих пор поддерживается обратная совместимость.


                  Angular



                  И вот наконец Angular был переписан с нуля дабы стать основной для многих будущих веб-приложений.


                  Конечно путь к этому был долог и полон Breaking Changes,
                  но на сегодняшний день Angular 4 стабилен и позиционируется как полностью production-ready.


                  Одна из наиболее крутых вещей, которую дал нам новый Angular — популяризация TypeScript.
                  Лично я был с ним знаком и работал еще до того, как он стал основным для моего любимого фреймворка,
                  но многие узнали о нем именно благодаря Angular.


                  TypeScript



                  Не буду подробно останавливаться на TypeScript, т.к. это тема для отдельной статьи,
                  да и написано о нем уже больше чем нужно. Но для enterprise разработки TypeScript дает огромное количество преимуществ. Начиная с самой статической типизации и областями видимости и заканчивая поддержкой ES7/8 даже для IE9.


                  Главное преимущество работы с TypeScript — богатый инструментарий и прекрасная поддержка IDE. По нашему опыту, юнит тестов с TS приходится писать существенно меньше.


                  Vue



                  Если вы читаете данную статью, то с вероятностью 95% вы уже знаете что это такое.


                  Но для тех 5% кто еще не знает — Vue.js это крайне легковесный (но очень богатый по функционалу) фреймворк, вобравший в себя многое хорошее, как из AngularJS, так и из React.


                  Фактически больше он похож все же на React, но шаблоны практически идентичны AngularJS (HTML + Mustache).


                  В реальности от AngularJS, он отличается очень даже разительно, но в поверхносном смысле,
                  понять как он работает, если у вас уже был опыт работы с React или AngularJS, будет несложно.


                  Предыстория


                  Было — большой проект на AngularJS


                  Последний мой проект, который совсем недавно вышел в production, мы писали на AngularJS 1.5-1.6.


                  Вопреки наличию стабильной версии Angular уже долгое время, мы приняли решение не мигрировать на него по ряду причин (не столько технических, сколько политических).
                  Но одной из фич современного веба мы пользовались с самого начала — это TypeScript.


                  Вот пример нашего компонента из данного проекта:


                  import {Component} from "shared-front/app/decorators";
                  import FileService, {ContentType, IFile} from "../file.service";
                  import AlertService from "shared/alert.service";
                  
                  @Component({
                      template: require("./file.component.html"),
                      bindings: {
                          item: "<",
                      },
                  })
                  export default class FileComponent {
                      public static $inject = ["fileService"];
                      public item: IFile;
                  
                      constructor(private fileService: FileService, private alertService: AlertService) {
                      }
                  
                      public isVideo() {
                          return this.item.contentKeyType === ContentType.VIDEO;
                      }
                  
                      public downloadFile() {
                          this.fileService.download(this.getFileDownloadUrl()).then(() => {
                              this.alertService.success();
                          });
                      }
                  
                      private getFileDownloadUrl() {
                          return `url-for-download${this.item.text}`;
                      }
                  }

                  На мой взгляд выглядит очень даже приятно, не слишком многословно, даже если вы не фанат TS.
                  К тому же все это замечательно тестируется как Unit-тестами, так и Е2Е.


                  Продолжай AngularJS развиваться, как тот же React, и будь он побыстрее, можно было бы и по сей день продолжать писать крупные проекты на нем.


                  В принципе это по прежнему вполне разумно, если ваша команда очень хорошо знакома с AngularJS. Но думаю, что большинство все же предпочитает двигаться в ногу со временем и захочет выбрать нечто более современное.


                  Стало — средний проект на Angular


                  Так мы и поступили, рационально выбрав Angular 2 (позже 4) для нашего нового проекта несколько месяцев назад.


                  Выбор казался довольно очевидным, учитывая, что вся наша команда имеет большой опыт работы с первой версией. К тому же, лично я ранее работал с alpha-RC версиями и тогда проблемы фреймворка списывались на 0.х номер версии.


                  К сожалению, как теперь стало понятно, многие из этих проблем заложены в архитектуру и не изменятся в ближайшее время.


                  А вот пример компонента из проекта на Angular:


                  import {Component} from '@angular/core';
                  
                  import FileService, {ContentType, IFile} from "../file.service";
                  import AlertService from "shared/alert.service";
                  
                  @Component({
                    selector: 'app-file',
                    templateUrl: './file.component.html',
                    styleUrls: ['./file.component.scss']
                  })
                  export class FileComponent {
                  
                      Input() item: IFile;
                  
                      constructor(private fileService: FileService, private alertService: AlertService) {
                      }
                  
                      public isVideo() {
                          return this.item.contentKeyType === ContentType.VIDEO;
                      }
                  
                      public downloadFile() {
                          this.fileService.download(this.getFileDownloadUrl()).subscribe(() => {
                              this.alertService.success();
                          });
                      }
                  
                      private getFileDownloadUrl() {
                          return `url-for-download${this.item.text}`;
                      }
                  }
                  

                  Возможно чуть чуть более многословно, но гораздо чище.


                  Плюсы


                  Angular CLI — единственное реальное преимущество перед AngularJS



                  Первое, что вы установите при разработке нового Angular 4 приложения это Angular CLI


                  Нужен он CLI для того, чтобы упростить создание новых компонентов/модулей/тестов итд.
                  На мой взгляд это едва ли не лучшее, что есть в новом Angular. Тула действительно очень удобная в использовании и сильно ускоряет процесс разработки.


                  Это то, чего так сильно не хватало в AngularJS, и все решали проблему отсутствия данной тулзы по-своему. Множество различных сидов (стартеров), сотни разных подходов к одному и тому же, в общем анархия. Теперь с этим покончено.


                  Конечно CLI тоже имеет ряд недостатков в части настроек и конфигурации "под себя", но все же он на голову выше аналогичных утилит для React (create-react-app) или Vue (vue-cli). Хотя второй, благодаря своей гибкости, становится лучше с каждым днем.


                  Минусы или "За что я перестал любить Angular"


                  Изначально я не хотел писать очередную хейтерскую статью вроде
                  Angular 2 is terrible (нашелся даже перевод).


                  Однако, несмотря на то, что статья выше была написана для уже весьма устаревшей версии,
                  по большинству пунктов она совершенно в точку.
                  Я бы даже сказал, что местами автор был слишком мягок.


                  В целом не совсем разделяю взгяд автора на RxJS, т.к. библиотека невероятно мощная.


                  An Ajax request is singular, and running methods like Observable.prototype.map when there will only ever be one value in the pipe makes no semantic sense. Promises on the other hand represent a value that has yet to be fulfilled, which is exactly what a HTTP request gives you. I spent hours forcing Observables to behave before giving up using Observable.prototype.toPromise to transform the Observable back to a Promise and simply using Promise.all, which works much better than anything Rx.js offers.

                  В реальности, благодаря RxJS, со временем становится очень приятно воспринимать любые данные,
                  пусть даже не совсем подходящие для Observable, как единую шину.


                  Но суровая правда в том, что Object.observe нативно мы все же не увидим:


                  After much discussion with the parties involved, I plan to withdraw the Object.observe proposal from TC39 (where it currently sits at stage 2 in the ES spec process), and hope to remove support from V8 by the end of the year (the feature is used on 0.0169% of Chrome pageviews, according to chromestatus.com).

                  И несмотря на огромное количество фич, которые Rx позволяет делать — делать его ядром фреймворка не самый правильный подход. Думаю желающие, вроде меня, могли бы подключить его и отдельно без особых проблем.


                  Также не соглашусь в целом по поводу TypeScript'а, так как это все же замечательный язык, но об этом ниже.


                  Статья крайне рекомендуется к ознакомлению, если вы уже используете или планируете использовать Angular


                  Однако все же изложу несколько собственных мыслей (к сожалению, все больше негативных), не упомянутых в статье выше.


                  TypeScript в Angular


                  Пожалуй самое болезненное разочарование для меня — это то, во что превратили работу с TypeScript'ом в Angular.


                  Ниже несколько примеров наиболее неудачных решений.


                  Ужасные API


                  Одной из основных проблем использования TypeScript в Angular я считаю крайне спорные API.
                  Сам по себе TypeScript идеально подходит для написания максимально строгого кода, без возможностей случайно сделать шаг не в ту сторону. Фактически он просто создан для того, чтобы писать публичный API, но команда Angular сделала все, чтобы данное преимущество превратилось в недостаток.


                  Примеры:


                  HttpParams

                  По какой-то причине команда Angular решила сделать класс HttpParams иммутабельным.
                  Иммутабельность это здорово, но если вы думаете, что большинство классов в Angular являются таковыми, то это вовсе не так.


                  Например код вида:


                  let params = new HttpParams();
                  params.set('param', param);
                  params.set('anotherParam', anotherParam);
                  ...
                  this.http.get('test', {params: params});

                  Не будет добавлять параметры к запросу. Казалось бы почему?
                  Ведь никаких ошибок, ни TypeScript ни сам Angular не отображает.


                  Только открыв сам класс в TypeScript можно найти комментарий


                  This class is immuatable — all mutation operations return a new instance.

                  Конечно, это совершенно неочевидно.


                  В вот и вся документация про них:


                  http
                    .post('/api/items/add', body, {
                      params: new HttpParams().set('id', '3'),
                    })
                    .subscribe();

                  RxJS operator import

                  Начнем с того, что документация по Angular вообще не имеет толкового разбора и описания Observable и того, как с ними работать.


                  Нет даже толковых ссылок на документацию по RxJS. И это при том, что Rx является ключевой частью фреймворка, а само создание Observable уже отличается:


                  // rx.js
                  Rx.Observable.create();
                  vs
                  // Angular
                  new Observable()

                  Ну да и черт с ним, здесь я хотел рассказать о Rx + TypeScript + Angular.


                  Допустим вы хотите использовать некий RxJS оператор, вроде do:


                  observable.do(event => {...})

                  В коде никакой ошибки не произойдет, это сработает и замечательно запустится.


                  Вот только, во время выполнения возникнет такая ошибка:


                  ERROR TypeError: observable.do is not a function

                  Потому что вы очевидно (потратили кучу времени на поиск проблемы) забыли заимпортировать сам оператор:


                  import 'rxjs/add/operator/do';

                  Почему это ломается в рантайме, если у нас есть TypeScript? Не знаю. Но это так.


                  Router API

                  Претензий к новому ангуляровскому роутеру у меня накопилось уже великое множество, но его API — одна из основных.


                  Events

                  Теперь для работы с параметрами предлагается подписываться на события роутера.
                  Ок, пускай, но приходят всегда все события, независимо от того, какие нам нужны.
                  А проверять предлагается через instanceof (снова новый подход, отличный от большинства других мест):


                  this.router.events.subscribe(event => {
                    if(event instanceof NavigationStart) {
                      ...
                    }
                  }


                  В очередной раз странным решением было сделать всю работу с роутами командами — причем массивом из них. Простые и наиболее распространенные переходы будут выглядеть как-то так:


                  this.router.navigate(['/some']);
                  ...
                  this.router.navigate(['/other']);
                  

                  Почему это плохо?


                  Потому что команды в данном случае имеют сигнатуру any[].
                  Для незнакомых с TypeScript — это фактически отключение его фич.


                  Это при том, что роутинг — наиболее слабо связанная часть в Angular.


                  Например мы в нашем AngularJS приложении наоборот старались типизировать роуты,
                  чтобы вместо простых строк они были хотя бы enum.


                  Это позволяет находить те или иные места, где используется данный роут без глобального поиска по строке 'some'.


                  Но нет, в Angular это преимущество TypeScript не используется никак.


                  Lazy Load

                  Этот раздел также мог бы пойти на отдельную статью, но скажу лишь, что в данном случае
                  игнорируются любые фичи TypeScript и название модуля пишется как строка, в конце через #


                  {
                    path: 'admin',
                    loadChildren: 'app/admin/admin.module#AdminModule',
                  },

                  Forms API

                  Для начала — в Angular есть два типа форм: обычные
                  и реактивные.


                  Само собой, работать с ними нужно по-разному.


                  Однако лично меня раздражает именно API reactive forms:


                  // Зачем нужен первый пустой параметр?
                  // Почему name это массив c валидатором??
                  this.heroForm = this.fb.group({
                    name: ['', Validators.required ],
                  });

                  или из документации


                  // Почему пустое поле это имя??
                  this.heroForm = this.fb.group({
                    name: '', // <--- the FormControl called "name"
                  });

                  и так далее


                  this.complexForm = fb.group({   
                    // Почему понадобился compose ?
                    // Неужели нельзя без null ??
                    'lastName': [null, Validators.compose([Validators.required, Validators.minLength(5), Validators.maxLength(10)])],
                    'gender' : [null, Validators.required],
                  })

                  А еще — нельзя просто использовать атрибуты типа [disabled] с реактивными формами...


                  Это далеко не все примеры подобных откровенно спорных решений, но думаю, что для раздела про API этого хватит


                  __metadata


                  К сожалению использование горячо любимого мною TypeScript'а в Angular слишком сильно завязано на декораторы.


                  Декораторы — прекрасная вещь, но к сожалению в рантайме нет самой нужной их части, а именно __metadata.


                  __metadata просто напросто содержит информацию о классе/типе/методе/параметрах, помеченном тем или иным декоратором,
                  для того чтобы позже эту информацию можно было получить в рантайме.


                  Без метаданных декораторы тоже можно использовать — во время компиляции, но толку в таком случае от них не очень много.


                  Впрочем, мы в нашем AngularJS приложении использовали такие декораторы, например @Component:


                  export const Component = (options: ng.IComponentOptions = {}) => controller => angular.extend(options, {controller});

                  Он фактически просто оборачивает наши TypeScript классы в компоненты AngularJS и делает их контроллерами.


                  Но в Angular, несмотря на экспериментальность фичи, это стало частью ядра фреймворка,
                  что делает необходимым использование полифила reflect-metadata в совершенно любом случае. Очень спорное решение.


                  Абстракции


                  Обилие внутренних классов и абстракций, а также всякие хитрости завязанные на TypeScript,
                  также не идут на пользу его принятию комьюнити. Да еще и портят впечатление о TS в целом.


                  Самый яркий пример подобных проблем — это Dependency Injection в Angular.


                  Сама по себе концепция замечательная, особенно для unit тестирования.
                  Но практика показывает, что большой нужды делать из фронтенда нечто Java-подобное нет.
                  Да, в нашем AngularJS приложении мы очень активно это использовали, но поработав с тестированием Vue компонентов, я серьезно начал сомневаться в пользе DI.


                  В Angular для большинства обычных зависимостей, вроде сервисов, DI будет выглядеть очень просто, с получением через конструктор:


                  constructor(heroService: HeroService) {
                    this.heroes = heroService.getHeroes();
                  }

                  Но так работает только для TypeScript классов, и если вы хотите добавить константу, необходимо будет использовать @Inject:


                  constructor(@Inject(APP_CONFIG) config: AppConfig) {
                    this.title = config.title;
                  }

                  Ах да, сервисы которые вы будете инжектить должны быть проанотированы как @Injectable().


                  Но не все, а только те, у которых есть свои зависимости, если их нет — можно этот декоратор не указывать.


                  Consider adding @Injectable() to every service class, even those that don't have dependencies and, therefore, do not technically require it.
                  Here's why:

                  Future proofing: No need to remember @Injectable() when you add a dependency later.

                  Consistency: All services follow the same rules, and you don't have to wonder why a decorator is missing.

                  Почему бы не сделать это обязательным сразу, если потом это рекомендуется делать всегда?


                  Еще прекрасная цитата из официальной документации по поводу скобочек:


                  Always write @Injectable(), not just @Injectable. The application will fail mysteriously if you forget the parentheses.

                  Короче говоря, создается впечатление, что TypeScript в Angular явно используется не по назначению.


                  Хотя еще раз подчеркну, что сам по себе язык обычно очень помогает в разработке.


                  Синтаксис шаблонов


                  Синтаксис шаблонов — основная претензия к Angular. И по вполне объективным причинам.


                  Пример не только множества разных директив, но еще и разных вариантов их использования:


                  style using ngStyle
                  Hello Wordl!
                  CSS class using property syntax, this text is blue
                  object of classes
                  array of classes
                  string of classes

                  Изначально разработчики позиционировали новый синтакисис шаблонов, как спасение от множества директив.


                  Обещали, что будет достаточно только [] и ().


                  Binding Example
                  Properties
                  Events
                  Two-way

                  К сожалению в реальности директив едва ли не больше чем в AngularJS.



                  И да, простое правило запоминания синтаксиса two-way binding про банан в коробке
                  из официальной документации:


                  Visualize a banana in a box to remember that the parentheses go inside the brackets.

                  Документация


                  Вообще писать про документацию Angular даже нет смысла, она настолько неудачная,
                  что достаточно просто попытаться ее почитать, чтобы все стало понятно.


                  Контрпример — доки Vue.
                  Мало того, что написаны подробно и доходчиво, так еще и на 6 языках,
                  в т.ч. русском.


                  View encapsulation


                  Angular позволяет использовать так называемый View encapsulation.


                  Суть сводится к эмуляции Shadow DOM или использовании нативной его поддержки.


                  Сам по себе Shadow DOM — прекрасная вещь и действительно потенциально позволяет использовать даже разные CSS фреймворки для разных копмонентов без проблем.
                  Однако нативная поддержка на сегодняшний день совершенно печальна.


                  По умолчанию включена эмуляция Shadow DOM.


                  Вот пример простого CSS для компонента:


                  .first {
                    background-color: red;
                  }
                  .first .second {
                    background-color: green;
                  }
                  .first .second .third {
                    background-color: blue;
                  }

                  Angular преобразует это в:


                  .first[_ngcontent-c1] {
                    background-color: red;
                  }
                  .first[_ngcontent-c1]   .second[_ngcontent-c1] {
                    background-color: green;
                  }
                  .first[_ngcontent-c1]   .second[_ngcontent-c1]   .third[_ngcontent-c1] {
                    background-color: blue;
                  }

                  Совершенно не ясно зачем делать именно так.


                  Например Vue делает то же самое, но гораздо чище:


                  .first[data-v-50646cd8] {
                    background-color: red;
                  }
                  .first .second[data-v-50646cd8] {
                    background-color: green;
                  }
                  .first .second .third[data-v-50646cd8] {
                    background-color: blue;
                  }

                  Не говоря уже о том, что в Vue это не дефолтное поведение и включается добавлением простого scoped к стилю.


                  Так же хотелось бы отметить, что Vue (vue-cli webpack) подобным же образом позволяет указывать SASS/SCSS, тогда как для Angular CLI нужны команды типа ng set defaults.styleExt scss.
                  Не очень понятно зачем все это, если внутри такой же webpack.


                  Но это все ерунда, реальные проблемы у нас начались, когда нам потребовалось использовать сторонние компоненты.


                  В частности мы использовали один из наиболее популярных UI фреймворков — PrimeNG,
                  а он иногда использует подобные селекторы:


                  body .ui-tree .ui-treenode .ui-treenode-content .ui-tree-toggler {
                      font-size: 1.1em;
                  }

                  Они по определению имеют больший приоритет нежели стили компонента, который использует в себе тот или иной сторонний элемент.


                  Что в итоге приводит к необходимости писать нечто подобное:


                  body :host >>> .ui-tree .ui-treenode .ui-treenode-content .ui-tree-toggler {
                    font-size: 2em;
                  }

                  Иногда и вовсе приходилось вспомнить великий и ужасный !important.


                  Безусловно все это связано конкретно с PrimeNG и не является как таковой проблемой фреймворка, но это именно та проблема, которая скорее всего возникнет и у вас при реальной работе с Angular.


                  К слову о стабильности


                  В примере выше мы использовали >>> — как и /deep/ это алиас для так называемого shadow-piercing селектора.


                  Он позволяет как бы "игнорировать" Shadow DOM и для некоторых сторонних компонентов порой просто незаменим.


                  В одном из относительно свежих релизов Angular создатели фреймворка решили,
                  в соответствии со стандартом, задепрекейтить /deep/ и >>>.


                  Никаких ошибок или ворнингов их использование не принесло, они просто перестали работать.
                  Как выяснилось позже, теперь работает только ::ng-deep — аналог shadow-piercing селектора в Angular вселенной.


                  Обновление это было отнюдь не мажорной версии (4.2.6 -> 4.3.0), просто в один прекрасный момент наша верстка во многих местах поползла (спасибо и NPM за версии с шапочкой ^).


                  Конечно, не все наши разработчики ежедневно читают ChangeLog Angular 4, да и за трендами веб разработки не всегда можно уследить. Само собой сначала грешили на собственные стили — пришлось потратить немало времени и нервов для обнаружения столь неприятной особенности.


                  К тому же скоро и ::ng-deep перестанет работать.
                  Как в таком случае править стили кривых сторонних компонентов, вроде тех же PrimeNG, ума не приложу.


                  Наш личный вывод: дефолтная настройка — эмуляция Shadow DOM порождает больше проблем чем решает.


                  Свой HTML парсер


                  Это вполне потянет на отдельную статью, но вкратце суть в том, что команда Angular действительно написала свой HTML парсер. По большей части это было необходимо для обеспечения регистрозависимости.


                  Нет смысла холиварить на тему ухода Angular от стандартов, но по мнению многих это довольно странная идея, ведь в том же AngularJS обычного HTML (регистронезависимого) вполне хватало.


                  С AngularJS нередко бывало такое: добавили вы некий а тест не написали.
                  Прошло некоторое время и модуль который содержит логику данного компонента был удален/отрефакторен/итд.


                  Так или иначе — теперь ваш компонент не отображается.


                  Теперь он сразу определяет неизвестные теги и выдает ошибку если их не знает.


                  Правда теперь любой сторонний компонент требует либо полного отключения этой проверки,
                  либо включения CUSTOM_ELEMENTS_SCHEMA которая пропускает любые тэги с -


                  Сорцы можете оценить самостоятельно


                  ...
                  const TAG_DEFINITIONS: {[key: string]: HtmlTagDefinition} = {
                    'base': new HtmlTagDefinition({isVoid: true}),
                    'meta': new HtmlTagDefinition({isVoid: true}),
                    'area': new HtmlTagDefinition({isVoid: true}),
                    'embed': new HtmlTagDefinition({isVoid: true}),
                    'link': new HtmlTagDefinition({isVoid: true}),
                    'img': new HtmlTagDefinition({isVoid: true}),
                    'input': new HtmlTagDefinition({isVoid: true}),
                    'param': new HtmlTagDefinition({isVoid: true}),
                    'hr': new HtmlTagDefinition({isVoid: true}),
                    'br': new HtmlTagDefinition({isVoid: true}),
                    'source': new HtmlTagDefinition({isVoid: true}),
                    'track': new HtmlTagDefinition({isVoid: true}),
                    'wbr': new HtmlTagDefinition({isVoid: true}),
                    'p': new HtmlTagDefinition({
                      closedByChildren: [
                        'address', 'article', 'aside', 'blockquote', 'div', 'dl',      'fieldset', 'footer', 'form',
                        'h1',      'h2',      'h3',    'h4',         'h5',  'h6',      'header',   'hgroup', 'hr',
                        'main',    'nav',     'ol',    'p',          'pre', 'section', 'table',    'ul'
                      ],
                      closedByParent: true
                    }),
                  ...
                    'td': new HtmlTagDefinition({closedByChildren: ['td', 'th'], closedByParent: true}),
                    'th': new HtmlTagDefinition({closedByChildren: ['td', 'th'], closedByParent: true}),
                    'col': new HtmlTagDefinition({requiredParents: ['colgroup'], isVoid: true}),
                    'svg': new HtmlTagDefinition({implicitNamespacePrefix: 'svg'}),
                    'math': new HtmlTagDefinition({implicitNamespacePrefix: 'math'}),
                    'li': new HtmlTagDefinition({closedByChildren: ['li'], closedByParent: true}),
                    'dt': new HtmlTagDefinition({closedByChildren: ['dt', 'dd']}),
                    'dd': new HtmlTagDefinition({closedByChildren: ['dt', 'dd'], closedByParent: true}),
                    'rb': new HtmlTagDefinition({closedByChildren: ['rb', 'rt', 'rtc'
                    ...

                  А теперь ключевой момент — все эти ошибки происходят в консоли браузера в рантайме,
                  нет, ваш webpack билд не упадет, но и увидеть что либо кроме белого экрана не получится.
                  Потому что по умлочанию используется JIT компилятор.


                  Однако это решается прекомпиляцией шаблонов благодаря другому, AOT компилятору.
                  Нужно всего лишь собирать с флагом --aot, но и здесь не без ложки дегтя, это плохо работает с ng serve
                  и еще больше тормозит и без того небыструю сборку. Видимо поэтому он и не включен по умолчанию (а стоило бы).


                  Наличие двух различно работающих компиляторов само по себе довольно опасно звучит,
                  и на деле постоянно приводит к проблемам.


                  У нас с AOT было множество разнообразных ошибок, в том числе весьма неприятные и до сих пор открытые, например нельзя использовать экспорты по умолчанию


                  Обратите внимание на элегантные предлагаемые решения проблемы:


                  don't use default exports :)

                  Just place both export types and it works

                  Или нечто подобное описанному здесь (AOT не всегда разбирает замыкания)


                  Код подобного вида вызывает очень странные ошибки компилятора AOT:


                  @NgModule({
                    providers: [
                      {provide: SomeSymbol, useFactor: (i) => i.get('someSymbol'), deps: ['$injector']}
                    ]
                  })
                  export class MyModule {}

                  Приходится подстраиваться под компилятор и переписывать код в более примитивном виде:


                  export factoryForSomeSymbol = (i) => i.get('someSymbol');
                  
                  @NgModule({
                    providers: [
                      {provide: SomeSymbol, useFactor: factoryForSomeSymbol, deps: ['$injector']}
                    ]
                  })
                  export class MyModule {}

                  Также хотелось бы отметить, что текст ошибок в шаблонах зачастую совершенно неинформативен.


                  Zone.js


                  Одна из концептуально очень крутых вещей которые появились в Angular это Zone.js.
                  Он позволяет отслеживать контекст выполнения для асинхронных задач, но новичков отпугивают огромной длины стактрейсы. Например:


                  core.es5.js:1020 ERROR Error: Uncaught (in promise): Error: No clusteredNodeId supplied to updateClusteredNode.
                  Error: No clusteredNodeId supplied to updateClusteredNode.
                      at ClusterEngine.updateClusteredNode (vis.js:47364)
                      at VisGraphDataService.webpackJsonp.../../../../../src/app/services/vis-graph-data.service.ts.VisGraphDataService.updateNetwork (vis-graph-data.service.ts:84)
                      at vis-graph-display.service.ts:63
                      at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:391)
                      at Object.onInvoke (core.es5.js:3890)
                      at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:390)
                      at Zone.webpackJsonp.../../../../zone.js/dist/zone.js.Zone.run (zone.js:141)
                      at zone.js:818
                      at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:424)
                      at Object.onInvokeTask (core.es5.js:3881)
                      at ClusterEngine.updateClusteredNode (vis.js:47364)
                      at VisGraphDataService.webpackJsonp.../../../../../src/app/services/vis-graph-data.service.ts.VisGraphDataService.updateNetwork (vis-graph-data.service.ts:84)
                      at vis-graph-display.service.ts:63
                      at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:391)
                      at Object.onInvoke (core.es5.js:3890)
                      at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:390)
                      at Zone.webpackJsonp.../../../../zone.js/dist/zone.js.Zone.run (zone.js:141)
                      at zone.js:818
                      at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:424)
                      at Object.onInvokeTask (core.es5.js:3881)
                      at resolvePromise (zone.js:770)
                      at zone.js:696
                      at zone.js:712
                      at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:391)
                      at Object.onInvoke (core.es5.js:3890)
                      at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:390)
                      at Zone.webpackJsonp.../../../../zone.js/dist/zone.js.Zone.run (zone.js:141)
                      at zone.js:818
                      at ZoneDelegate.webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:424)
                      at Object.onInvokeTask (core.es5.js:3881)

                  Некоторым Zone.js нравится и
                  наверняка в продакшене нам это еще не раз поможет, но в нашем проекте большой пользы мы пока не извлекли.


                  А ошибок в консоли зачастую больше одной и поиск сути проблемы постоянно осложняется анализом таких стактрейсов.


                  Не говоря уж о том, что нередко длины консоли хрома часто просто не хватает чтобы вместить все сразу.


                  И сама суть проблемы остается где-то далеко наверху, если ошибка происходит слишком много раз.



                  UI frameworks


                  Еще один пункт, который формально не относится к самому фреймворку — это довольно скудное количество готовых наборов UI компонентов. Думаю, что написание большинства компонентов с нуля — непозволительная роскошь для многих веб-разработчиков. Зачастую проще, быстрее и разумнее взять готовый UI framework вместо написания собственных гридов и деревьев.


                  Да, я понимаю, что выбирать фреймворк по наличиую UI компонентов в корне неверно,
                  но при реальной разработке это необходимо.


                  Вот список основных UI фреймворков для Angular: https://angular.io/resources (раздел UI components).


                  Рассмотрим наиболее популярные бесплатные варианты.


                  Angular Material 2



                  Безусловно, наибольшие надежды я возлагал на Angular Material 2 ввиду того, что разрабатывается он командой Angular и наверняка будет соответствовать всем гайдлайнам.


                  К сожалению, несмотря на его возраст, набор компонентов крайне мал.


                  На момент начала написания данной статьи, и уж тем более, когда мы выбирали фреймворк для проекта — гридов не было.


                  И вот совсем недавно он-таки появился. Но функционал все же пока довольно базовый.


                  Я считаю, что Angular Material 2 подойдет лишь небольшим или, в лучшем случае, средним проектам, т.к. до сих пор нет, например, деревьев. Часто очень нужны компоненты вроде multiple-select, коих тоже нет.


                  Отдельно стоит сказать про очень скупую документацию и малое количество примеров.


                  Перспективы развития тоже довольно печальные.


                  Feature Status
                  tree In-progress
                  stepper In-progress, planned Q3 2017
                  sticky-header In-progress, planned Q3 2017
                  virtual-repeat Not started, planned Q4 2017
                  fab speed-dial Not started, not planned
                  fab toolbar Not started, not planned
                  bottom-sheet Not started, not planned
                  bottom-nav Not started, not planned

                  Bootstrap



                  По тем же причинам, что и выше не буду останавливаться на Bootstrap фреймворках типа
                  ng2-bootstrap (получше) и ngx-bootstrap.
                  Они очень даже неплохи, но простейшие вещи можно сделать и обычным CSS, а сложных компонентов тут нет (хотя наверняка многим будет достаточно modal, datepicker и typeahead).


                  Prime Faces



                  Это на сегодняшний день наиболее популярый фреймворк содержащий множество сложных компонентов. В том числе гриды и деревья (и даже Tree Table!).


                  Изначально я вообще довольно скептически относился к PrimeFaces т.к. у меня был давний опыт работы с JSF, и много неприятных воспоминаний. Да и выглядят PrimeNG визуально так же (не очень современно). Но на момент начала нашего проекта достойных альтернатив не было, и все же хочется сказать спасибо разработчикам за то, что в короткие сроки был написан дейтсивтельно широчайший инструментарий.


                  Однако проблем с этим набором компонентов у нас возникало очень много.


                  Часто документация совершенно не помогает.


                  Забыли добавить некий модуль — что-то просто молча не работает и никаких ошибок не возникает.
                  Приходится дебажить и выясняется, что сорцы совершенно без комментариев.


                  В общем, несмотря на наличие большого количества готовых компонентов,
                  по возможности, я бы не рекомендовал выбирать PrimeNG в качестве базы.


                  Clarity



                  Луч света в темном царстве — это относительно молодая (меньше года от роду) библиотека Clarity от vmware.


                  Набор компонентов впечатляет,
                  документация крайне приятная, да и выглядят здорово.


                  Фреймворк не просто предоставляет набор UI компонентов, но и CSS гайдлайны.
                  Эдакий свой bootstrap. Благодаря этому достигается консистентный и крайне приятный/минималистичный вид компонентов.


                  Гриды очень функциональные и стабильные, а сорцы говорят сами за себя
                  (о боже, комментарии и юнит тесты, а так можно было?).


                  Однако пока что очень слабые формы,
                  нет datepicker'а и select2-подобного компонента.
                  Работа над ними идет в данный момент:
                  DatePicker,
                  Select 2.0
                  (как всегда дизайн на высоте, и хотя с разработкой не торопятся я могу быть уверен, что делают на совесть).


                  Пожалуй, "Clarity Design System" — единственная причина почему я еще верю в жизнь Angular
                  (и вообще единственный фреймворк который не стыдно использовать для enterprise разработки).
                  Как никак VMware серьезнейший мейнтейнер и есть надежда на светлое будущее.


                  Мы только начали его использовать и наверняка еще столкнемся с проблемами,
                  но на сегодняшний день он нас полностью устраивает и просто прекрасно работает.


                  Но он лишь один


                  Да, я считаю что для Angular на сегодняшний день есть лишь один достойный UI фреймворк.
                  О чем это говорит?


                  Полноценно разрабатывать такие фреймворки для Angular могут лишь серьезнейшие компании вроде той же VMware. Нужен ли вам такой суровый enterprise? Каждый решает сам.


                  А теперь давайте посмотрим, что происходит с одним из свежих конкурентов.


                  Vue UI frameworks



                  Для сравнения мощные уже существующие фреймворки для Vue.js с теми же гридами:


                  Element (~15k stars), Vue Material
                  (существенно младше Angular Material 2 но уже содержит в разы больше),
                  Vuetify (снова Material и снова множество компонентов),
                  Quasar,
                  также надо отметить популярные чисто китайские фреймворки типа
                  iView и Muse-UI
                  (iView выглядит очень приятно, но документация хромает).


                  Вот и простой, но очевидный, пример того, что писать компоненты на Vue гораздо проще.
                  Это позволяет даже выбирать наиболее приглянувшийся из множества наборов компонентов,
                  нежели надяться на один, который поддерживает огромная команда.


                  Какой вывод мы сделали?


                  Благодаря Clarity есть надежда на то, что наш Angular проект в дальнейшем будет становится только лучше.


                  Однако для нас стало очевидно, что количество всевозможных проблем с Angular,
                  а также многочисленные его усложнения совершенно не приносят пользы даже для больших проектов.


                  В реальности все это лишь увеличивает время разработки, не снижая стоимость поддержки и рефакторинга.


                  Поэтому для нашего нового проекта мы выбрали Vue.js.


                  Достаточно просто развернуть базовый webpack шаблон для vue-cli и оценить скорость работы библиотеки.


                  Несмотря на то, что лично я всегда был сторонником фреймворков all-in-one,
                  Vue без особых проблем делает почти все то же, что и Angular.


                  Ну и конечно, множество тех же UI framework'ов также играет свою роль.


                  Единственное чего немного не хватает — чуть более полноценной поддержки TypeScript,
                  уж очень много головной боли он нам сэкономил за эти годы.


                  Но ребята из Microsoft уже вот вот вмержает ее.
                  А потом она появится и в webpack шаблоне.


                  Почему мы не выбрали React? После AngularJS наша команда гораздо проще вошла в Vue,
                  ведь все эти v-if, v-model и v-for уже были очень знакомы.


                  Лично мне во многом, но не во всем, нравится Aurelia но уж больно она малопопулярна,
                  а в сравнении с взрывным ростом популярности Vue она кажется совсем неизвестной.


                  Надеюсь, что через год-два Angular под давлением community все-таки избавится от всего лишнего,
                  исправит основные проблемы и станет наконец тем enterprise framework'ом, которым должен был.
                  Но сегодня я рекомендую вам посмотреть в сторону других, более легковесных и элегантных решений.
                  И поверьте, спустя 4 года работы с Angular, вот так бросить его было очень нелегко.
                  Но достаточно один раз попробовать Vue...

                  Original source: habrahabr.ru (comments, light).

                  https://habrahabr.ru/post/337578/


                  Метки:  

                  ICO для моей игры: ошибки и инсайты

                  Воскресенье, 10 Сентября 2017 г. 12:43 + в цитатник
                  Alienter сегодня в 12:43 Разное

                  ICO для моей игры: ошибки и инсайты

                    Некоторое время назад я публиковал на Хабре анонс по разработке моей игры (в стиле выживание в пиратском сеттинге), ссылка.Порцию свежих новостей по разработке можно найти, например, здесь, в этой же статье я хочу рассказать (а может, даже, и спросить совета у знающих людей) о волшебных трех буквах (ICO) применительно к моему игровому проекту.

                    Немного освежим фактологию:
                    • Игра разрабатывается уже 10 месяцев
                    • Да, разработка началась с круглого нуля одним человеком на энтузиазме
                    • На данный момент сформировалась постоянная команда из 4 человек (3 кодера, из которых один является и геймдизайнером, плюс 3D-моделлер) + имеется ощутимая помощь от сообщества (еще некоторое количество человек, оказывавших и оказывающих существенную помощь кодом, моделями, ценными идеями)
                    • Игра приобрела концепцию и свою изюминку, а вместе с этим — и новое название: Privateers.Life (замахнувшись от вполне обычной выживалки на полноценный симулятор жизни в 17 веке)

                    Главная страница сайта игры Privateers.Life

                    Начнем с того, что идея ICO для игры витала в воздухе еще полгода назад. Хотя тогда я не знал, что она обретет воплощение именно в виде ICO, но мысль сделать игровую валюту осязаемой была уже тогда. А по мере развития игры, приближения к играбельной альфа-версии, а следовательно — перехода на, так сказать, новый уровень (мультиплеер), возникло понимание, что ICO в нашем случае это самый оптимальный вариант и с его помощью решаются сразу несколько вопросов.
                    1. Айсио позволяет получить экономически обоснованную игровую валюту (оборот койна не только внутри игры, но и на биржах, позволит придать валюте ценность, а значит в действие вступят законы рынка, что позволит избежать искусственных перекосов в ценообразовании на игровые предметы). Со стороны разработчика, может быть, и было бы выгоднее такие перекосы допускать (получая больше необоснованной прибыли с продажи переоцененных вещей за ничем не обеспеченные фантики, чем грешат многие игры), но это палка о двух концах — можно либо отпугнуть аудиторию, заигравшись с этими ценовыми перекосами, либо получить неприятные долговременные последствия, нарушающие игровой баланс в целом.
                    2. В результате Первичного Предложения Монет разработчик получает финансирование на продолжение работ над продуктом (в нашем случае — игрой).
                    3. Наличие у разработчика определенного количества монет после ICO позволяет получать финансирование на текущие нужды, без необходимости обращаться к заемному капиталу (просто продав некоторое количество монет по текущему рыночному курсу)


                    Так как проект изначально строился на энтузиазме, сначала моем, а со временем — и присоединившихся ко мне людей, у нас не было большого рекламного бюджета, чтобы громко заявить о себе (хотя, лично я считаю, что у нас больше оснований громко о себе заявлять, поскольку, в отличие от множества других ICO, мы, кроме концепции, имеем определенную подтвержденную историю разработки (канал на YouTube с более чем 1000 часов видео разработки в прямом эфире) и почти готовую альфа-версию игры), следовательно цели собрать миллионы долларов просто потому, что «мы такие красивые и перспективные» у нас не было. Первостепенная цель ICO — получить после него определенное количество монет с ценой, определяемой не нами, а рынком. И этой цели мы уже, как бы то ни было, почти достигли (продано около 300000 монет LDM, что, с учетом делимости токена (18 знаков после точки), более чем достаточно для запуска игровой экономики, осталось дождаться завершения ICO, зафиксировать итоговое выпущенное количество монет и добавить токен LDM (от лат. Ludum — Игровой) на биржи).

                    Однако, как в свое время и разработка игры, проведение ICO для меня было новым опытом, о котором я и хочу немного порассуждать, рассказать о совершенных ошибках/недочетах и спросить мнения знатоков (которые на Хабре однозначно есть, судя по широте освещения здесь темы ICO), что нами было сделано неправильно, что можно было бы исправить или улучшить.

                    Очень важный для меня инсайт произошел на мероприятии, проводимом в питерском #BlockchainHouse, где собрались организаторы и инвесторы в ICO, а так же представители инфраструктурных отраслевых проектов (того же ICOrating): мне предложили описать свой проект в паре предложений, а у меня это не получилось. Теперь мне понятно, что одной из главных моих ошибок было то, что я сам не представлял концепции игры, которую я создаю.

                    А, между тем, это очень важно — суметь объяснить потенциальным инвесторам в сжатой форме идею того, чем ты занимаешься. До того момента мне не составляло труда объяснить общую канву моей игры человеку, который хочет о ней послушать и располагает временем на мой пространный рассказ, но когда мне дали на это всего пару минут — я не справился. Однако уже к концу мероприятия я вычленил для себя основы и формулировка обрела четкие формы:

                    Мы делаем игру, в которой каждый сможет построить свой успешный бизнес на базе реальной криптовалюты, а заработанные в игре деньги тратить в реальной жизни. И все это — в пиратской атмосфере 17 века.


                    Да, в нашей игре мы предоставим возможность любому игроку построить свой бизнес. Причем — для этого не обязательно будет покупать игровую валюту (токены LDM), их можно будет заработать, например, выполнением контрактов от разработчика на создание предметов для игрового магазина (это могут быть как крупные объекты, вроде торговых или военных кораблей, так и компоненты, требующие намного меньших затрат времени, но позволяющие относительно быстро получить первый пусть и небольшой доход). Вы, как игрок, сможете открыть, например, бордель (какая пиратская колония без борделя?), работу которого вам нужно будет наладить (обеспечить снабжение расходными материалами, зарплату персонала, соцпакет ;), то есть кормить-лечить-защищать), вы однозначно будете нести расходы, а вот ваша прибыль будет зависеть от вашего подхода к своему предприятию (бордель на необитаемом острове будет, мягко говоря, убыточным, а вот в крупном городе — может приносить десятки и сотни процентов прибыли, в сравнении с которыми биржевой рост курса игровой валюты может померкнуть). Более расширено эти аспекты рассмотрены в Белой книге.

                    Значительным недочетом с нашей стороны было чрезмерное полагание на эффективность топика на bitcointalk. Да, так повелось, это можно даже считать своеобразным стандартом — наличие топика на Bitcointalk.org у проекта, выходящего на ICO, обязательно. Но его (и даже нескольких языковых версий в других разделах форума) недостаточно для охвата сколько-нибудь серьезной по размерам аудитории. А на профильные ICO-трекеры мы начали добавлять информацию вообще только перед самым началом продажи токенов, а то и уже после фактического начала ICO.

                    Немалое значение для успеха ICO имеет система бонусов (в сфере игровых ICO бонусы довольно существенны и могут составлять 50-100% за приобретаемые на определенных условиях монеты, а на баунти-кампанию может быть выделено 10-15% от всего объема выпускаемых токенов, интересно было проанализировать в этой связи опыт ICO околоигровых монет Nexium (игра Beyond the Void) и Rustbits (визуальная новелла SpacePirates). Подводный камень в том, что большие бонусы это гарантия слива купленных или полученных за Баунти токенов сразу же на первой бирже (часто ей становится EtherDelta, выход на которую — дело техники), следовательно с большим бонусом не стоит продавать большие объемы монет, так как даже продажа по цене ICO может быть чрезвычайно выгодной тем, кто получил монеты с 100%-ным бонусом, а «баунтисты» вообще особо не замечены в каком-то особом пиетете к проектам, в которых участвуют и могут стать именно тем спусковым крючком, вызывающим сильное проседание курса после ICO. Что касается нашего проекта, то нам, похоже, удалось найти ту золотую середину, когда инвесторы заходят с долгосрочными планами (даже с бонусом), а баунтисты не смогут оказать особого влияния на курс (в силу того, что на Баунти-программы нами выделено всего 5% монет). Во всяком случае, при личном общении обладатели сколь-нибдуь существенного количества токенов LDM настаивают на неспекулятивном интересе к нашему проекту.

                    Напоследок я хотел бы перечислить несколько моментов, на которые обязательно стоит обратить внимание тем, кто собирается проводить ICO своего продукта или компании, или, как минимум, стоит иметь в виду:
                    • Есть два глобальных варианта производства токенов: премайн (когда вся сумма создается заранее и потом распределяется, а непроданная часть сжигается) и производство токенов в момент покупки (соответствующая сумма токенов создается в момент поступления ETH на кошелек контракта и отправляется на адрес инвестора, в этом случае сжигать нечего, так как «непроданных» токенов не будет — они просто не будут созданы — но от вопросов вида «а что с непроданными токенами?» это вас все равно не избавит)
                    • Не премините воспользоваться удобным ENS-сервисом от MyEtherWallet.com и присвоить номеру кошелька вашего контракта короткое название, например — созвучное с именем домена. Мы о такой возможности узнали, когда в наших топиках появился мошенник,
                      подсуетившийся раньше нас (и присвоивший похожее на официальное название своему кошельку). В нашем случае это не принесло ущерба, потому что мы все связанные с деньгами процессы автоматизировали (токены создаются только в момент поступления ETH на кошелек смарт-контракта, указанный на сайте), телеграмм-чата где можно спамить кошельками у нас нет, а в Дискорде (аналог Slack) у нас сидят по большей части люди, следящие за разработкой игры, а не инвесторы-спекулянты. Но лучше это иметь ввиду и сделать самостоятельно, чем потом разбираться с возможными последствиями.
                    • Как и с токенами, с бонусами так же есть пара основных вариантов: бонусы за приобретаемую сумму (или в рамках определенного продаваемого объема) и в течение определенного времени, на мой взгляд, бонусы за сумму больше подходят проектам из реального сектора (да, такие тоже уже проводят ICO, и мне уже поступают вопросы от некоторых действующих офлайн-бизнесов с многолетней историей развития, раздумывающих о проведении Первичного Предложения Монет), а бонусы за время покупки — онлайн-проектам. И лучше бонусами не злоупотреблять — пока ICO не завершено и цена монеты не определена рынком, ваши койны не стоят ничего, а следовательно, более менее значимый объем рекламы за них вы не получите (скорее всего). А вот локальный слив получатели тех или иных бонусов после выхода уже на первую биржу вашему токену устроить при желании смогут.
                    Original source: habrahabr.ru (comments, light).

                    https://habrahabr.ru/post/337544/


                    Метки:  

                    [Из песочницы] Что «под капотом» у LinkedList?

                    Суббота, 09 Сентября 2017 г. 20:33 + в цитатник
                    AsteriskIT сегодня в 20:33 Разработка

                    Что «под капотом» у LinkedList?

                    Добрый день, хабрачитатели!

                    Как работает ArrayList, вполне понятно. Есть много статей по этому поводу, часть из них иллюстрированы замечательными картинками, так что даже новичкам становится сразу все ясно. К лучшим статьям на эту тему я отношу «Структуры данных в картинках. ArrayList», написанную tarzan82.

                    Этот же автор описывает принципы работы LinkedList, однако часть данных устарела еще с выходом Java 7, поэтому попытка детально разобраться, что происходит внутри этой коллекции, по рисункам tarzan82 уже сложно. Да и в других источниках я не встретила понятных картинок, потому и возникла идея написать эту статью.

                    Итак, LinkedList — класс, реализующий два интерфейса — List и Deque. Это обеспечивает возможность создания двунаправленной очереди из любых (в том числе и null) элементов. Каждый объект, помещенный в связанный список, является узлом (нодом). Каждый узел содержит элемент, ссылку на предыдущий и следующий узел. Фактически связанный список состоит из последовательности узлов, каждый из которых предназначен для хранения объекта определенного при создании типа.

                    Разберемся, что же происходит, когда мы пишем уже простые и привычные строки кода.

                    1. Создание связанного списка


                    LinkedList numbers = new LinkedList<>();

                    Данный код создает объект класса LinkedList и сохраняет его в ссылке numbers. Созданный объект предназначен для хранения целых чисел (Integer). Пока этот объект пуст.

                    Класс LinkedList содержит три поля:

                    // модификатор transient указывает на то, что данное свойство класса нельзя
                    // сериализировать 
                    transient int size = 0;
                    transient Node first;
                    transient Node last;



                    2. Добавление объекта в конец связанного списка


                    numbers.add(8);

                    Данный код добавляет число 8 в конец ранее созданного списка. Под «капотом» этот метод вызывает ряд других методов, обеспечивающих создание объекта типа Integer, создание нового узла, установку объекта класса Integer в поле item этого узла, добавление узла в конец списка и установку ссылок на соседние узлы.

                    Для установки ссылок на предыдущий и следующий элементы LinkedList использует объекты своего вложенного класса Node:

                    private static class Node {
                        E item;
                        Node next;
                        Node prev;
                    
                        Node(Node prev, E element, Node next) {
                            this.item = element;
                            this.next = next;
                            this.prev = prev;
                        }
                    }

                    При каждом добавлении объекта в список создается один новый узел, а также изменяются значения полей связанного списка (size, first, last).

                    В случае с добавлением первого элемента создается узел, у которого предыдущий и следующий элементы отсутствуют, т.е. являются null, размер коллекции увеличивается на 1, а созданный узел устанавливается как первый и последний элемент коллекции.

                    Добавим еще один элемент в нашу коллекцию:

                    numbers.add(5);

                    Сначала создается узел для нового элемента (число 5) и устанавливается ссылка на существующий элемент (узел с числом 8) коллекции как на предыдущий, а следующим элементом у созданного узла остается null. Также этот новый узел сохраняется в переменную связанного списка last:

                    Как можно увидеть на рис. 4, первый элемент коллекции (под индексом 0) пока ссылается на null как на следующий элемент. Теперь эта ссылка заменяется и первый элемент начинает ссылаться на второй элемент коллекции (под индексом 1), а также увеличивается размер коллекции:

                    3. Добавление объекта в середину связанного списка


                    numbers.add(1, 13);

                    LinkedList позволяет добавить элемент в середину списка. Для этого используется метод add(index, element), где index — это место в списке, куда будет вставлен элемент element.

                    Как и метод add(element), данный метод вызывает несколько других методов. Сначала осуществляется проверка значения index, которое должно быть положительным числом, меньшим или равным размеру списка. Если index не удовлетворит этим условиям, то будет сгенирировано исключение IndexOutOfBoundsException.

                    Затем, если index равен размеру коллеции, то осуществляются действия, описанные в п. 2, так как фактически необходимо вставить элемент в конец существующего списка.

                    Если же index не равен size списка, то осуществляется вставка перед элементом, который до этой вставки имеет заданный индекс, т.е. в данном случае перед узлом со значением 5.

                    Для начала с помощью метода node(index) определяется узел, находящийся в данный момент под индексом, под который нам необходимо вставить новый узел. Поиск данного узла осуществляется с помощью простого цикла for по половине списка (в зависимости от значения индекса — либо с начала до элемента, либо с конца до элемента). Далее создается узел для нового элемента (число 13), ссылка на предыдущий элемент устанавливается на узел, в котором элементом является число 8, а ссылка на следующий элемент устанавливается на узел, в котором элементом является число 5. Ссылки ранее существующих узлов пока не изменены:

                    Теперь последовательно заменяются ссылки: для элемента, следующего за новым элементом, заменяется ссылка на предыдущий элемент (теперь она указывает на узел со значением 13), для предшествующего новому элементу заменяется ссылка на следующий элемент (теперь она указывает на узел со значением 5). И в последнюю очередь увеличивается размер списка:

                    4. Удаление объекта из списка


                    Для удаления одного элемента из списка класс LinkedList предлагает нам аж 10 методов, различающихся по типу возвращаемого значения, наличию или отсутствию выбрасываемых исключений, а также способу указания, какой именно элемент следует удалить:

                    Рассмотрим удаление элемента из связанного списка по его значению. Удалим элемент со значением 5 из нижепредставленного списка:

                    numbers.remove(Integer.valueOf(5));

                    Обратите внимание, что принимаемым значением в методе remove(object) является именно объект, если же мы попытаемся удалить элемент со значением 5 следующей строкой
                    numbers.remove(5);

                    то получим IndexOutOfBoundsException, т.к. компиллятор воспримет число 5 как индекс и вызовет метод remove(index).

                    Итак, что же происходит при вызове метода remove(object)? Сначала искомый объект сравнивается по порядку со всеми элементами, сохраненными в узлах списка, начиная с нулевого узла. Когда найден узел, элемент которого равен искомому объекту, первым делом элемент сохраняется в отдельной переменной. Потом переопределяются ссылки соседних узлов так, чтобы они указывали друг на друга:

                    Затем обнуляется значение узла, который содержит удаляемый объект, а также уменьшается размер коллекции:

                    Теперь вернемся к тому моменту, что элемент из удаляемого узла мы сохраняли в памяти. Зачем мы это делали, спросите вы, если эти данные мы нигде дальше не использовали. Дело в том, что рассматриваемый нами метод в результате своей работу не возвращает удаленный элемент, потому данные, возврещенные вызванным в рамках работы метода unlink(node), вызванного методом remove(object), просто не понадобились. А вот когда мы используем метод remove(index), также вызывающий метод unlink(node), то значение данного элемента последовательно возвращается сначала методом unlink(node), а затем и методом remove(index). Похожая ситуация наблюдается и в остальных методах, возвращающих значение удаленного элемента, только внутри вызываются другие методы, отсоединяющие ссылку: в методах poll(), pollFirst(), remove() и removeFirst() это метод unlinkFirst(node), а в методах pollLast() и removeLast() — метод unlinkLast(node).

                    Итак, что следует помнить о LinkedList, решая, использовать ли данную коллекцию:

                    • не синхронизирована;
                    • позволяет хранить любые объекты, в том числе null и повторяющиеся;
                    • за константное время O(1) выполняются операции вставки и удаления первого и последнего элемента, операции вставки и удаления элемента из середины списка (не учитывая время поиска позиции вставки элемента);
                    • за линейное время O(n) выполняются операции поиска элемента по индексу и по значению.

                    Ссылки


                    Исходный код LinkedList (JDK 8)
                    Документация LinkedList
                    Original source: habrahabr.ru (comments, light).

                    https://habrahabr.ru/post/337558/


                    Метки:  

                    [Из песочницы] Путь джедая. От мелкого юзера до сотрудника IT-компании

                    Суббота, 09 Сентября 2017 г. 19:46 + в цитатник
                    FaMon сегодня в 19:46 Разное

                    Путь джедая. От мелкого юзера до сотрудника IT-компании

                    Всем привет!

                    Меня зовут Андрей. Я сотрудник одной из IT-компаний, а если быть точнее, я — QA. Вот недавно задумался, как я дошел до этого. После прокрутки данного периода из своей жизни, мне захотелось поделиться с «обществом» своей историей. Возможно, это когда-то кому-то чем-то поможет. Приступим.

                    Все началось с далеких 2000-х. В это время компьютеры разрастались далеко за рамки компьютерных клубов и начинали появляться почти в каждом доме. Ну и конечно сие творение появилось у меня. Будучи подростком, мой интерес начался конечно же с компьютерных игр. Играл я много и по долгу. Но со временем, я осознал для себя, что мне хочется сделать на компьютере что-то свое и поделиться этим с людьми, а не тупо рубиться с неизвестными мне людьми по сети, зарабатывая уровни и виртуальные суммы.

                    Первое, что пришло мне на ум — создать собственный сайт. Я не знал ничего в этой теме и уж тем более, я не мог определиться с тематикой. HTML, CSS, Javascript и т.п. все это вызывало ужас и в тоже время — глубокое желание обучиться хотя бы азам, да бы не быть таким жестким профаном. Конечно, логичнее всего было бы начать с книг, постепенно переводя прочитанное в сделанное, но читать мне было лень. В итоге, я напоролся на конструкторы сайтов, которые обещали мне мой собственный сайт без напряга и изучения матчасти.

                    Выбрал я тогда ucoz, почему-то на тот момент он показался мне более легким в «управлении». Помимо самой идеи создания сайта, я хотел начать зарабатывать хотя бы какие-то минимальные деньги. Отсюда родилась тематика сайта — заработок в интернете. Родители воспитали меня хорошо, поэтому обмана для своих читателей я не хотел. Я пошел лазить по интернету, пытаясь заработать деньги для себя. Решил побыть пушечным мясом и ощутить всю боль кидалова. Если мне удавалось деньги выводить, я писал статьи о том или ином ресурсе и рассказывал людям как на них зарабатывать. А кидалы шли в топку.

                    В процессе написание статей в конструкторе, я потихоньку начал осваивать HTML и CSS, но их на тот момент я создавал в визуальном редакторе, что порождало кучу лишнего кода в статье. Если я открывал исходный код, то просто сталкивался с дремучей массой нечитабельного контента. Это пришлось в итоге исправлять и причесывать свои «текста», но это уже отдельная тема.

                    После наполнения своего сайта статьями, я уже более-менее плавал в HTML и CSS. Начитавшись кучу негативных отзывов о своем Ucoz'e я решил попробовать другие конструкторы сайтов. В итоге, через год я уже побывал в wordpress, joomla, nethouse, blogspot и еще нескольких малоизвестных систем, позволявших мне быстро клепать сайты.

                    Период, от моих первых попыток по созданию сайта и до опыта в большом количестве CMS, занял где-то 2,5 года. Много? Наверное да, можно было и за 0,5-1 год управиться, но учеба и личная жизнь не давали мне так много времени на мое хобби.

                    Что было дальше? Я устроился по специальности на работу в одну из обычных серых компаний-однодневок, которые пытались выжить любой ценой среди кучи им же подобных. Несмотря на то, что мое образование было далеко от компьютерной тематики, я по зову сердца, помимо профильной работы, предложил свои услуги коллегам по созданию сайта. В итоге, я создал сайт для этой компании на wordpress и был он в итоге в ТОП-1 по важным для компании запросам.

                    Забыл сказать — я жуткий педант. Это и только это в последствии повлияло на мой выбор. Какой? Кем же мне в итоге быть… Так как от моей специальности меня «воротило», только компьютер и сайты давали мне радость и даже некую гордость что ли. Исходя из абзаца выше, я должен был бы стать верстаком. Но судьба распорядилась иначе и правильные знакомства указали мне возможность стать QA-Инженером, чем я и воспользовался.

                    Пол года я самостоятельно обучался в свободное от работы время. Автобусы, метро, парки, скучные однобокие вечера — я обучался везде, где только мог. И в итоге, я слепил резюме и пошел хантить свою первую IT-вакансию — Junior QA Engineer. Возможно это был знак свыше, но работу я нашел в первую неделю поисков. Меня взяли джуном на живучий стартап, который нуждался в рутинных проверках сайта тестировщиком.

                    После года работы я решил двигаться дальше и стал самостоятельно изучать Java на одном классном ресурсе (я думаю вы все его знаете, но не буду делать рекламу). Добавил к этому десятки видеоуроков и вуаля — через год дополнительного обучения и я научился автоматизации. Стартанул с Selenium IDE и закончил на Java+Selenium со своим собственным фреймворком.

                    Сейчас я уже почти 5 лет, как работаю QA и не жалею об этом. Не то, чтобы я гордился тем, кто я сейчас. Если честно, я просто рад, что нашел ту работу, от которой мне не хочется каждый день и все выходные отдыхать перед телевизором с бутылкой пива. Меня не воротит от моей работы. И я благодарен всем людям, которые помогали мне и подсказывали правильные вещи в нужное время.

                    Вот и все. Я рассказал о части своего жизненного пути и продемонстрировал, как мне кажется, одну очень важную вещь — если искать что-то конкретное и сильно этого хотеть, то жизнь сама вам это даст. Никогда не опускайте руки и делайте то, во что влюблены. Так вы всегда добьетесь успеха и будете счастливы.

                    P.S.: если кому-то нужна инфа по видеоуроками или еще чему-то — пишите, возможно я смогу помочь.
                    Original source: habrahabr.ru (comments, light).

                    https://habrahabr.ru/post/337554/


                    Метки:  

                    Learnopengl. Урок 2.4 — Текстурные карты

                    Суббота, 09 Сентября 2017 г. 18:07 + в цитатник
                    eanmos сегодня в 18:07 Разработка

                    Learnopengl. Урок 2.4 — Текстурные карты

                    • Tutorial

                    image


                    Ранее мы обсуждали возможность каждого объекта иметь уникальный материал, чтобы по-разному реагировать на свет. Это отлично подходит для того, чтобы придать каждому объекту уникальный вид относительно других объектов на сцене. Но этого все еще не дает нам большой гибкости в настройке внешнего вида объекта.





                    Текстурные карты


                    В предыдущем уроке мы определили материал для целого объекта, но в реальном мире объекты обычно состоят не из одного, а из нескольких материалов. Представьте себе машину: ее внешний корпус блестящий; окна частично отражают окружающую среду; у машины также есть матовые покрышки, а еще есть сверкающие обода (сверкающие, если вы хорошо моете свою машину). Так вот, каждый объект имеет разные свойства материала для каждой своей части.


                    Итак, наша система материалов из предыдущего урока не подходит для более или менее сложных объектов, по этому нам нужно расширить ее, введя диффузную и бликовую карты. Это даст нам возможность влиять на диффузный (и, косвенным образом, на фоновый, так как это почти всегда одно и то же) и бликовый компоненты объекта с большей точностью.


                    Диффузные карты


                    Все, что нам нужно — это способ установить диффузный цвет для каждого фрагмента объекта. Что может повлиять на значение цвета, основываясь на позиции фрагмента?


                    Припоминаете? Это — текстуры, которые мы интенсивно обсуждали в одном из предыдущих уроков. Карты освещения – всего лишь другое название того же принципа: используя изображение, нанесенное на поверхность объекта, мы можем делать выборки цвета для каждого фрагмента. В сценах с освещением это обычно называется диффузной картой (как правило ее так называют 3D художники), так как текстурное изображение представляет все диффузные цвета объекта.


                    Для демонстрации диффузных карт мы будем использовать изображение деревянного контейнера с железной рамкой:



                    Использование диффузных карт в шейдерах очень похоже на использование текстур в одном из предыдущих уроков. Однако теперь мы заменим ранее определенный вектор vec3 диффузного цвета диффузной картой sampler2D.


                    Имейте в виду, что sampler2D это, так называемый, непрозрачный тип данных. Это значит, что мы не можем создать экземпляр такого типа, мы можем только определить его как uniform. Если мы попытаемся использовать этот тип не как uniform (например как параметр функции), то GLSL выведет странные ошибки. Это правило также распространяется и на любую структуру, которая содержит непрозрачный тип.



                    Мы также удалили вектор ambient, так как он в большинстве случаев совпадает с диффузным цветом, так что нам не нужно хранить его отдельно:


                    struct Material {
                        sampler2D diffuse;
                        vec3      specular;
                        float     shininess;
                    }; 
                    ...
                    in vec2 TexCoords;
                    

                    Если вы такой упрямый и все еще хотите установить фоновый цвет отличным от диффузного, то можете оставить вектор ambient. Но фоновый цвет будет один для всего объекта. Чтобы получить различные значения фонового цвета для каждого фрагмента объекта, вам нужно использовать отдельную текстуру для значений фонового цвета.



                    Обратите внимание, что нам снова нужны текстурные координаты во фрагментном шейдере, по этому мы объявляем дополнительную входящую переменную. Затем мы просто делаем выборку из текстуры, чтобы извлечь значение диффузного цвета фрагмента:


                    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));  
                    

                    Также не забудьте установить фоновый цвет материала таким же, как и диффузный:


                    vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
                    

                    Это все, что нам нужно для использования диффузной карты. Как вы могли заметить, в этом нет ничего нового, но это дает впечатляющий рост визуального качества. Чтобы это заработало, нам нужно добавить текстурные координаты к данным вершин и передать их в качестве вершинного атрибута во фрагментный шейдер, загрузить текстуру и связать ее с соответствующим текстурным блоком.


                    Обновленные вершинные данные можно найти здесь. Теперь они включают в себя позиции вершин, векторы нормалей и текстурные координаты для каждой вершины куба. Давайте обновим вершинный шейдер, чтобы он мог принимать текстурные координаты, как вершинный атрибут и передавать их во фрагментный шейдер:


                    #version 330 core
                    layout (location = 0) in vec3 aPos;
                    layout (location = 1) in vec3 aNormal;
                    layout (location = 2) in vec2 aTexCoords;
                    ...
                    out vec2 TexCoords;
                    
                    void main()
                    {
                        ...
                        TexCoords = aTexCoords;
                    }  
                    

                    Удостоверьтесь, что обновили вершинные атрибуты обоих VAO (прим. переводчика: имеются ввиду VAO текстурированного куба и VAO куба-лампы), так чтобы они совпадали с новыми вершинными данными, и загрузили изображение контейнера в текстуру. Перед тем, как нарисовать контейнер нам нужно присвоить переменной material.diffuse предпочтительный текстурный блок и связать с ним текстуру контейнера:


                    lightingShader.setInt("material.diffuse", 0);
                    ...
                    glActiveTexture(GL_TEXTURE0);
                    glBindTexture(GL_TEXTURE_2D, diffuseMap);
                    

                    Используя диффузную карту, мы снова получили огромный прирост детализации и теперь, с добавленным освещением, наш контейнер действительно начал блистать (в буквальном смысле). Вероятно, теперь он выглядит вот так:



                    Вы можете найти полный исходный код приложения здесь.


                    Бликовые карты


                    Вероятно, вы заметили, что блик выглядит немного странно, ведь наш объект — это контейнер, который большей частью состоит из дерева. А, как мы знаем, дерево не дает такого зеркального блеска. Мы можем исправить это, установив вектор specular в структуре Material равным vec3(0.0), но это значит, что железная рамка контейнера тоже перестанет давать блики, а мы знаем, что металл должен, хотя бы немного блестеть. И снова мы хотели бы контролировать, какие части объекта должны блестеть и с какой силой. Эта проблема очень похожа на обсуждение диффузных карт. Совпадение? Не думаю.


                    Мы снова можем воспользоваться текстурной картой, только теперь для зеркальных бликов. Это значит, что нам нужно создать черно-белую (или цветную, если хотите) текстуру, которая определит силу блеска каждой части объекта. Вот пример бликовой карты:



                    Интенсивность блеска определяется яркостью каждого пикселя изображения. Каждый пиксель такой карты может быть представлен как цветовой вектор, где черный цвет — это vec3(0.0), а серый — vec3(0.5), например. Затем, во фрагментном шейдере мы отбираем соответствующее цветовое значение и умножаем его на интенсивность бликового цвета. Соответственно, чем "белее" пиксель, тем больше получается результат умножения, а, следовательно, и яркость блика на фрагменте объекта.


                    Так как контейнер большей частью состоит из дерева, а дерево — это материал, который не дает бликов, то вся "деревянная" часть текстуры закрашена черным. Черные части вообще не блестят. Поверхность стальной рамки контейнера имеет переменную силу зеркального блеска: сама сталь дает довольно интенсивные блики, в то время как трещины и потертости – нет.


                    Технически, дерево тоже имеет зеркальные отражения, хотя и с намного более низкой силой блеска (свет сильнее рассеивается), но в образовательных целях, мы сделаем вид, что дерево никак не реагирует на зеркальный свет.



                    Используя инструменты, такие как Photoshop или Gimp, довольно просто превратить диффузную текстуру в бликовую. Достаточно просто вырезать некоторые части, сделать изображение черно-белым и увеличить яркость/контрастность.


                    Сэмплинг бликовых карт


                    Карта зеркальных отражений — это самая обычная текстура, по этому код ее загрузки очень похож на загрузку диффузной карты. Удостоверьтесь, что вы правильно загрузили изображение и сгенерировали текстурный объект. Так как мы используем новую текстуру в том же фрагментом шейдере, нам нужно использовать другой текстурный блок для карты бликов. Давайте свяжем эту текстуру с соответствующим текстурным блоком перед визуализацией:


                    lightingShader.setInt("material.specular", 1);
                    ...
                    glActiveTexture(GL_TEXTURE1);
                    glBindTexture(GL_TEXTURE_2D, specularMap); 
                    

                    Затем обновим свойства материала во фрагментном шейдере, чтобы отражающий компонент принимался в качестве sampler2D, а не vec3:


                    struct Material {
                        sampler2D diffuse;
                        sampler2D specular;
                        float     shininess;
                    };  
                    

                    И, наконец, нам нужно провести выборку карты бликов, чтобы получить соответствующую интенсивность блика для каждого фрагмента объекта:


                    vec3 ambient  = light.ambient  * vec3(texture(material.diffuse, TexCoords));
                    vec3 diffuse  = light.diffuse  * diff * vec3(texture(material.diffuse, TexCoords));  
                    vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
                    FragColor = vec4(ambient + diffuse + specular, 1.0);   
                    

                    Используя карту бликов, мы можем с чрезвычайной точностью определить, какие части объекта дают зеркальные блики, и установить соответствующую их интенсивность. Таким образом, карта бликов дает нам дополнительный уровень контроля поверх диффузной карты.


                    Вы также можете использовать в бликовой карте цвета, которые определяют не только интенсивность блика, но и его цвет. Однако в реальности, цвет блика в значительной степени (а в большинстве случаев и полностью) зависит от источника света, поэтому использование цветных карт бликов не даст реалистичных результатов (вот почему эти изображения обычно черно-белые — нас интересует только интенсивность блика).



                    Если вы запустите приложение, то увидите, что материал контейнера очень похож, на деревянный контейнер с железной рамкой:



                    Вы можете найти полный исходный код приложения здесь.


                    Используя диффузные и бликовые карты, мы можем добавить огромное количество деталей в относительно простые объекты. Мы можем добавить еще больше деталей, используя другие текстурные карты, такие как карты нормалей/рельефа и/или карты отражений, но их мы прибережем для следующих уроков. Покажите ваш контейнер друзьям и семье и помните, что однажды наш контейнер может стать еще более привлекательным, чем сейчас!


                    Упражнения


                    • Попробуйте поиграться с фоновым, диффузным и бликовым векторами источника света и посмотрите, как они влияют на внешний вид объекта.
                    • Попробуйте инвертировать цвета бликовой карты во фрагментном шейдере, т.е. дерево должно блестеть, а железная рамка — нет (заметьте, что трещины на рамке все еще дают зеркальное отражение, хотя и с меньшей интенсивностью): решение.
                    • Попробуйте создать карту бликов из диффузной карты, которая использует не черно-белые цвета, и вы увидите, что результат не очень реалистичный. Вы можете использовать эту цветную карту бликов, если не можете сделать ее сами. Результат.
                    • Вы также можете попробовать добавить к нашему кубу карту эмиссии, которая хранит значение свечения каждого фрагмента объекта. Значения эмиссии — это цвета, которые объект может излучать, как если бы он содержал источник света. Таким образом, объект может светиться независимо от условий освещения. Обычно карты свечения можно увидеть на тех игровых объектах, которые светятся (например глаза робота или полосы света на контейнере). Добавьте эту текстуру (от creativesam) на наш контейнер в качестве карты свечений, так, чтобы буквы излучали свет. Решение, результат.
                    Original source: habrahabr.ru (comments, light).

                    https://habrahabr.ru/post/337550/


                    Метки:  

                    Поиск сообщений в rss_rss_hh_new
                    Страницы: 1437 ... 1136 1135 [1134] 1133 1132 ..
                    .. 1 Календарь