project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/GameConnection.java
author Wuzzy <almikes@aol.com>
Sat, 30 Sep 2017 23:52:08 +0200
changeset 12627 07fdda8c13a2
parent 10017 de822cd3df3a
permissions -rw-r--r--
TrophyRace: Fix game never eliminating any hogs after a hog skipped or ran out of time Warning: This commit _might_ invalidate past records, but I'm not sure if this is actually the case. Note that only the eliminiation part of the script is touched, not the actual race logic. Even if records are actually broken by this, I and sheepluva have decided that it's more imporant to fix this very, VERY stupid and old bug than to preserve records.

/*
 * Hedgewars for Android. An Android port of Hedgewars, a free turn based strategy game
 * Copyright (C) 2012 Simeon Maxein <smaxein@googlemail.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package org.hedgewars.hedgeroid;

import java.net.ConnectException;

import org.hedgewars.hedgeroid.Datastructures.GameConfig;
import org.hedgewars.hedgeroid.frontlib.Flib;
import org.hedgewars.hedgeroid.frontlib.Frontlib;
import org.hedgewars.hedgeroid.frontlib.Frontlib.ByteArrayPtr;
import org.hedgewars.hedgeroid.frontlib.Frontlib.BytesCallback;
import org.hedgewars.hedgeroid.frontlib.Frontlib.GameSetupPtr;
import org.hedgewars.hedgeroid.frontlib.Frontlib.GameconnPtr;
import org.hedgewars.hedgeroid.frontlib.Frontlib.IntCallback;
import org.hedgewars.hedgeroid.frontlib.Frontlib.StrBoolCallback;
import org.hedgewars.hedgeroid.frontlib.Frontlib.StrCallback;
import org.hedgewars.hedgeroid.frontlib.Frontlib.VoidCallback;
import org.hedgewars.hedgeroid.frontlib.NativeSizeT;
import org.hedgewars.hedgeroid.netplay.GameMessageListener;
import org.hedgewars.hedgeroid.netplay.Netplay;
import org.hedgewars.hedgeroid.util.TickHandler;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.util.Log;

import com.sun.jna.Pointer;

/**
 * This class handles both talking to the engine (IPC) for running a game, and
 * coordinating with the netconn if it is a netgame, using the frontlib for the
 * actual IPC networking communication.
 *
 * After creating the GameConnection object, it will communicate with the engine
 * on its own thread. It shuts itself down as soon as the connection to the engine
 * is lost.
 */
public final class GameConnection {
    private static final Handler mainHandler = new Handler(Looper.getMainLooper());

    public final int port;
    private final HandlerThread thread;
    private final Handler handler;
    private TickHandler tickHandler;
    private final Netplay netplay; // ==null if not a netgame
    private GameconnPtr conn;

    private GameConnection(GameconnPtr conn, Netplay netplay) {
        this.conn = conn;
        this.port = Flib.INSTANCE.flib_gameconn_getport(conn);
        this.netplay = netplay;
        this.thread = new HandlerThread("IPCThread");
        thread.start();
        this.handler = new Handler(thread.getLooper());
    }

    private void setupConnection() {
        tickHandler = new TickHandler(thread.getLooper(), 50, tickCb);
        tickHandler.start();

        if(netplay != null) {
            mainHandler.post(new Runnable() {
                public void run() {
                    netplay.registerGameMessageListener(gameMessageListener);
                }
            });
            Flib.INSTANCE.flib_gameconn_onChat(conn, chatCb, null);
            Flib.INSTANCE.flib_gameconn_onEngineMessage(conn, engineMessageCb, null);
        }
        Flib.INSTANCE.flib_gameconn_onConnect(conn, connectCb, null);
        Flib.INSTANCE.flib_gameconn_onDisconnect(conn, disconnectCb, null);
        Flib.INSTANCE.flib_gameconn_onErrorMessage(conn, errorMessageCb, null);
    }

