yumetodoの旅とプログラミングとかの記録

旅や登山の記録やプログラミング関連の話とかフリーソフト紹介とか

C++erらしくRAIIしつつmmap

teratail.com

に回答するために、mmapを調べていたら

nnabeyang.hatenablog.com

という記事を見つけたのだが、これはCで書かれているのでC++から使うためにラップした。

どうもWindowsの場合は上述ブログ中のget_filecontentで開いてclose_mapで閉じるらしい。このときget_filecontentで得たmap領域へのポインタとハンドル両方いるのだそう。
unixではget_filecontentで開いてmunmapで閉じるらしい。このときget_filecontentで得たmap領域へのポインタとサイズがいるらしい。

というわけでこれをRAII技法を使ってラップした。

なおついでに

  • C++にはvoid*から任意のポインタへの暗黙変換はないのでキャストが必要
  • キャストはstatic_castを使う

などした。

#ifndef YUMETODO_MMAP_HPP_
#define YUMETODO_MMAP_HPP_
// ref: http://nnabeyang.hatenablog.com/entry/20110620/1308560702
#include <cstdint>
#include <cassert>
#ifdef OS_WINDOWS
#include <stdio.h>
#include <windows.h>
#else

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/mman.h>

#endif

#ifdef OS_WINDOWS
namespace impl{
std::size_t get_filecontent(char** buf, const char* fpath, HANDLE* map_handle) noexcept {
  unsigned int mode1, mode2, mode3, mode4;
  mode1 = GENERIC_READ;
  //assert(0x80000000 == mode1);
  mode2 = PAGE_READONLY;
  mode3 = FILE_MAP_READ;
  mode4 = FILE_SHARE_READ;
  wchar_t fname[80];
  int len;
  char* p = fpath;
  int i = 0;
  while(*p != '\0')  {
    mbtowc(&fname[i++], p, MB_CUR_MAX);
    p++;
  }
  fname[i] = '\0';
  HANDLE handle = CreateFileW(fname, mode1, mode4, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  if(handle == INVALID_HANDLE_VALUE) {
    fprintf(stderr, "file open failed\n");
    exit(1);
  }
  auto size = GetFileSize(handle, 0);
  assert(size >= 0);
  *map_handle = CreateFileMapping(handle, 0, mode2, 0, 0, 0);
  *buf = static_cast<char*>(MapViewOfFile(*map_handle, mode3, 0, 0, 0));
  if(handle != INVALID_HANDLE_VALUE) {
    CloseHandle(handle);
    handle = INVALID_HANDLE_VALUE;
  }
  return std::size_t(size);
}

void close_map(char** inp, HANDLE* map_handle) noexcept {
  UnmapViewOfFile(*inp);
  if(*map_handle != INVALID_HANDLE_VALUE) {
    CloseHandle(*map_handle);
    *map_handle = INVALID_HANDLE_VALUE;
  }
  *inp_ = NULL;
}
}
class mmap_handle {
  char *inp_;
  std::size_t size_;
  HANDLE map_handle_;
public:
  mmap_handle(const char* fpath) noexcept {
    this->size_ = impl::get_filecontent(&inp_, fpath, &map_handle_);
  }
  ~mmap_handle() noexcept {
    impl::close_map(&inp_, &map_handle_);
  }
  char* get() noexcept;
  const char* get() const noexcept;
  std::size_t size() const noexcept;
  char* begin() noexcept;
  const char* begin() const noexcept;
  const char* cbegin() const noexcept;
  char* end() noexcept;
  const char* end() const noexcept;
  const char* cend() const noexcept;
};
#else
namespace impl{
std::size_t get_filecontent(char** buf, const char* fpath) noexcept {
  int fd = open(fpath, O_RDONLY);
  assert(fd > 0);
  struct stat st;
  assert(fstat(fd, &st) >= 0 && st.st_size >= 0);
  const auto re = std::size_t(st.st_size);
  *buf = static_cast<char*>(mmap(NULL, re, PROT_READ, MAP_PRIVATE, fd, 0));
  close(fd);
  return re;
}
}
class mmap_handle {
  char *inp_;
  std::size_t size_;
public:
  mmap_handle(const char* fpath) noexcept {
    this->size_ = impl::get_filecontent(&inp_, fpath);
  }
  ~mmap_handle() noexcept {
    munmap(inp_, size_);
  }
  char* get() noexcept;
  const char* get() const noexcept;
  std::size_t size() const noexcept;
  char* begin() noexcept;
  const char* begin() const noexcept;
  const char* cbegin() const noexcept;
  char* end() noexcept;
  const char* end() const noexcept;
  const char* cend() const noexcept;
};
#endif
inline char* mmap_handle::get() noexcept { return this->inp_; }
inline const char* mmap_handle::get() const noexcept { return this->inp_; }
inline std::size_t mmap_handle::size() const noexcept { return this->size_; }
inline char* mmap_handle::begin() noexcept { return this->inp_; }
inline const char* mmap_handle::begin() const noexcept { return this->inp_; }
inline const char* mmap_handle::cbegin() const noexcept { return this->inp_; }
inline char* mmap_handle::end() noexcept { return this->inp_ + this->size_; }
inline const char* mmap_handle::end() const noexcept { return this->inp_ + this->size_; }
inline const char* mmap_handle::cend() const noexcept { return this->inp_ + this->size_; }
#endif //YUMETODO_MMAP_HPP_

使用例は

wandbox.org

ニコニコ生放送のエモーション機能のアンケートに意見を送った

エモーションとは?

blog.nicovideo.jp

ニコニコ生放送において、コメント機能に加えて新たに追加された視聴者からの反応を届ける手段である。

コメントより少ない操作数でかんたんに反応を送ることができる特徴をもつ。

すでに競合サービスにおいて似たような機能は、ミラティブのスタンプ機能などがある。

エモーションの良い点

スマートフォンにおいてコメントを打つのは面倒な側面があったので、それに対する手当がなされた点ではないか。

エモーションの問題点

エモーションの立ち位置についての論理バグ

ニコニコ動画・生放送におけるもっとも有名な特徴の一つであるコメントが画面に流れる機能は再生画面下のボタンひとつで容易にON/OFFできる。

一方でエモーションの表示を視聴者でOFFにするにはわざわざ詳細設定に行き、「ゲーム・ギフト・エモーションの演出を非表示にする」をONにする必要がある。しかもこれはエモーション単体をOFFにする機能ではない。

説明生放送ではエモーションとコメントの立ち位置について、コメントのほうが上であると明言された。しかしながら、「OFFにできるコメント VS OFFにできないエモーション」という構図はこれに矛盾する。説明生放送での説明は虚偽である。

共通体験とはなにか

エモーションを配信側でOFFにする手段は共通体験という名目のもと提供しないとされた。ある放送では使えるけど別のある放送では使えないとなるとライト層が困ると言いたいのだろう。

しかし後述するようにエモーションと相性の悪い放送というのが存在する。そういった文脈を無視して一律OFFにさせません、というのでは公式が提供する配信荒らし手段と言わざるを得ない。

画面がうまる様子

視線のクロスファイア問題

エモーションは上から、コメントは右から流れてくる。配信画面で何かしら文章を映したならそれは左から読む。つまり視線の動きが左から、右から、上から、というようにクロスファイアしており、あとは下からなんか来たらコンプリート状態である。視線の移動を常時行うことを強いられているわけである。エモーションがうざく感じる最大の要因はこの視線の動きがクロスファイアすることにあると考える。社内でおそらくコメントと同様に右から流れるようなことを試されたはずであり、その情報を開示してほしい。エモーションがすでに他の競合サービスで実装されているにも関わらず生放送アンケで「良くなかった」が70.8%であったのは、ニコ生にのみあるコメント機能との相性問題があるのではないか。

配信荒れすぎ

アンケ低すぎな?

なぜエモーションは画面下部に滞留するのか

画面を専有するものとして議論の的となってきたコメントアートや下コメは、例えばコメントをNGにするとかコメント表示を一時的にOFFにする警告コメントを付けるなどのユーザーのリテラシーである程度回避することができた。しかしながらエモーションは生放送で行われることもあり、回避が著しく困難である。そもそも一定時間滞留する必要はあったのか?今こそ問い直さなければならない。

エモーションと相性の悪いゲーム実況・スライド解説

エモーションは画面下部に滞留する性質がある。ゆえに一部のゲーム実況のように下部に重要な情報が表示される場合には、見えなくなるため相性がわるい。また上述した視線のクロスファイア問題から文字情報が大事なスライド解説のような生放送とも相性が悪い。そんな状況で上述の共通体験と言われたのでは、説明生放送で「栗田がゲーム配信嫌いだから潰しにきたか?」というコメまで付くのは必然である。

スライド見えません

BAN機能

コメントは配信者によってBAN、すなわち表示非表示を切り替えることができる。一方で今の所コメントビューアがエモーションに対応するためのAPIなどは示されていない。いかなる場所にもエモーションが打たれたリストは開示されていない。匿名性の観点を考えても、コメントにおける184相当に、放送ごとのidを振り、これをブロックする機能が必要不可欠であると考える。

コメントを書きやすくする機能は考えないのか

説明生放送でも過去にニコニコTV?だったかでそれっぽいコメントの候補を自動て提示する機能があったことを言及されていたが、スマートフォン版にそれが導入されたことは記憶にない。むしろその機能が今必要なのではないか?復活したニコる同様再検討の時である。

CPU使用率

私の環境はIntel Core i7-7500U RAM: 16GBのPCでFirefoxを使って視聴している。しかしながら、エモーションで画面がうまる中、CPUの使用率は「ゲーム・ギフト・エモーションの演出を非表示にする」をONにすることで30%前後減少した。つまりCPUを意図ぜず大幅に喰っている。あれっ、Coinhive事件・・・

まとめ

エモーション自体は良い試みだと思う。エモーション導入の背景である、リスナーからのフィードバックがある双方向性がコンテンツクリエイターたる生主のモチベーションになることから考えてもそこを手当できる可能性のある機能だと思う。しかしながらこのままでは、「OFFにできるコメント VS OFFにできないエモーション」という論理破綻を抱えた視線の移動を常時行うことを強いられる公式が提供する配信荒らし手段となる可能性が高い。共通体験という掛け声は大切にしようとしたコンテンツクリエイターをかえって傷つける結果となるのではないか。にこにこ(く)発表会のときの大荒れをもう一度思い出してほしい。あのときの教訓であるコンテンツクリエイターを大切にする姿勢を忘れず再検討してほしい。

アンケートに意見を送った

この機能についてのアンケートが実施されている。アンケートは誰でも送ることができる

secure.nicovideo.jp

実は当初設問の選択肢に問題があり、実質プレミアム会員でないと回答できない状態になっていたが、某所で指摘したところ速やかに修正されている。

我が家にも特別定額給付金の申請用紙が届いた

マイナポータルから申請するとかえって受け取りに時間がかかることに定評のある特別定額給付金、その申請用紙が郵送されてきた。

封筒

中の書類。水道料金引き落としの銀行口座がfillされてるかと思ったが、そんなことはなかった。

書類

注意書き

注意書き