博客

  • 阿里云ubuntu系统升级到16.04

    之前一直使用的是阿里云上ubuntu 12.04系统,为了使用docker方便有过一次升级,不过那个时候升级比较粗暴,我直接更新了源,然后用apt-get upgrade升的级。

    今天想升级到16.04才突然发现了doreleaseupgrade,整个过程轻松加愉快。

    直接运行

    sudo do-release-upgrade -d

    升级之后检查一下

    lsb_release -a

    upgrade-ubuntu

  • Nginx配置所有访问导向同一页面

    最近由于各种原因,需要将之前的一个网站关闭。

    关闭之后因为域名没有到期,所以还是希望能够有一个静态页面让平时的常客至少下载一下存档,所以就做了一个静态网站。

    因为静态网站只有一个页面和一个zip存档,所以需要在nginx中将所有请求都转发到这个页面上。

    配置如下:

    location / {
        try_files $uri /index.html;
    }

    这样的话默认请求存在的文件的时候就会直接访问这些文件,否则就会跳转回首页。

  • 使用Rultor简化开发

    通常一个项目,特别是开源项目有很多常见的操作,比如merge一个pull request或者发布一个版本。

    merge一个pull request可以通过github的issue页面完成,但是发布版本通常需要一些准备,特别是证书等等。

    Rultor是一个辅助工具,可以完成合并,发布等功能。

    Rultor的配置是.rultor.yml,这是一个例子

    decrypt:
      settings.xml: "repo/settings.xml.asc"
      pubring.gpg: "repo/pubring.gpg.asc"
      secring.gpg: "repo/secring.gpg.asc"
    docker:
      image: "yegor256/java8" 
    release:
      script: |
        mvn versions:set "-DnewVersion=${tag}"
        git commit -am "${tag}"
        mvn clean deploy -P central-deploy --settings /home/r/settings.xml -P cxf-spring-boot-starter-maven-plugin

    使用的时候很简单,比如需要发布新版本,直接创建一个issue,在其中@tultor即可

    @rultor release, tag is 1.0.0.RELEASE
    

    一个参考效果如下

    tultor-release

    参考文档

    http://www.rultor.com/

  • 使用Jenkins Groovy脚本添加Slave

    Jenkins应该是最常用的CI/CD工具了,而Jenkins也有官方的Docker版本,可以很方便的获得一个可用的Jenkins。

    但是大部分时候我们期待的是从Docker启动Jenkins之后一切环境都是准备就绪的,比如对应的插件啥的。

    对于插件来说还比较简单,直接提供一个plugin.txt文件即可

    credentials:1.18
    maven-plugin:2.7.1
    

    Jenkins的任务是可以在master直接运行的,但是很多时候,特别会需要很多不同构建环境的时候我们就会需要多个不同的Jenkins Slave了。

    Docker-Compose可以做到启动多个容器,这样我们就可以直接在其中就行配置了,比如一个java7的Slave,一个java8的,再来一个node的,但是最后还是需要到jenkins中添加这些Slave。

    Jenkins提供了Groovy脚本来实现一些配置和功能,比如需要修改执行器数量(默认为2),可以使用一些

    import jenkins.model.*
    Jenkins.instance.setNumExecutors(5)

    对于添加Slave的脚本就稍微复杂一些,需要用到import hudson.plugins.sshslaves.*

    大概的脚本如下

    List<Entry> env = new ArrayList<Entry>();
      EnvironmentVariablesNodeProperty envPro = new EnvironmentVariablesNodeProperty(env);
      Slave slave = new DumbSlave(
                        "java-7-node","",
                        "/var/jenkins_home",
                        "1",
                        Node.Mode.NORMAL,
                        "agent-node-label",
                        new SSHLauncher("java-7-node",22,"jenkins","password","","","","",""),
                        new RetentionStrategy.Always(),
                        new LinkedList())
      slave.getNodeProperties().add(envPro)
      Jenkins.instance.addNode(slave)

    然后将这个脚本拷贝到init.groovy.d目录

    FROM jenkins
    COPY slaves.groovy /usr/share/jenkins/ref/init.groovy.d/slaves.groovy

    参考

    https://hub.docker.com/_/jenkins/

  • libGDX中的自动寻路

    Gdx-ai提供了常见的AI算法,当然也包括寻路算法,比如常用的A*。

    Gdx-ai自身的API抽象也很易懂,也方便自定义寻路。

    首先由Graph和Connection提供图中的连通性和对应连接的开销。 简单一点直接用DefaultConnection,它提供的默认开销为1。

    而PathFinder就是具体执行寻路的类,而返回的结果是GraphPath,它包含了路的结果,对应的连接还有其他一些数据。

    这是Meritxell Calvo Palanques画的示意图

    grafos

     

    另外就是Heuristic,它并不是必须的,但是一个有效的Heuristic函数可以加快搜索,不过不方便提供,或者不想提供,最坏的结果无非就是退化为Dijkstra。(我提供的例子会直接返回一个错误的启发函数,不过依然可以获得结果)。

    例子就简单一点吧,一般地图的数据是从Tiled地图或者其他格式的文件中获得的,这里我直接用硬编码来。

    这个图只有四个点,其中点1,2,3相互连接,点3连接点4,我们要寻找从点1到点4的路径。

    首先表示这个图

    IndexedGraph<Node> graph = new IndexedGraph<Node>() {
                @Override
                public Array<Connection<Node>> getConnections(Node fromNode) {
                    Array<Connection<Node>> connections = new Array<Connection<Node>>();
                    switch (fromNode.getId()) {
                        case 1:
                            connections.add(new DefaultConnection<Node>(n1, n2));
                            connections.add(new DefaultConnection<Node>(n1, n3));
                            break;
                        case 2:
                            connections.add(new DefaultConnection<Node>(n2, n1));
                            connections.add(new DefaultConnection<Node>(n2, n3));
                            break;
                        case 3:
                            connections.add(new DefaultConnection<Node>(n3, n4));
                            connections.add(new DefaultConnection<Node>(n3, n1));
                            break;
                        default:
                            connections.add(new DefaultConnection<Node>(n4, n3));
                    }
                    return connections;
                }
    
                @Override
                public int getIndex(Node node) {
                    return node.getId() - 1;
                }
    
                @Override
                public int getNodeCount() {
                    return 4;
                }
            };

    然后使用A*寻路并提供一个随机的启发函数

    IndexedAStarPathFinder<Node> pathFinder = new IndexedAStarPathFinder<Node>(graph);
            GraphPath<Connection<Node>> out = new DefaultGraphPath<Connection<Node>>();
            final RandomXS128 randomXS128 = new RandomXS128();
            pathFinder.searchConnectionPath(n1, n4, new Heuristic<Node>() {
                @Override
                public float estimate(Node node, Node endNode) {
                    return randomXS128.nextFloat();
                }
            }, out);

    最后从GraphPath<Connection<Node>> out获取数据即可

    a-star-path-finder

  • libGDX中的状态机实现

    LIbgdx旗下的gdx-ai库提供了大量使用的AI算法,其中包括了最常见的有限状态机FSM。

    gdx-ai是一个普通的java库,并不存在平台相关依赖,所以直接在core中引入就可。

    dependencies {
            compile "com.badlogicgames.gdx:gdx-ai:1.7.0"
        }

    StateMachine是有限状态机的接口,对应的实现目前有两个DefaultStateMachine和StackStateMachine。

    StackStateMachine以栈的形式保存状态,所以可以提供了方法获取上一个状态,一般使用DefaultStateMachine就行了。

    FSM的例子很多,我们直接来一个简单的。

    首先主体对象是人,而人只有两个状态,一个走路,一个打伞。

    状态切换的条件是时间,也就是假定下雨的时间符合一定的条件。

    首先创建一个Person类

    public class Person extends Image {
        private TextureRegion walk;
        private TextureRegion rain;
        private StateMachine<Person, PersonState> stateMachine;
        private float time;
    
        public Person(final TextureRegion walk, final TextureRegion rain) {
            super(walk);
            this.walk = walk;
            this.rain = rain;
            stateMachine = new DefaultStateMachine<Person, PersonState>(this, PersonState.WALK);
        }
    
        public void showRain() {
            setDrawable(new TextureRegionDrawable(rain));
            setSize(getPrefWidth(), getPrefHeight());
        }
    
        public void showWalk() {
            setDrawable(new TextureRegionDrawable(walk));
            setSize(getPrefWidth(), getPrefHeight());
        }
    
        public void update(float delta) {
            time += delta;
            stateMachine.update();
        }
    }
    

    当初始化时默认状态是没有下雨了,而界面的表现就是显示不同的图片

    walkrain

    而time就是总共经历的时间

    在最关键状态处理逻辑,包括进入,更新,退出,接受消息,是放在对应的State中的。这里我们不考虑接受消息的情况,所以永远返回false。

    public enum PersonState implements State<Person> {
        WALK {
            @Override
            public void enter(Person entity) {
                entity.showWalk();
            }
    
            @Override
            public void update(Person entity) {
                float v = entity.getTime() % 10;
                if (v >= 6 || v <= 4) {
                    entity.getStateMachine().changeState(PersonState.RUN);
                }
            }
    
            @Override
            public void exit(Person entity) {
            }
    
            @Override
            public boolean onMessage(Person entity, Telegram telegram) {
                return false;
            }
        },
        RUN {
            @Override
            public void enter(Person entity) {
                entity.showRain();
            }
    
            @Override
            public void update(Person entity) {
                float v = entity.getTime() % 10;
                if (v < 6 && v > 4) {
                    entity.getStateMachine().changeState(PersonState.WALK);
                }
            }
    
            @Override
            public void exit(Person entity) {
            }
    
            @Override
            public boolean onMessage(Person entity, Telegram telegram) {
                return false;
            }
        }
    }
    

    当在update中切换状态时,会自动触发对应的进入(enter)事件,所以也不需要额外的处理。

    切换状态时的对应代码实现

    @Override
    	public void changeState (S newState) {
    		// Keep a record of the previous state
    		previousState = currentState;
    
    		// Call the exit method of the existing state
    		if (currentState != null) currentState.exit(owner);
    
    		// Change state to the new state
    		currentState = newState;
    
    		// Call the entry method of the new state
    		if (currentState != null) currentState.enter(owner);
    	}

    参考资料:

    https://github.com/libgdx/gdx-ai/wiki/State-Machine

  • 使用AnnotationAssetManager管理Libgdx中的资源

    Libgdx对于资源管理提供了AssetManager来做资源的管理,如果你需要使用它那么你就不得不使用这样的代码

    manager.load("data/mytexture.png", Texture.class);
    manager.load("data/myfont.fnt", BitmapFont.class);
    manager.load("data/mymusic.ogg", Music.class);

    其中的文件路径是字符串。然后在你使用的地方

    Texture tex = manager.get("data/mytexture.png", Texture.class);
    BitmapFont font = manager.get("data/myfont.fnt", BitmapFont.class);

    这样使用其实蛮不方便的,如果你把文件路径作为一个全局变量的话,其实很多代码是重复的。

    而AnnotationAssetManager提供了反射的方法来完成这个工作

    package com.huangyunkun.hundred;
    
    import com.badlogic.gdx.graphics.Texture;
    import net.dermetfan.gdx.assets.AnnotationAssetManager;
    
    public class Assets {
        @AnnotationAssetManager.Asset(Texture.class)
        public static final String ball = "ball.png";
    }

    比如有一个球的图片,那么只需要声明一个字符串。然后加载的时候直接传入这个类本身,由加载器自己去寻找所有需要加载的资源即可。

    public class QuickStartScreen extends ScreenAdapter {
        private Stage stage;
        private AnnotationAssetManager annotationAssetManager;
    
        public QuickStartScreen() {
            this.annotationAssetManager = new AnnotationAssetManager();
            this.stage = new Stage(new StretchViewport(960, 480));
            this.annotationAssetManager.load(Assets.class);
            this.annotationAssetManager.finishLoading();
            this.stage.addActor(new Image(this.annotationAssetManager.get(Assets.ball, Texture.class)));
        }
    
        @Override
        public void render(float delta) {
            GdxUtilities.clearScreen();
            stage.draw();
        }
    }

    Asset类本身还可以根据需要拆成不同的小类。

    而@Asset注解还支持参数,比如

    @Asset(value = Texture.class, params = "textureParams")
    public static final String grass = "img/grass.png";

     

  • Apt的mirrors模式

    之前用的ubuntu一直配置的上海交通大学的源,速度也不错,最近升级到16.04以后又准备继续使用,突然发现可以直接使用mirrors自动选择最合适的源。

    默认的可用的列表文件在这里 http://mirrors.ubuntu.com/mirrors.txt

    在source.list直接使用

    deb mirror://mirrors.ubuntu.com/mirrors.txt xenial main restricted universe multiverse
    deb mirror://mirrors.ubuntu.com/mirrors.txt xenial-updates main restricted universe multiverse
    deb mirror://mirrors.ubuntu.com/mirrors.txt xenial-backports main restricted universe multiverse
    deb mirror://mirrors.ubuntu.com/mirrors.txt xenial-security main restricted universe multiverse
  • Windows系统下classpath过长导致的bootRun不能运行

    Spring Boot提供了一个很便利的命令bootRun来快速启动应用,项目中的依赖会在启动时作为classpath的一部分传递给java,但是windows系统下有一个问题,如果classpath很长的时候就会直接报错,无法运行。但是如果你生成jar文件,再运行jar文件本身就不会遇到这个问题。

    bootRun是一个很便利的功能,自然我们不希望这个功能无法使用,有个取巧的办法。

    首先生成一个jar,这个jar文件的manifest中包含了所有依赖,然后bootRun直接依赖这个jar。

    task pathingJar(type: Jar) {
        dependsOn configurations.runtime
        appendix = 'pathing'
     
        doFirst {
            manifest {
                attributes "Class-Path": configurations.runtime.files.collect {
                    it.toURL().toString().replaceFirst(/file:/+/, '/')
                }.join(' ')
            }
        }
    }
     
    bootRun {
        dependsOn pathingJar
        doFirst {
            classpath = files("$buildDir/classes/main", "$buildDir/resources/main", pathingJar.archivePath)
        }
    }
  • Docker Toolbox配置镜像

    Docker的镜像拉取有时候会花很长的时间,而国内有很多提供镜像服务的公司,随便找一个即可。

    但是在配置的时候经常会出现无法配置成功的问题,比如DaoCloud会告诉你修改profile文件,要么使用它的主机监控程序。一般情况我只想用镜像加速,并不想主机监控服务,所以选用修改profile文件,但是每次重启之后都没有生效。

    比较了阿里百川还有灵雀云的加速配置文档,基本是一个逻辑,但是依照文档,修改镜像的方法只有两种,第一种是创建的时候直接指定

    docker-machine create ... --engine-registry-mirror=<你的加速地址>

    或者是修改config.json中的RegistryMirror配置

    docker-machine-config-json

    这样在docker-machine重启以后配置才会真正生效。

    参考文档:

    https://github.com/docker/machine/blob/master/docs/reference/create.md