    /**
     * Start a new IPC server to communicate with the engine.
     * Performs networking operations, don't run on the UI thread.
     * @throws ConnectException if we can't set up the IPC server
     */
    public static GameConnection forNetgame(final GameConfig config, Netplay netplay) throws ConnectException {
        final String playerName = netplay.getPlayerName();
        GameconnPtr conn = Flib.INSTANCE.flib_gameconn_create(playerName, GameSetupPtr.createJavaOwned(config), true);
        if(conn == null) {
            throw new ConnectException();
        }
        GameConnection result = new GameConnection(conn, netplay);
        result.setupConnection();
        return result;
    }

    /**
     * Start a new IPC server to communicate with the engine.
     * Performs networking operations, don't run on the UI thread.
     * @throws ConnectException if we can't set up the IPC server
     */
    public static GameConnection forLocalGame(final GameConfig config) throws ConnectException {
        GameconnPtr conn = Flib.INSTANCE.flib_gameconn_create("Player", GameSetupPtr.createJavaOwned(config), false);
        if(conn == null) {
            throw new ConnectException();
        }
        GameConnection result = new GameConnection(conn, null);
        result.setupConnection();
        return result;
    }

    private final Runnable tickCb = new Runnable() {
        public void run() {
            Flib.INSTANCE.flib_gameconn_tick(conn);
        }
    };

    // runs on the IPCThread
    private void shutdown() {
        tickHandler.stop();
        thread.quit();
        Flib.INSTANCE.flib_gameconn_destroy(conn);
        conn = null;
        if(netplay != null) {
            mainHandler.post(new Runnable() {
                public void run() {
                    netplay.unregisterGameMessageListener(gameMessageListener);
                }
            });
        }
    }

    // runs on the IPCThread
    private final StrBoolCallback chatCb = new StrBoolCallback() {
        public void callback(Pointer context, String message, boolean teamChat) {
            if(teamChat) {
                netplay.sendTeamChat(message);
            } else {
                netplay.sendChat(message);
            }
        }
    };

    // runs on the IPCThread
    private final VoidCallback connectCb = new VoidCallback() {
        public void callback(Pointer context) {
            Log.i("GameConnection", "Connected");
        }
    };

    // runs on the IPCThread
    private final IntCallback disconnectCb = new IntCallback() {
        public void callback(Pointer context, int reason) {
            if(netplay != null) {
                netplay.sendRoundFinished(reason==Frontlib.GAME_END_FINISHED);
            }
            shutdown();
        }
    };

    // runs on the IPCThread
    private final BytesCallback engineMessageCb = new BytesCallback() {
        public void callback(Pointer context, ByteArrayPtr buffer, NativeSizeT size) {
            netplay.sendEngineMessage(buffer.deref(size.intValue()));
        }
    };

    // runs on the IPCThread
    private final StrCallback errorMessageCb = new StrCallback() {
        public void callback(Pointer context, String message) {
            Log.e("GameConnection", message);
        }
    };

    // runs on any thread
    private final GameMessageListener gameMessageListener = new GameMessageListener() {
        public void onNetDisconnected() {
            handler.post(new Runnable() {
                public void run() {
                    Flib.INSTANCE.flib_gameconn_send_quit(conn);
                }
            });
        }

        public void onMessage(final int type, final String message) {
            handler.post(new Runnable() {
                public void run() {
                    Flib.INSTANCE.flib_gameconn_send_textmsg(conn, type, message);
                }
            });
        }

        public void onEngineMessage(final byte[] em) {
            handler.post(new Runnable() {
                public void run() {
                    ByteArrayPtr ptr = ByteArrayPtr.createJavaOwned(em);
                    Flib.INSTANCE.flib_gameconn_send_enginemsg(conn, ptr, NativeSizeT.valueOf(em.length));
                }
            });
        }

        public void onChatMessage(final String nick, final String message) {
            handler.post(new Runnable() {
                public void run() {
                    Flib.INSTANCE.flib_gameconn_send_chatmsg(conn, nick, message);
                }
            });
        }
    };
}