Last active
December 1, 2017 11:16
-
-
Save ken39arg/124596cee96685567b37f78305d8c61b to your computer and use it in GitHub Desktop.
ISUCON7 MSAの最終コードと自分用メモ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
isu1$ rsync -avr isu{1..4}:/etc/mysql ~/repo/etc/isu{1..4}/ | |
isu1$ rsync -avr isu{1..4}:/etc/nginx ~/repo/etc/isu{1..4}/ | |
isuN$ sudo rm /etc/nginx/nginx.conf && ln -s ~/repo/etc/isuN/nginx/nginx.conf /etc/nginx/nginx.conf | |
isuN$ sudo rm /etc/nginx/sites-enabled/cco.nginx.conf && ln -s ~/repo/etc/isuN/nginx/sites-available/cco.nginx.conf /etc/nginx/sites-enabled/cco.nginx.conf |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"context" | |
"fmt" | |
"log" | |
"math/big" | |
"strconv" | |
"sync" | |
"time" | |
_ "github.com/go-sql-driver/mysql" | |
"github.com/gorilla/websocket" | |
"github.com/jmoiron/sqlx" | |
) | |
var ( | |
bigInt1000 = big.NewInt(1000) | |
) | |
type Prices struct { | |
pricesMap map[int]*big.Int | |
mux *sync.RWMutex | |
} | |
type Powers struct { | |
powersMap map[int]*big.Int | |
mux *sync.RWMutex | |
} | |
type RoomTime struct { | |
time int64 | |
mux *sync.Mutex | |
} | |
type RoomsTime struct { | |
timeMap map[string]*RoomTime | |
mux *sync.Mutex | |
} | |
func (rt *RoomsTime) getOrCreateRoomTime(name string) *RoomTime { | |
rt.mux.Lock() | |
defer rt.mux.Unlock() | |
var res *RoomTime | |
res, ok := rt.timeMap[name] | |
if ok { | |
return res | |
} | |
res = &RoomTime{ | |
mux: new(sync.Mutex), | |
} | |
rt.timeMap[name] = res | |
return res | |
} | |
var ( | |
roomTimes = RoomsTime{ | |
timeMap: make(map[string]*RoomTime, 100000), | |
mux: new(sync.Mutex), | |
} | |
) | |
type GameRequest struct { | |
RequestID int `json:"request_id"` | |
Action string `json:"action"` | |
Time int64 `json:"time"` | |
// for addIsu | |
Isu string `json:"isu"` | |
// for buyItem | |
ItemID int `json:"item_id"` | |
CountBought int `json:"count_bought"` | |
} | |
type GameResponse struct { | |
RequestID int `json:"request_id"` | |
IsSuccess bool `json:"is_success"` | |
} | |
// 10進数の指数表記に使うデータ。JSONでは [仮数部, 指数部] という2要素配列になる。 | |
type Exponential struct { | |
// Mantissa * 10 ^ Exponent | |
Mantissa int64 | |
Exponent int64 | |
} | |
func (n Exponential) MarshalJSON() ([]byte, error) { | |
return []byte(fmt.Sprintf("[%d,%d]", n.Mantissa, n.Exponent)), nil | |
} | |
type Adding struct { | |
RoomName string `json:"-" db:"room_name"` | |
Time int64 `json:"time" db:"time"` | |
Isu string `json:"isu" db:"isu"` | |
} | |
type Buying struct { | |
RoomName string `db:"room_name"` | |
ItemID int `db:"item_id"` | |
Ordinal int `db:"ordinal"` | |
Time int64 `db:"time"` | |
} | |
type Schedule struct { | |
Time int64 `json:"time"` | |
MilliIsu Exponential `json:"milli_isu"` | |
TotalPower Exponential `json:"total_power"` | |
} | |
type Item struct { | |
ItemID int `json:"item_id"` | |
CountBought int `json:"count_bought"` | |
CountBuilt int `json:"count_built"` | |
NextPrice Exponential `json:"next_price"` | |
Power Exponential `json:"power"` | |
Building []Building `json:"building"` | |
} | |
type OnSale struct { | |
ItemID int `json:"item_id"` | |
Time int64 `json:"time"` | |
} | |
type Building struct { | |
Time int64 `json:"time"` | |
CountBuilt int `json:"count_built"` | |
Power Exponential `json:"power"` | |
} | |
type GameStatus struct { | |
Time int64 `json:"time"` | |
Adding []Adding `json:"adding"` | |
Schedule []Schedule `json:"schedule"` | |
Items []Item `json:"items"` | |
OnSale []OnSale `json:"on_sale"` | |
} | |
type mItem struct { | |
ItemID int `db:"item_id"` | |
Power1 int64 `db:"power1"` | |
Power2 int64 `db:"power2"` | |
Power3 int64 `db:"power3"` | |
Power4 int64 `db:"power4"` | |
Price1 int64 `db:"price1"` | |
Price2 int64 `db:"price2"` | |
Price3 int64 `db:"price3"` | |
Price4 int64 `db:"price4"` | |
priceMux Prices `db:"-"` | |
powerMux Powers `db:"-"` | |
} | |
func (item *mItem) GetPower(count int) *big.Int { | |
mx := item.powerMux | |
mx.mux.RLock() | |
power, ok := mx.powersMap[count] | |
mx.mux.RUnlock() | |
if ok { | |
return power | |
} | |
mx.mux.Lock() | |
// power(x):=(cx+1)*d^(ax+b) | |
a := item.Power1 | |
b := item.Power2 | |
c := item.Power3 | |
d := item.Power4 | |
x := int64(count) | |
s := big.NewInt(c*x + 1) | |
t := new(big.Int).Exp(big.NewInt(d), big.NewInt(a*x+b), nil) | |
power = new(big.Int).Mul(s, t) | |
mx.powersMap[count] = power | |
mx.mux.Unlock() | |
return power | |
} | |
func (item *mItem) GetPrice(count int) *big.Int { | |
mx := item.priceMux | |
mx.mux.RLock() | |
price, ok := mx.pricesMap[count] | |
mx.mux.RUnlock() | |
if ok { | |
return price | |
} | |
mx.mux.Lock() | |
// price(x):=(cx+1)*d^(ax+b) | |
a := item.Price1 | |
b := item.Price2 | |
c := item.Price3 | |
d := item.Price4 | |
x := int64(count) | |
s := big.NewInt(c*x + 1) | |
t := new(big.Int).Exp(big.NewInt(d), big.NewInt(a*x+b), nil) | |
price = new(big.Int).Mul(s, t) | |
mx.pricesMap[count] = price | |
mx.mux.Unlock() | |
return price | |
} | |
func str2big(s string) *big.Int { | |
x := new(big.Int) | |
x.SetString(s, 10) | |
return x | |
} | |
func big2exp(n *big.Int) Exponential { | |
s := n.String() | |
if len(s) <= 15 { | |
return Exponential{n.Int64(), 0} | |
} | |
t, err := strconv.ParseInt(s[:15], 10, 64) | |
if err != nil { | |
log.Panic(err) | |
} | |
return Exponential{t, int64(len(s) - 15)} | |
} | |
func getCurrentTime() (int64, error) { | |
return time.Now().UnixNano() / int64(time.Millisecond), nil | |
} | |
// 部屋のロックを取りタイムスタンプを更新する | |
// | |
// トランザクション開始後この関数を呼ぶ前にクエリを投げると、 | |
// そのトランザクション中の通常のSELECTクエリが返す結果がロック取得前の | |
// 状態になることに注意 (keyword: MVCC, repeatable read). | |
func updateRoomTime(tx *sqlx.Tx, roomName string, reqTime int64) (int64, bool, func()) { | |
// See page 13 and 17 in https://www.slideshare.net/ichirin2501/insert-51938787 | |
rt := roomTimes.getOrCreateRoomTime(roomName) | |
rt.mux.Lock() | |
fn := func() { | |
rt.mux.Unlock() | |
} | |
roomTime := rt.time | |
currentTime, _ := getCurrentTime() | |
if roomTime > currentTime { | |
//log.Println("room time is future") | |
return 0, false, fn | |
} | |
if reqTime != 0 { | |
if reqTime < currentTime { | |
//log.Println("reqTime is past") | |
return 0, false, fn | |
} | |
} | |
rt.time = currentTime | |
return currentTime, true, fn | |
} | |
func addIsu(roomName string, reqIsu *big.Int, reqTime int64) bool { | |
g := gameLocks[roomName] | |
if g == nil { | |
log.Println("gameState is missing") | |
return false | |
} | |
g.m.Lock() | |
defer g.m.Unlock() | |
tx, err := db.Beginx() | |
if err != nil { | |
log.Println(err) | |
return false | |
} | |
currentTime, ok, fn := updateRoomTime(tx, roomName, reqTime) | |
defer fn() | |
if !ok { | |
tx.Rollback() | |
return false | |
} | |
var isUpdate bool | |
var add Adding | |
alen := len(g.adding) | |
if alen > 0 { | |
add = g.adding[alen-1] | |
if add.Time == reqTime { | |
isUpdate = true | |
} | |
} | |
if !isUpdate { | |
add = Adding{ | |
RoomName: roomName, | |
Time: reqTime, | |
Isu: "0", | |
} | |
} | |
isu := str2big(add.Isu) | |
isu.Add(isu, reqIsu) | |
add.Isu = isu.String() | |
if isUpdate { | |
_, err = tx.Exec("UPDATE adding SET isu = ? WHERE room_name = ? AND time = ?", add.Isu, roomName, reqTime) | |
} else { | |
_, err = tx.Exec("INSERT INTO adding(room_name, time, isu) VALUES (?, ?, ?)", roomName, reqTime, add.Isu) | |
} | |
if err != nil { | |
log.Println(err) | |
tx.Rollback() | |
return false | |
} | |
if err := tx.Commit(); err != nil { | |
log.Println(err) | |
return false | |
} | |
if isUpdate { | |
g.adding[alen-1] = add | |
} else { | |
g.adding = append(g.adding, add) | |
} | |
// old adding | |
fixed := big.NewInt(0) | |
addingNew := make([]Adding, 0, alen) | |
for _, a := range g.adding { | |
if currentTime < a.Time { | |
addingNew = append(addingNew, a) | |
} else { | |
if len(addingNew) == 0 { | |
addingNew = append(addingNew, a) | |
} | |
addingNew[0].Time = a.Time | |
fixed.Add(fixed, str2big(a.Isu)) | |
} | |
} | |
if len(addingNew) < alen { | |
addingNew[0].Isu = fixed.String() | |
g.adding = addingNew | |
} | |
return true | |
} | |
func buyItem(roomName string, itemID int, countBought int, reqTime int64) bool { | |
g := gameLocks[roomName] | |
if g == nil { | |
log.Println("gameState is missing") | |
return false | |
} | |
g.m.Lock() | |
defer g.m.Unlock() | |
tx, err := db.Beginx() | |
if err != nil { | |
log.Println(err) | |
return false | |
} | |
_, ok, fn := updateRoomTime(tx, roomName, reqTime) | |
defer fn() | |
if !ok { | |
tx.Rollback() | |
return false | |
} | |
var countBuying int | |
err = tx.Get(&countBuying, "SELECT COUNT(*) FROM buying WHERE room_name = ? AND item_id = ?", roomName, itemID) | |
if err != nil { | |
log.Println(err) | |
tx.Rollback() | |
return false | |
} | |
if countBuying != countBought { | |
tx.Rollback() | |
log.Println(roomName, itemID, countBought+1, " is already bought") | |
return false | |
} | |
totalMilliIsu := new(big.Int) | |
for _, a := range g.adding { | |
if a.Time <= reqTime { | |
totalMilliIsu.Add(totalMilliIsu, new(big.Int).Mul(str2big(a.Isu), bigInt1000)) | |
} | |
} | |
for _, b := range g.buying { | |
item := mItems[b.ItemID] | |
cost := new(big.Int).Mul(item.GetPrice(b.Ordinal), bigInt1000) | |
totalMilliIsu.Sub(totalMilliIsu, cost) | |
if b.Time <= reqTime { | |
gain := new(big.Int).Mul(item.GetPower(b.Ordinal), big.NewInt(reqTime-b.Time)) | |
totalMilliIsu.Add(totalMilliIsu, gain) | |
} | |
} | |
item := mItems[itemID] | |
need := new(big.Int).Mul(item.GetPrice(countBought+1), bigInt1000) | |
if totalMilliIsu.Cmp(need) < 0 { | |
log.Println("not enough") | |
tx.Rollback() | |
return false | |
} | |
_, err = tx.Exec("INSERT INTO buying(room_name, item_id, ordinal, time) VALUES(?, ?, ?, ?)", roomName, itemID, countBought+1, reqTime) | |
if err != nil { | |
log.Println(err) | |
tx.Rollback() | |
return false | |
} | |
if err := tx.Commit(); err != nil { | |
log.Println(err) | |
return false | |
} | |
g.buying = append(g.buying, Buying{ | |
RoomName: roomName, | |
ItemID: itemID, | |
Ordinal: countBought + 1, | |
Time: reqTime, | |
}) | |
return true | |
} | |
func getStatus(roomName string) (*GameStatus, error) { | |
g := gameLocks[roomName] | |
if g == nil { | |
return nil, fmt.Errorf("gameState is missing") | |
} | |
currentTime, ok, fn := updateRoomTime(nil, roomName, 0) | |
defer fn() | |
if !ok { | |
return nil, fmt.Errorf("updateRoomTime failure") | |
} | |
status, err := calcStatus(currentTime, mItems, g.adding, g.buying) | |
if err != nil { | |
return nil, err | |
} | |
// calcStatusに時間がかかる可能性があるので タイムスタンプを取得し直す | |
latestTime, err := getCurrentTime() | |
if err != nil { | |
return nil, err | |
} | |
status.Time = latestTime | |
return status, err | |
} | |
func calcStatus(currentTime int64, mItems map[int]mItem, addings []Adding, buyings []Buying) (*GameStatus, error) { | |
var ( | |
// 1ミリ秒に生産できる椅子の単位をミリ椅子とする | |
totalMilliIsu = big.NewInt(0) | |
totalPower = big.NewInt(0) | |
itemPower = map[int]*big.Int{} // ItemID => Power | |
itemPrice = map[int]*big.Int{} // ItemID => Price | |
itemPriceForCmp = map[int]*big.Int{} | |
itemOnSale = map[int]int64{} // ItemID => OnSale | |
itemBuilt = map[int]int{} // ItemID => BuiltCount | |
itemBought = map[int]int{} // ItemID => CountBought | |
itemBuilding = map[int][]Building{} // ItemID => Buildings | |
itemPower0 = map[int]Exponential{} // ItemID => currentTime における Power | |
itemBuilt0 = map[int]int{} // ItemID => currentTime における BuiltCount | |
addingAt = map[int64]Adding{} // Time => currentTime より先の Adding | |
buyingAt = map[int64][]Buying{} // Time => currentTime より先の Buying | |
) | |
for itemID := range mItems { | |
itemPower[itemID] = big.NewInt(0) | |
itemBuilding[itemID] = []Building{} | |
} | |
for _, a := range addings { | |
// adding は adding.time に isu を増加させる | |
if a.Time <= currentTime { | |
totalMilliIsu.Add(totalMilliIsu, new(big.Int).Mul(str2big(a.Isu), bigInt1000)) | |
} else { | |
addingAt[a.Time] = a | |
} | |
} | |
for _, b := range buyings { | |
// buying は 即座に isu を消費し buying.time からアイテムの効果を発揮する | |
itemBought[b.ItemID]++ | |
m := mItems[b.ItemID] | |
totalMilliIsu.Sub(totalMilliIsu, new(big.Int).Mul(m.GetPrice(b.Ordinal), bigInt1000)) | |
if b.Time <= currentTime { | |
itemBuilt[b.ItemID]++ | |
power := m.GetPower(itemBought[b.ItemID]) | |
totalMilliIsu.Add(totalMilliIsu, new(big.Int).Mul(power, big.NewInt(currentTime-b.Time))) | |
totalPower.Add(totalPower, power) | |
itemPower[b.ItemID].Add(itemPower[b.ItemID], power) | |
} else { | |
buyingAt[b.Time] = append(buyingAt[b.Time], b) | |
} | |
} | |
for _, m := range mItems { | |
itemPower0[m.ItemID] = big2exp(itemPower[m.ItemID]) | |
itemBuilt0[m.ItemID] = itemBuilt[m.ItemID] | |
price := m.GetPrice(itemBought[m.ItemID] + 1) | |
priceForCmp := new(big.Int).Mul(price, bigInt1000) | |
itemPrice[m.ItemID] = price | |
itemPriceForCmp[m.ItemID] = priceForCmp | |
if 0 <= totalMilliIsu.Cmp(priceForCmp) { | |
itemOnSale[m.ItemID] = 0 // 0 は 時刻 currentTime で購入可能であることを表す | |
} | |
} | |
schedule := []Schedule{ | |
Schedule{ | |
Time: currentTime, | |
MilliIsu: big2exp(totalMilliIsu), | |
TotalPower: big2exp(totalPower), | |
}, | |
} | |
// currentTime から 1000 ミリ秒先までシミュレーションする | |
for t := currentTime + 1; t <= currentTime+1000; t++ { | |
totalMilliIsu.Add(totalMilliIsu, totalPower) | |
updated := false | |
// 時刻 t で発生する adding を計算する | |
if a, ok := addingAt[t]; ok { | |
updated = true | |
totalMilliIsu.Add(totalMilliIsu, new(big.Int).Mul(str2big(a.Isu), bigInt1000)) | |
} | |
// 時刻 t で発生する buying を計算する | |
if _, ok := buyingAt[t]; ok { | |
updated = true | |
updatedID := map[int]bool{} | |
for _, b := range buyingAt[t] { | |
m := mItems[b.ItemID] | |
updatedID[b.ItemID] = true | |
itemBuilt[b.ItemID]++ | |
power := m.GetPower(b.Ordinal) | |
itemPower[b.ItemID].Add(itemPower[b.ItemID], power) | |
totalPower.Add(totalPower, power) | |
} | |
for id := range updatedID { | |
itemBuilding[id] = append(itemBuilding[id], Building{ | |
Time: t, | |
CountBuilt: itemBuilt[id], | |
Power: big2exp(itemPower[id]), | |
}) | |
} | |
} | |
if updated { | |
schedule = append(schedule, Schedule{ | |
Time: t, | |
MilliIsu: big2exp(totalMilliIsu), | |
TotalPower: big2exp(totalPower), | |
}) | |
} | |
// 時刻 t で購入可能になったアイテムを記録する | |
for itemID := range mItems { | |
if _, ok := itemOnSale[itemID]; ok { | |
continue | |
} | |
if 0 <= totalMilliIsu.Cmp(itemPriceForCmp[itemID]) { | |
itemOnSale[itemID] = t | |
} | |
} | |
} | |
gsAdding := []Adding{} | |
for _, a := range addingAt { | |
gsAdding = append(gsAdding, a) | |
} | |
gsItems := []Item{} | |
for itemID, _ := range mItems { | |
gsItems = append(gsItems, Item{ | |
ItemID: itemID, | |
CountBought: itemBought[itemID], | |
CountBuilt: itemBuilt0[itemID], | |
NextPrice: big2exp(itemPrice[itemID]), | |
Power: itemPower0[itemID], | |
Building: itemBuilding[itemID], | |
}) | |
} | |
gsOnSale := []OnSale{} | |
for itemID, t := range itemOnSale { | |
gsOnSale = append(gsOnSale, OnSale{ | |
ItemID: itemID, | |
Time: t, | |
}) | |
} | |
return &GameStatus{ | |
Adding: gsAdding, | |
Schedule: schedule, | |
Items: gsItems, | |
OnSale: gsOnSale, | |
}, nil | |
} | |
func serveGameConn(ws *websocket.Conn, roomName string) { | |
defer ws.Close() | |
status, err := getStatus(roomName) | |
if err != nil { | |
log.Println(err) | |
return | |
} | |
err = ws.WriteJSON(status) | |
if err != nil { | |
log.Println(err) | |
return | |
} | |
ctx, cancel := context.WithCancel(context.Background()) | |
defer cancel() | |
chReq := make(chan GameRequest) | |
go func() { | |
defer cancel() | |
for { | |
req := GameRequest{} | |
err := ws.ReadJSON(&req) | |
if err != nil { | |
log.Println(err) | |
return | |
} | |
select { | |
case chReq <- req: | |
case <-ctx.Done(): | |
return | |
} | |
} | |
}() | |
ticker := time.NewTicker(500 * time.Millisecond) | |
defer ticker.Stop() | |
for { | |
select { | |
case req := <-chReq: | |
//log.Println(req) | |
success := false | |
switch req.Action { | |
case "addIsu": | |
success = addIsu(roomName, str2big(req.Isu), req.Time) | |
case "buyItem": | |
success = buyItem(roomName, req.ItemID, req.CountBought, req.Time) | |
default: | |
log.Println("Invalid Action") | |
return | |
} | |
if success { | |
// GameResponse を返却する前に 反映済みの GameStatus を返す | |
status, err := getStatus(roomName) | |
if err != nil { | |
log.Println(err) | |
return | |
} | |
err = ws.WriteJSON(status) | |
if err != nil { | |
log.Println(err) | |
return | |
} | |
} | |
err := ws.WriteJSON(GameResponse{ | |
RequestID: req.RequestID, | |
IsSuccess: success, | |
}) | |
if err != nil { | |
log.Println(err) | |
return | |
} | |
case <-ticker.C: | |
status, err := getStatus(roomName) | |
if err != nil { | |
log.Println(err) | |
return | |
} | |
err = ws.WriteJSON(status) | |
if err != nil { | |
log.Println(err) | |
return | |
} | |
case <-ctx.Done(): | |
return | |
} | |
} | |
} | |
var mItems map[int]mItem | |
func initMItems() error { | |
var items []mItem | |
err := db.Select(&items, "SELECT * FROM m_item") | |
if err != nil { | |
return err | |
} | |
mItems = make(map[int]mItem, len(items)) | |
for i := range items { | |
items[i].priceMux = Prices{ | |
pricesMap: make(map[int]*big.Int), | |
mux: new(sync.RWMutex), | |
} | |
items[i].powerMux = Powers{ | |
powersMap: make(map[int]*big.Int), | |
mux: new(sync.RWMutex), | |
} | |
mItems[items[i].ItemID] = items[i] | |
} | |
return nil | |
} | |
type gameState struct { | |
m *sync.Mutex | |
lasttime int64 | |
adding []Adding | |
buying []Buying | |
} | |
var glock = new(sync.Mutex) | |
var gameLocks = map[string]*gameState{} | |
func initGameState(roomName string) { | |
glock.Lock() | |
defer glock.Unlock() | |
if _, ok := gameLocks[roomName]; !ok { | |
//log.Printf("initGameState[%s]", roomName) | |
addings := []Adding{} | |
if err := db.Select(&addings, "SELECT * FROM adding WHERE room_name = ? ORDER BY time", roomName); err != nil { | |
log.Printf("select adding failed %s", err) | |
} | |
buyings := []Buying{} | |
if err := db.Select(&buyings, "SELECT * FROM buying WHERE room_name = ? ORDER BY time", roomName); err != nil { | |
log.Printf("select buying failed %s", err) | |
} | |
gameLocks[roomName] = &gameState{ | |
m: new(sync.Mutex), | |
lasttime: 0, | |
adding: addings, | |
buying: buyings, | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/json" | |
"fmt" | |
"log" | |
"net/http" | |
"net/url" | |
"os" | |
"sync" | |
"time" | |
"github.com/go-sql-driver/mysql" | |
"github.com/gorilla/handlers" | |
"github.com/gorilla/mux" | |
"github.com/gorilla/websocket" | |
"github.com/jmoiron/sqlx" | |
) | |
func IsMySQLDuplicateError(err error) bool { | |
if mysqlError, ok := err.(*mysql.MySQLError); ok { | |
return mysqlError.Number == 1062 | |
} | |
return false | |
} | |
type RoomsMux struct { | |
idMap map[string]int | |
mux *sync.RWMutex | |
} | |
func (rm *RoomsMux) getID(name string) (id int, ok bool) { | |
rm.mux.RLock() | |
id, ok = rm.idMap[name] | |
rm.mux.RUnlock() | |
return | |
} | |
func (rm *RoomsMux) setID(name string, id int) { | |
rm.mux.Lock() | |
rm.idMap[name] = id | |
rm.mux.Unlock() | |
} | |
var ( | |
rooms = RoomsMux{ | |
idMap: make(map[string]int, 100000), | |
mux: new(sync.RWMutex), | |
} | |
) | |
var ( | |
hosts = []string{ | |
"app0051.isu7f.k0y.org", | |
"app0052.isu7f.k0y.org", | |
"app0053.isu7f.k0y.org", | |
"app0051.isu7f.k0y.org", | |
"app0052.isu7f.k0y.org", | |
"app0053.isu7f.k0y.org", | |
"app0054.isu7f.k0y.org", | |
} | |
) | |
var ( | |
db *sqlx.DB | |
) | |
func initDB() { | |
db_host := os.Getenv("ISU_DB_HOST") | |
if db_host == "" { | |
db_host = "127.0.0.1" | |
} | |
db_port := os.Getenv("ISU_DB_PORT") | |
if db_port == "" { | |
db_port = "3306" | |
} | |
db_user := os.Getenv("ISU_DB_USER") | |
if db_user == "" { | |
db_user = "root" | |
} | |
db_password := os.Getenv("ISU_DB_PASSWORD") | |
if db_password != "" { | |
db_password = ":" + db_password | |
} | |
dsn := fmt.Sprintf("%s%s@tcp(%s:%s)/isudb?parseTime=true&loc=Local&charset=utf8mb4", | |
db_user, db_password, db_host, db_port) | |
log.Printf("Connecting to db: %q", dsn) | |
db, _ = sqlx.Connect("mysql", dsn) | |
for { | |
err := db.Ping() | |
if err == nil { | |
break | |
} | |
log.Println(err) | |
time.Sleep(time.Second * 3) | |
} | |
db.SetMaxOpenConns(20) | |
db.SetConnMaxLifetime(5 * time.Minute) | |
log.Printf("Succeeded to connect db.") | |
if err := initMItems(); err != nil { | |
log.Printf("error initMItems") | |
} | |
} | |
func getInitializeHandler(w http.ResponseWriter, r *http.Request) { | |
db.MustExec("TRUNCATE TABLE adding") | |
db.MustExec("TRUNCATE TABLE buying") | |
db.MustExec("TRUNCATE TABLE room_time") | |
w.WriteHeader(204) | |
} | |
func getRoomHandler(w http.ResponseWriter, r *http.Request) { | |
vars := mux.Vars(r) | |
roomName := vars["room_name"] | |
path := "/ws/" + url.PathEscape(roomName) | |
id, ok := rooms.getID(roomName) | |
if !ok { | |
res, err := db.Exec("INSERT INTO room_websocket(room_name) VALUES(?)", roomName) | |
if err != nil { | |
if IsMySQLDuplicateError(err) { | |
err := db.Get(&id, "select id from room_websocket where room_name = ?", roomName) | |
if err != nil { | |
log.Println(err) | |
return | |
} | |
} else { | |
log.Println(err) | |
return | |
} | |
} else { | |
var id64 int64 | |
id64, err = res.LastInsertId() | |
if err != nil { | |
log.Println(err) | |
return | |
} | |
id = int(id64) | |
rooms.setID(roomName, id) | |
} | |
} | |
w.Header().Set("Content-Type", "application/json") | |
json.NewEncoder(w).Encode(struct { | |
Host string `json:"host"` | |
Path string `json:"path"` | |
}{ | |
Host: hosts[id%len(hosts)], | |
Path: path, | |
}) | |
} | |
func wsGameHandler(w http.ResponseWriter, r *http.Request) { | |
vars := mux.Vars(r) | |
roomName := vars["room_name"] | |
initGameState(roomName) | |
ws, err := websocket.Upgrade(w, r, nil, 1024, 1024) | |
if _, ok := err.(websocket.HandshakeError); ok { | |
log.Println("Failed to upgrade", err) | |
return | |
} | |
go serveGameConn(ws, roomName) | |
} | |
func main() { | |
log.SetFlags(log.LstdFlags | log.Lshortfile) | |
initDB() | |
r := mux.NewRouter() | |
r.HandleFunc("/initialize", getInitializeHandler) | |
r.HandleFunc("/room/", getRoomHandler) | |
r.HandleFunc("/room/{room_name}", getRoomHandler) | |
r.HandleFunc("/ws/", wsGameHandler) | |
r.HandleFunc("/ws/{room_name}", wsGameHandler) | |
r.PathPrefix("/").Handler(http.FileServer(http.Dir("../public/"))) | |
//go http.ListenAndServe(":8080", nil) | |
log.Fatal(http.ListenAndServe(":5000", handlers.LoggingHandler(os.Stderr, r))) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "MSA", | |
"data": { | |
"2017-11-25T11:08:48": 7211, | |
"2017-11-25T11:17:34": 4300, | |
"2017-11-25T11:22:34": 4790, | |
"2017-11-25T12:01:36": 5103, | |
"2017-11-25T12:29:29": 10401, | |
"2017-11-25T12:56:49": 8695, | |
"2017-11-25T12:59:45": 4586, | |
"2017-11-25T13:04:03": 9784, | |
"2017-11-25T13:31:27": 5460, | |
"2017-11-25T13:33:33": 3731, | |
"2017-11-25T13:35:29": 4561, | |
"2017-11-25T13:39:15": 5397, | |
"2017-11-25T13:44:02": 8669, | |
"2017-11-25T13:48:00": 8337, | |
"2017-11-25T13:54:31": 6658, | |
"2017-11-25T14:05:23": 1491, | |
"2017-11-25T14:08:30": 4836, | |
"2017-11-25T14:45:35": 4987, | |
"2017-11-25T14:47:46": 8654, | |
"2017-11-25T15:04:19": 7700, | |
"2017-11-25T15:09:50": 7746, | |
"2017-11-25T15:26:45": 8021, | |
"2017-11-25T15:28:47": 9259, | |
"2017-11-25T15:31:35": 8572, | |
"2017-11-25T15:33:16": 4730, | |
"2017-11-25T15:45:17": 7477, | |
"2017-11-25T15:51:37": 7012, | |
"2017-11-25T15:53:03": 7921, | |
"2017-11-25T15:55:51": 7796, | |
"2017-11-25T16:02:11": 5790, | |
"2017-11-25T16:04:12": 6404, | |
"2017-11-25T16:09:28": 7795, | |
"2017-11-25T16:20:45": 7928, | |
"2017-11-25T16:23:23": 8744, | |
"2017-11-25T16:31:37": 5773, | |
"2017-11-25T16:36:27": 48745, | |
"2017-11-25T16:43:45": 57103, | |
"2017-11-25T16:50:27": 60396, | |
"2017-11-25T17:09:21": 61750, | |
"2017-11-25T17:24:53": 59491, | |
"2017-11-25T17:36:59": 62847, | |
"2017-11-25T17:41:01": 63404, | |
"2017-11-25T17:45:11": 62871, | |
"2017-11-25T17:50:53": 65218, | |
"2017-11-25T18:07:04": 49934, | |
"2017-11-25T18:09:24": 65077, | |
"2017-11-25T18:11:31": 64631, | |
"2017-11-25T18:14:24": 62049, | |
"2017-11-25T18:17:18": 60943, | |
"2017-11-25T18:18:45": 64847 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment