<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;" class="">netrek-dev,<div class=""><br class=""></div><div class="">Success!<div class=""><br class=""></div><div class="">Netrek passed app review and is now on the MacOS App Store (screenshot at bottom).  This is a free app.</div><div class="">If you ran an alpha release, I suggest deleting it before downloading from the App store.  Otherwise the app store will install the 1.0 release in your alpha release location, as opposed to /Applications.</div><div class=""><br class=""></div><div class="">App store version 1.0 adds full controls in the strategic map window, hints on the welcome screen, a medium-sized help/documentation/credits book, convenient “mayday” and “escort me” buttons, and a tip jar.</div><div class=""><br class=""></div><div class="">I’d appreciate people downloading it and (if you like it) positive reviews in the app store.  I’m hoping that will attract new players.</div><div class=""><br class=""></div><div class="">As promised, I’ve made the source code publicly available at <a href="https://github.com/darrellroot/SwiftNetrek" class="">https://github.com/darrellroot/SwiftNetrek</a></div><div class="">I chose the MIT license, which is functionally similar to “copyright.h” and “copyright2.h”, but is a “standard license”.  I welcome pull requests (but will review them of course).</div><div class=""><br class=""></div><div class="">I’m still having issues with the “preferred team”.  I thank Stas for his data and debugging suggestions but team selection is not working as expected.  After a “Netrek development break” in April I will come back and figure that out.  I’ll also work on “position extrapolation” to increase frame rate after my break.</div><div class=""><br class=""></div><div class="">Netrek requires MacOS 10.14 Mojave.  Sven asked:</div><div class="">> What's the reason for the Mojave OS requirement?</div><div class=""><br class=""></div><div class="">Rather than the classic BSD sockets API (socket/connect/bind/listen/accept), my Netrek client uses the new Network framework API from Apple.  Source code at <a href="https://github.com/darrellroot/SwiftNetrek/blob/master/Netrek/Communication/TcpReader.swift" class="">https://github.com/darrellroot/SwiftNetrek/blob/master/Netrek/Communication/TcpReader.swift</a>   The only problem with this API is it requires Mojave.</div><div class=""><br class=""></div><div class="">It’s a real cool API, and helps prevent programmer mistakes from people like me.  Excerpts below.  I believe it gives me “free” IPv6 support if the hostname returns an AAAA address (I haven’t checked to see if the netrek vanilla server supports IPv6—I don’t see any netrek servers with AAAA DNS records).  FYI it looks like IPv4 is supposed to be deprecated on 2/1/2030. #ipv4flagday</div><div class=""><br class=""></div><div class=""><div class=""><pre style="word-wrap: break-word; white-space: pre-wrap;" class="">// TcpReader partial excerpts only see <a href="https://github.com/darrellroot/SwiftNetrek/blob/master/Netrek/Communication/TcpReader.swift" class="">https://github.com/darrellroot/SwiftNetrek/blob/master/Netrek/Communication/TcpReader.swift</a>
</pre></div><div class=""></div><pre style="word-wrap: break-word; white-space: pre-wrap;" class="">class TcpReader {</pre><pre style="word-wrap: break-word; white-space: pre-wrap;" class="">     init?(hostname: String, port: Int, delegate: NetworkDelegate) {</pre></div><div class=""><pre style="word-wrap: break-word; white-space: pre-wrap;" class="">        let serverEndpoint = NWEndpoint.Host(hostname)
</pre><pre style="word-wrap: break-word; white-space: pre-wrap;" class="">        guard let portEndpoint = NWEndpoint.Port(rawValue: port) else { return nil }
</pre></div><div class=""><pre style="word-wrap: break-word; white-space: pre-wrap;" class="">        connection = NWConnection(host: serverEndpoint, port: portEndpoint, using: .tcp)
</pre></div><div class=""><pre style="word-wrap: break-word; white-space: pre-wrap;" class="">        connection.stateUpdateHandler = { [weak self] (newState) in
            switch newState {
            case .ready:
                debugPrint("TcpReader.ready to send")
                self?.appDelegate.newGameState(.serverConnected)
                self?.receive()
            case .failed(let error):
                debugPrint("TcpReader.client failed with error \(error)")
                self?.appDelegate.newGameState(.noServerSelected)
            case .setup:
                debugPrint("TcpReader.setup")
            case .waiting(_):
                debugPrint("TcpReader.waiting")
            case .preparing:
                debugPrint("TcpReader.preparing")
            case .cancelled:
                debugPrint("TcpReader.cancelled")
                self?.appDelegate.newGameState(.noServerSelected)
            }
        }
</pre></div><div class=""><pre style="word-wrap: break-word; white-space: pre-wrap;" class="">        connection.start(queue: queue)</pre><pre style="word-wrap: break-word; white-space: pre-wrap;" class="">    }</pre></div><div class=""><pre style="word-wrap: break-word; white-space: pre-wrap;" class=""><div class=""><pre style="word-wrap: break-word; white-space: pre-wrap;" class="">// the receive function waits asynchronously for new data and then uses the swift “closure” to process the data on a separate thread whenever it arrives.  No select()!  No testing for available data.</pre></div><div class=""></div></pre><pre style="word-wrap: break-word; white-space: pre-wrap;" class="">    func receive() {
</pre></div><div class=""><pre style="word-wrap: break-word; white-space: pre-wrap;" class="">        connection.receive(minimumIncompleteLength: 1, maximumLength: 16384) { (content, context, isComplete, error) in
</pre></div><div class=""><pre style="word-wrap: break-word; white-space: pre-wrap;" class="">            if let content = content {
                //debugPrint("content startIndex \(content.startIndex) endIndex \(content.endIndex)" )
                self.delegate.gotData(data: content, from: self.hostname, port: self.port)
</pre></div><div class=""><pre style="word-wrap: break-word; white-space: pre-wrap;" class="">        }
</pre></div><div class=""><pre style="word-wrap: break-word; white-space: pre-wrap;" class="">    }
</pre><pre style="word-wrap: break-word; white-space: pre-wrap;" class="">    func send(content: Data) {</pre></div><div class=""><pre style="word-wrap: break-word; white-space: pre-wrap;" class="">        connection.send(content: content, completion: .contentProcessed({ (error) in
            if let error = error {
                print("send error: \(error)")
            }
        }))
    }
</pre></div><div class="">And in the packet analyzer code after processing gotData:</div><div class=""><br class=""></div><div class=""><table class="js-file-line-container highlight tab-size" data-tab-size="8" style="box-sizing: border-box; border-collapse: collapse; border-spacing: 0px; tab-size: 8; caret-color: rgb(36, 41, 46); color: rgb(36, 41, 46); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 14px;"><tbody style="box-sizing: border-box;" class=""><tr style="box-sizing: border-box;" class=""></tr><tr style="box-sizing: border-box;" class=""><td id="LC56" class="js-file-line blob-code-inner blob-code" style="box-sizing: border-box; padding: 0px 10px; line-height: 20px; position: relative; vertical-align: top; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 12px; overflow: visible; white-space: pre; word-wrap: normal;">                appDelegate.<span class="pl-smi" style="box-sizing: border-box;">reader</span><span class="pl-k" style="box-sizing: border-box; color: rgb(215, 58, 73);">?</span>.<span class="pl-c1" style="box-sizing: border-box; color: rgb(0, 92, 197);">receive</span>()</td></tr><tr style="box-sizing: border-box;" class=""><td id="L57" class="blob-num js-line-number" data-line-number="57" style="box-sizing: border-box; padding: 0px 10px; -webkit-user-select: none; color: rgba(27, 31, 35, 0.298039); cursor: pointer; font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 12px; line-height: 20px; min-width: 50px; text-align: right; vertical-align: top; white-space: nowrap; width: 50px;"></td></tr></tbody></table>Darrell</div><div class=""><br class=""><br class=""></div><div class=""><img apple-inline="yes" id="66E58705-191B-4F53-B8C1-C59C280C37EC" src="cid:EDA2C17B-7D94-4EB3-B71E-55EDAE3E32DB@hsd1.ca.comcast.net." class=""></div></div></body></html>