<commit_before>// Copyright 2014 PDFium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com

#include "../include/fsdk_define.h"
#include "../include/fpdfdoc.h"

static int THISMODULE = 0;

static CPDF_Bookmark FindBookmark(const CPDF_BookmarkTree& tree, CPDF_Bookmark bookmark, const CFX_WideString& title)
{
	if (bookmark && bookmark.GetTitle().CompareNoCase(title) == 0) {
		// First check this item
		return bookmark;
	}
	// go into children items
	CPDF_Bookmark child = tree.GetFirstChild(bookmark);
	while (child) {
		// check if this item
		CPDF_Bookmark found = FindBookmark(tree, child, title);
		if (found)
			return found;
		child = tree.GetNextSibling(child);
	}
	return CPDF_Bookmark();
}

DLLEXPORT FPDF_BOOKMARK STDCALL FPDFBookmark_GetFirstChild(FPDF_DOCUMENT document, FPDF_BOOKMARK pDict)
{
    if (!document || !pDict)
        return NULL;
    CPDF_Document* pDoc = (CPDF_Document*)document;
    CPDF_BookmarkTree tree(pDoc);
    CPDF_Bookmark bookmark = CPDF_Bookmark((CPDF_Dictionary*)pDict);
    return tree.GetFirstChild(bookmark).GetDict();
}

DLLEXPORT FPDF_BOOKMARK STDCALL FPDFBookmark_GetNextSibling(FPDF_DOCUMENT document, FPDF_BOOKMARK pDict)
{
    if (!document || !pDict)
        return NULL;
    CPDF_Document* pDoc = (CPDF_Document*)document;
    CPDF_BookmarkTree tree(pDoc);
    CPDF_Bookmark bookmark = CPDF_Bookmark((CPDF_Dictionary*)pDict);
    return tree.GetNextSibling(bookmark).GetDict();
}

DLLEXPORT unsigned long STDCALL FPDFBookmark_GetTitle(FPDF_BOOKMARK pDict, void* buffer, unsigned long buflen)
{
    if (!pDict)
        return 0;
    CPDF_Bookmark bookmark((CPDF_Dictionary*)pDict);
    CFX_WideString title = bookmark.GetTitle();
    CFX_ByteString encodedTitle = title.UTF16LE_Encode(FALSE);
    unsigned long len = encodedTitle.GetLength();
    if (buffer && buflen >= len + 2) {
        FXSYS_memcpy(buffer, encodedTitle.c_str(), len);
        ((FX_BYTE*)buffer)[len] = 0;
        ((FX_BYTE*)buffer)[len + 1] = 0;
    }
    return len + 2;
}

DLLEXPORT FPDF_BOOKMARK STDCALL FPDFBookmark_Find(FPDF_DOCUMENT document, FPDF_WIDESTRING title)
{
	if (!document)
		return NULL;
	if (!title || title[0] == 0)
		return NULL;
	CPDF_Document* pDoc = (CPDF_Document*)document;
	CPDF_BookmarkTree tree(pDoc);
	FX_STRSIZE len = CFX_WideString::WStringLength(title);
	CFX_WideString encodedTitle = CFX_WideString::FromUTF16LE(title, len);
	return FindBookmark(tree, CPDF_Bookmark(), encodedTitle).GetDict();
}

DLLEXPORT FPDF_DEST STDCALL FPDFBookmark_GetDest(FPDF_DOCUMENT document, FPDF_BOOKMARK pDict)
{
	if (!document)
		return NULL;
	if (!pDict)
		return NULL;
	CPDF_Bookmark bookmark((CPDF_Dictionary*)pDict);
	CPDF_Document* pDoc = (CPDF_Document*)document;
	CPDF_Dest dest = bookmark.GetDest(pDoc);
	if (dest)
		return dest;
	// If this bookmark is not directly associated with a dest, we try to get action
	CPDF_Action action = bookmark.GetAction();
	if (!action)
		return NULL;
	return action.GetDest(pDoc);
}

DLLEXPORT FPDF_ACTION STDCALL FPDFBookmark_GetAction(FPDF_BOOKMARK pDict)
{
	if (!pDict)
		return NULL;
	CPDF_Bookmark bookmark((CPDF_Dictionary*)pDict);
	return bookmark.GetAction();
}

DLLEXPORT unsigned long STDCALL FPDFAction_GetType(FPDF_ACTION pDict)
{
	if (!pDict)
		return 0;
	CPDF_Action action = (CPDF_Dictionary*)pDict;
	CPDF_Action::ActionType type = action.GetType();
	switch (type) {
		case CPDF_Action::GoTo:
			return PDFACTION_GOTO;
		case CPDF_Action::GoToR:
			return PDFACTION_REMOTEGOTO;
		case CPDF_Action::URI:
			return PDFACTION_URI;
		case CPDF_Action::Launch:
			return PDFACTION_LAUNCH;
		default:
			return PDFACTION_UNSUPPORTED;
	}
	return PDFACTION_UNSUPPORTED;
}

DLLEXPORT FPDF_DEST STDCALL FPDFAction_GetDest(FPDF_DOCUMENT document, FPDF_ACTION pDict)
{
	if (!document)
		return NULL;
	if (!pDict)
		return NULL;
	CPDF_Document* pDoc = (CPDF_Document*)document;
	CPDF_Action action = (CPDF_Dictionary*)pDict;
	return action.GetDest(pDoc);
}

DLLEXPORT unsigned long STDCALL FPDFAction_GetURIPath(FPDF_DOCUMENT document, FPDF_ACTION pDict,
											  void* buffer, unsigned long buflen)
{
	if (!document)
		return 0;
	if (!pDict)
		return 0;
	CPDF_Document* pDoc = (CPDF_Document*)document;
	CPDF_Action action = (CPDF_Dictionary*)pDict;
	CFX_ByteString path = action.GetURI(pDoc);
	unsigned long len = path.GetLength() + 1;
	if (buffer != NULL && buflen >= len)
		FXSYS_memcpy(buffer, path.c_str(), len);
	return len;
}

DLLEXPORT unsigned long STDCALL FPDFDest_GetPageIndex(FPDF_DOCUMENT document, FPDF_DEST pDict)
{
	if (!document)
		return 0;
	if (!pDict)
		return 0;
	CPDF_Document* pDoc = (CPDF_Document*)document;
	CPDF_Dest dest = (CPDF_Array*)pDict;
	return dest.GetPageIndex(pDoc);
}

static void ReleaseLinkList(FX_LPVOID data)
{
	delete (CPDF_LinkList*)data;
}

DLLEXPORT FPDF_LINK STDCALL FPDFLink_GetLinkAtPoint(FPDF_PAGE page, double x, double y)
{
	if (!page)
		return NULL;
	CPDF_Page* pPage = (CPDF_Page*)page;
	// Link list is stored with the document
	CPDF_Document* pDoc = pPage->m_pDocument;
	CPDF_LinkList* pLinkList = (CPDF_LinkList*)pDoc->GetPrivateData(&THISMODULE);
	if (!pLinkList) {
		pLinkList = FX_NEW CPDF_LinkList(pDoc);
		pDoc->SetPrivateData(&THISMODULE, pLinkList, ReleaseLinkList);
	}
	return pLinkList->GetLinkAtPoint(pPage, (FX_FLOAT)x, (FX_FLOAT)y);
}

DLLEXPORT FPDF_DEST STDCALL FPDFLink_GetDest(FPDF_DOCUMENT document, FPDF_LINK pDict)
{
	if (!document)
		return NULL;
	CPDF_Document* pDoc = (CPDF_Document*)document;
	if (!pDict)
		return NULL;
	CPDF_Link link = (CPDF_Dictionary*)pDict;

	FPDF_DEST dest = link.GetDest(pDoc);
	if (dest)
		return dest;
	// If this link is not directly associated with a dest, we try to get action
	CPDF_Action action = link.GetAction();
	if (!action)
		return NULL;
	return action.GetDest(pDoc);
}

DLLEXPORT FPDF_ACTION STDCALL FPDFLink_GetAction(FPDF_LINK pDict)
{
	if (!pDict)
		return NULL;
	CPDF_Link link = (CPDF_Dictionary*)pDict;
	return link.GetAction();
}

DLLEXPORT FPDF_BOOL STDCALL FPDFLink_Enumerate(FPDF_PAGE page, int* startPos, FPDF_LINK* linkAnnot)
{
	if(!page || !startPos || !linkAnnot)
		return FALSE;
	CPDF_Page* pPage = (CPDF_Page*)page;
	if(!pPage->m_pFormDict)
		return FALSE;
	CPDF_Array* pAnnots = pPage->m_pFormDict->GetArray("Annots");
	if(!pAnnots)
		return FALSE;
	for (int i = *startPos; i < (int)pAnnots->GetCount(); i++) {
		CPDF_Dictionary* pDict = (CPDF_Dictionary*)pAnnots->GetElementValue(i);
		if (!pDict || pDict->GetType() != PDFOBJ_DICTIONARY)
			continue;
		if(pDict->GetString(FX_BSTRC("Subtype")).Equal(FX_BSTRC("Link"))) {
			*startPos = i + 1;
			*linkAnnot = (FPDF_LINK)pDict;
			return TRUE;
		}
	}
	return FALSE;
}

DLLEXPORT FPDF_BOOL STDCALL FPDFLink_GetAnnotRect(FPDF_LINK linkAnnot, FS_RECTF* rect)
{
	if(!linkAnnot || !rect)
		return FALSE;
	CPDF_Dictionary* pAnnotDict = (CPDF_Dictionary*)linkAnnot;
	CPDF_Rect rt = pAnnotDict->GetRect(FX_BSTRC("Rect"));
	rect->left = rt.left;
	rect->bottom = rt.bottom;
	rect->right = rt.right;
	rect->top = rt.top;
	return TRUE;
}

DLLEXPORT int STDCALL FPDFLink_CountQuadPoints(FPDF_LINK linkAnnot)
{
	if(!linkAnnot)
		return 0;
	CPDF_Dictionary* pAnnotDict = (CPDF_Dictionary*)linkAnnot;
	CPDF_Array* pArray = pAnnotDict->GetArray(FX_BSTRC("QuadPoints"));
	if (!pArray)
		return 0;
	else
		return pArray->GetCount() / 8;
}

DLLEXPORT FPDF_BOOL STDCALL FPDFLink_GetQuadPoints(FPDF_LINK linkAnnot, int quadIndex, FS_QUADPOINTSF* quadPoints)
{
	if(!linkAnnot || !quadPoints)
		return FALSE;
	CPDF_Dictionary* pAnnotDict = (CPDF_Dictionary*)linkAnnot;
	CPDF_Array* pArray = pAnnotDict->GetArray(FX_BSTRC("QuadPoints"));
	if (pArray) {
		if (quadIndex < 0 || quadIndex >= (int)pArray->GetCount()/8 || ((quadIndex*8+7) >= (int)pArray->GetCount()))
			return FALSE;
		quadPoints->x1 = pArray->GetNumber(quadIndex*8);
		quadPoints->y1 = pArray->GetNumber(quadIndex*8+1);
		quadPoints->x2 = pArray->GetNumber(quadIndex*8+2);
		quadPoints->y2 = pArray->GetNumber(quadIndex*8+3);
		quadPoints->x3 = pArray->GetNumber(quadIndex*8+4);
		quadPoints->y3 = pArray->GetNumber(quadIndex*8+5);
		quadPoints->x4 = pArray->GetNumber(quadIndex*8+6);
		quadPoints->y4 = pArray->GetNumber(quadIndex*8+7);
		return TRUE;
	}
	return FALSE;
}

DLLEXPORT unsigned long STDCALL FPDF_GetMetaText(FPDF_DOCUMENT doc, FPDF_BYTESTRING tag,
												 void* buffer, unsigned long buflen)
{
	if (!doc || !tag)
		return 0;
	CPDF_Document* pDoc = (CPDF_Document*)doc;
	// Get info dictionary
	CPDF_Dictionary* pInfo = pDoc->GetInfo();
	if (!pInfo)
		return 0;
	CFX_WideString text = pInfo->GetUnicodeText(tag);
	// Use UTF-16LE encoding
	CFX_ByteString encodedText = text.UTF16LE_Encode();
	unsigned long len = encodedText.GetLength();
	if (buffer && buflen >= len + 2) {
		FXSYS_memcpy(buffer, encodedText.c_str(), len);
		// use double zero as trailer
		((FX_BYTE*)buffer)[len] = 0;
		((FX_BYTE*)buffer)[len + 1] = 0;
	}
	return len+2;
}
<commit_msg>Allow FPDFBookmark_GetFirstChild to return the top level bookmark<commit_after>// Copyright 2014 PDFium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com

#include "../include/fsdk_define.h"
#include "../include/fpdfdoc.h"

static int THISMODULE = 0;

static CPDF_Bookmark FindBookmark(const CPDF_BookmarkTree& tree, CPDF_Bookmark bookmark, const CFX_WideString& title)
{
	if (bookmark && bookmark.GetTitle().CompareNoCase(title) == 0) {
		// First check this item
		return bookmark;
	}
	// go into children items
	CPDF_Bookmark child = tree.GetFirstChild(bookmark);
	while (child) {
		// check if this item
		CPDF_Bookmark found = FindBookmark(tree, child, title);
		if (found)
			return found;
		child = tree.GetNextSibling(child);
	}
	return CPDF_Bookmark();
}

DLLEXPORT FPDF_BOOKMARK STDCALL FPDFBookmark_GetFirstChild(FPDF_DOCUMENT document, FPDF_BOOKMARK pDict)
{
    if (!document)
        return NULL;
    CPDF_Document* pDoc = (CPDF_Document*)document;
    CPDF_BookmarkTree tree(pDoc);
    CPDF_Bookmark bookmark = CPDF_Bookmark((CPDF_Dictionary*)pDict);
    return tree.GetFirstChild(bookmark).GetDict();
}

DLLEXPORT FPDF_BOOKMARK STDCALL FPDFBookmark_GetNextSibling(FPDF_DOCUMENT document, FPDF_BOOKMARK pDict)
{
    if (!document || !pDict)
        return NULL;
    CPDF_Document* pDoc = (CPDF_Document*)document;
    CPDF_BookmarkTree tree(pDoc);
    CPDF_Bookmark bookmark = CPDF_Bookmark((CPDF_Dictionary*)pDict);
    return tree.GetNextSibling(bookmark).GetDict();
}

DLLEXPORT unsigned long STDCALL FPDFBookmark_GetTitle(FPDF_BOOKMARK pDict, void* buffer, unsigned long buflen)
{
    if (!pDict)
        return 0;
    CPDF_Bookmark bookmark((CPDF_Dictionary*)pDict);
    CFX_WideString title = bookmark.GetTitle();
    CFX_ByteString encodedTitle = title.UTF16LE_Encode(FALSE);
    unsigned long len = encodedTitle.GetLength();
    if (buffer && buflen >= len + 2) {
        FXSYS_memcpy(buffer, encodedTitle.c_str(), len);
        ((FX_BYTE*)buffer)[len] = 0;
        ((FX_BYTE*)buffer)[len + 1] = 0;
    }
    return len + 2;
}

DLLEXPORT FPDF_BOOKMARK STDCALL FPDFBookmark_Find(FPDF_DOCUMENT document, FPDF_WIDESTRING title)
{
	if (!document)
		return NULL;
	if (!title || title[0] == 0)
		return NULL;
	CPDF_Document* pDoc = (CPDF_Document*)document;
	CPDF_BookmarkTree tree(pDoc);
	FX_STRSIZE len = CFX_WideString::WStringLength(title);
	CFX_WideString encodedTitle = CFX_WideString::FromUTF16LE(title, len);
	return FindBookmark(tree, CPDF_Bookmark(), encodedTitle).GetDict();
}

DLLEXPORT FPDF_DEST STDCALL FPDFBookmark_GetDest(FPDF_DOCUMENT document, FPDF_BOOKMARK pDict)
{
	if (!document)
		return NULL;
	if (!pDict)
		return NULL;
	CPDF_Bookmark bookmark((CPDF_Dictionary*)pDict);
	CPDF_Document* pDoc = (CPDF_Document*)document;
	CPDF_Dest dest = bookmark.GetDest(pDoc);
	if (dest)
		return dest;
	// If this bookmark is not directly associated with a dest, we try to get action
	CPDF_Action action = bookmark.GetAction();
	if (!action)
		return NULL;
	return action.GetDest(pDoc);
}

DLLEXPORT FPDF_ACTION STDCALL FPDFBookmark_GetAction(FPDF_BOOKMARK pDict)
{
	if (!pDict)
		return NULL;
	CPDF_Bookmark bookmark((CPDF_Dictionary*)pDict);
	return bookmark.GetAction();
}

DLLEXPORT unsigned long STDCALL FPDFAction_GetType(FPDF_ACTION pDict)
{
	if (!pDict)
		return 0;
	CPDF_Action action = (CPDF_Dictionary*)pDict;
	CPDF_Action::ActionType type = action.GetType();
	switch (type) {
		case CPDF_Action::GoTo:
			return PDFACTION_GOTO;
		case CPDF_Action::GoToR:
			return PDFACTION_REMOTEGOTO;
		case CPDF_Action::URI:
			return PDFACTION_URI;
		case CPDF_Action::Launch:
			return PDFACTION_LAUNCH;
		default:
			return PDFACTION_UNSUPPORTED;
	}
	return PDFACTION_UNSUPPORTED;
}

DLLEXPORT FPDF_DEST STDCALL FPDFAction_GetDest(FPDF_DOCUMENT document, FPDF_ACTION pDict)
{
	if (!document)
		return NULL;
	if (!pDict)
		return NULL;
	CPDF_Document* pDoc = (CPDF_Document*)document;
	CPDF_Action action = (CPDF_Dictionary*)pDict;
	return action.GetDest(pDoc);
}

DLLEXPORT unsigned long STDCALL FPDFAction_GetURIPath(FPDF_DOCUMENT document, FPDF_ACTION pDict,
											  void* buffer, unsigned long buflen)
{
	if (!document)
		return 0;
	if (!pDict)
		return 0;
	CPDF_Document* pDoc = (CPDF_Document*)document;
	CPDF_Action action = (CPDF_Dictionary*)pDict;
	CFX_ByteString path = action.GetURI(pDoc);
	unsigned long len = path.GetLength() + 1;
	if (buffer != NULL && buflen >= len)
		FXSYS_memcpy(buffer, path.c_str(), len);
	return len;
}

DLLEXPORT unsigned long STDCALL FPDFDest_GetPageIndex(FPDF_DOCUMENT document, FPDF_DEST pDict)
{
	if (!document)
		return 0;
	if (!pDict)
		return 0;
	CPDF_Document* pDoc = (CPDF_Document*)document;
	CPDF_Dest dest = (CPDF_Array*)pDict;
	return dest.GetPageIndex(pDoc);
}

static void ReleaseLinkList(FX_LPVOID data)
{
	delete (CPDF_LinkList*)data;
}

DLLEXPORT FPDF_LINK STDCALL FPDFLink_GetLinkAtPoint(FPDF_PAGE page, double x, double y)
{
	if (!page)
		return NULL;
	CPDF_Page* pPage = (CPDF_Page*)page;
	// Link list is stored with the document
	CPDF_Document* pDoc = pPage->m_pDocument;
	CPDF_LinkList* pLinkList = (CPDF_LinkList*)pDoc->GetPrivateData(&THISMODULE);
	if (!pLinkList) {
		pLinkList = FX_NEW CPDF_LinkList(pDoc);
		pDoc->SetPrivateData(&THISMODULE, pLinkList, ReleaseLinkList);
	}
	return pLinkList->GetLinkAtPoint(pPage, (FX_FLOAT)x, (FX_FLOAT)y);
}

DLLEXPORT FPDF_DEST STDCALL FPDFLink_GetDest(FPDF_DOCUMENT document, FPDF_LINK pDict)
{
	if (!document)
		return NULL;
	CPDF_Document* pDoc = (CPDF_Document*)document;
	if (!pDict)
		return NULL;
	CPDF_Link link = (CPDF_Dictionary*)pDict;

	FPDF_DEST dest = link.GetDest(pDoc);
	if (dest)
		return dest;
	// If this link is not directly associated with a dest, we try to get action
	CPDF_Action action = link.GetAction();
	if (!action)
		return NULL;
	return action.GetDest(pDoc);
}

DLLEXPORT FPDF_ACTION STDCALL FPDFLink_GetAction(FPDF_LINK pDict)
{
	if (!pDict)
		return NULL;
	CPDF_Link link = (CPDF_Dictionary*)pDict;
	return link.GetAction();
}

DLLEXPORT FPDF_BOOL STDCALL FPDFLink_Enumerate(FPDF_PAGE page, int* startPos, FPDF_LINK* linkAnnot)
{
	if(!page || !startPos || !linkAnnot)
		return FALSE;
	CPDF_Page* pPage = (CPDF_Page*)page;
	if(!pPage->m_pFormDict)
		return FALSE;
	CPDF_Array* pAnnots = pPage->m_pFormDict->GetArray("Annots");
	if(!pAnnots)
		return FALSE;
	for (int i = *startPos; i < (int)pAnnots->GetCount(); i++) {
		CPDF_Dictionary* pDict = (CPDF_Dictionary*)pAnnots->GetElementValue(i);
		if (!pDict || pDict->GetType() != PDFOBJ_DICTIONARY)
			continue;
		if(pDict->GetString(FX_BSTRC("Subtype")).Equal(FX_BSTRC("Link"))) {
			*startPos = i + 1;
			*linkAnnot = (FPDF_LINK)pDict;
			return TRUE;
		}
	}
	return FALSE;
}

DLLEXPORT FPDF_BOOL STDCALL FPDFLink_GetAnnotRect(FPDF_LINK linkAnnot, FS_RECTF* rect)
{
	if(!linkAnnot || !rect)
		return FALSE;
	CPDF_Dictionary* pAnnotDict = (CPDF_Dictionary*)linkAnnot;
	CPDF_Rect rt = pAnnotDict->GetRect(FX_BSTRC("Rect"));
	rect->left = rt.left;
	rect->bottom = rt.bottom;
	rect->right = rt.right;
	rect->top = rt.top;
	return TRUE;
}

DLLEXPORT int STDCALL FPDFLink_CountQuadPoints(FPDF_LINK linkAnnot)
{
	if(!linkAnnot)
		return 0;
	CPDF_Dictionary* pAnnotDict = (CPDF_Dictionary*)linkAnnot;
	CPDF_Array* pArray = pAnnotDict->GetArray(FX_BSTRC("QuadPoints"));
	if (!pArray)
		return 0;
	else
		return pArray->GetCount() / 8;
}

DLLEXPORT FPDF_BOOL STDCALL FPDFLink_GetQuadPoints(FPDF_LINK linkAnnot, int quadIndex, FS_QUADPOINTSF* quadPoints)
{
	if(!linkAnnot || !quadPoints)
		return FALSE;
	CPDF_Dictionary* pAnnotDict = (CPDF_Dictionary*)linkAnnot;
	CPDF_Array* pArray = pAnnotDict->GetArray(FX_BSTRC("QuadPoints"));
	if (pArray) {
		if (quadIndex < 0 || quadIndex >= (int)pArray->GetCount()/8 || ((quadIndex*8+7) >= (int)pArray->GetCount()))
			return FALSE;
		quadPoints->x1 = pArray->GetNumber(quadIndex*8);
		quadPoints->y1 = pArray->GetNumber(quadIndex*8+1);
		quadPoints->x2 = pArray->GetNumber(quadIndex*8+2);
		quadPoints->y2 = pArray->GetNumber(quadIndex*8+3);
		quadPoints->x3 = pArray->GetNumber(quadIndex*8+4);
		quadPoints->y3 = pArray->GetNumber(quadIndex*8+5);
		quadPoints->x4 = pArray->GetNumber(quadIndex*8+6);
		quadPoints->y4 = pArray->GetNumber(quadIndex*8+7);
		return TRUE;
	}
	return FALSE;
}

DLLEXPORT unsigned long STDCALL FPDF_GetMetaText(FPDF_DOCUMENT doc, FPDF_BYTESTRING tag,
												 void* buffer, unsigned long buflen)
{
	if (!doc || !tag)
		return 0;
	CPDF_Document* pDoc = (CPDF_Document*)doc;
	// Get info dictionary
	CPDF_Dictionary* pInfo = pDoc->GetInfo();
	if (!pInfo)
		return 0;
	CFX_WideString text = pInfo->GetUnicodeText(tag);
	// Use UTF-16LE encoding
	CFX_ByteString encodedText = text.UTF16LE_Encode();
	unsigned long len = encodedText.GetLength();
	if (buffer && buflen >= len + 2) {
		FXSYS_memcpy(buffer, encodedText.c_str(), len);
		// use double zero as trailer
		((FX_BYTE*)buffer)[len] = 0;
		((FX_BYTE*)buffer)[len + 1] = 0;
	}
	return len+2;
}
<|endoftext|>


<commit_before>// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "logging/logging.hpp"

#include "master/allocator/sorter/drf/sorter.hpp"

using std::list;
using std::set;
using std::string;

namespace mesos {
namespace internal {
namespace master {
namespace allocator {

bool DRFComparator::operator()(const Client& client1, const Client& client2)
{
  if (client1.share == client2.share) {
    if (client1.allocations == client2.allocations) {
      return client1.name < client2.name;
    }
    return client1.allocations < client2.allocations;
  }
  return client1.share < client2.share;
}

DRFSorter::DRFSorter(
    const process::UPID& allocator,
    const std::string& metricsPrefix)
  : metrics(Metrics(allocator, *this, metricsPrefix)) {}

void DRFSorter::add(const string& name, double weight)
{
  Client client(name, 0, 0);
  clients.insert(client);

  allocations[name] = Allocation();
  weights[name] = weight;

  if (metrics.isSome()) {
    metrics->add(name);
  }
}

void DRFSorter::update(const string& name, double weight)
{
  CHECK(weights.contains(name));
  weights[name] = weight;
}

void DRFSorter::remove(const string& name)
{
  set<Client, DRFComparator>::iterator it = find(name);

  if (it != clients.end()) {
    clients.erase(it);
  }

  allocations.erase(name);
  weights.erase(name);

  if (metrics.isSome()) {
    metrics->remove(name);
  }
}

void DRFSorter::activate(const string& name)
{
  CHECK(allocations.contains(name));

  set<Client, DRFComparator>::iterator it = find(name);
  if (it == clients.end()) {
    Client client(name, calculateShare(name), 0);
    clients.insert(client);
  }
}

void DRFSorter::deactivate(const string& name)
{
  set<Client, DRFComparator>::iterator it = find(name);

  if (it != clients.end()) {
    // TODO(benh): Removing the client is an unfortunate strategy
    // because we lose information such as the number of allocations
    // for this client which means the fairness can be gamed by a
    // framework disconnecting and reconnecting.
    clients.erase(it);
  }
}

void DRFSorter::allocated(
    const string& name,
    const SlaveID& slaveId,
    const Resources& resources)
{
  set<Client, DRFComparator>::iterator it = find(name);

  if (it != clients.end()) { // TODO(benh): This should really be a CHECK.
    // TODO(benh): Refactor 'update' to be able to reuse it here.
    Client client(*it);

    // Update the 'allocations' to reflect the allocator decision.
    client.allocations++;

    // Remove and reinsert it to update the ordering appropriately.
    clients.erase(it);
    clients.insert(client);
  }

  allocations[name].resources[slaveId] += resources;
  allocations[name].scalarQuantities +=
    resources.createStrippedScalarQuantity();

  // If the total resources have changed, we're going to
  // recalculate all the shares, so don't bother just
  // updating this client.
  if (!dirty) {
    update(name);
  }
}

void DRFSorter::update(
    const string& name,
    const SlaveID& slaveId,
    const Resources& oldAllocation,
    const Resources& newAllocation)
{
  CHECK(contains(name));

  // TODO(bmahler): Check invariants between old and new allocations.
  // Namely, the roles and quantities of resources should be the same!
  // Otherwise, we need to ensure we re-calculate the shares, as
  // is being currently done, for safety.

  const Resources oldAllocationQuantity =
    oldAllocation.createStrippedScalarQuantity();
  const Resources newAllocationQuantity =
    newAllocation.createStrippedScalarQuantity();

  CHECK(total_.resources[slaveId].contains(oldAllocation));
  CHECK(total_.scalarQuantities.contains(oldAllocationQuantity));

  total_.resources[slaveId] -= oldAllocation;
  total_.resources[slaveId] += newAllocation;

  total_.scalarQuantities -= oldAllocationQuantity;
  total_.scalarQuantities += newAllocationQuantity;

  CHECK(allocations[name].resources[slaveId].contains(oldAllocation));
  CHECK(allocations[name].scalarQuantities.contains(oldAllocationQuantity));

  allocations[name].resources[slaveId] -= oldAllocation;
  allocations[name].resources[slaveId] += newAllocation;

  allocations[name].scalarQuantities -= oldAllocationQuantity;
  allocations[name].scalarQuantities += newAllocationQuantity;

  // Just assume the total has changed, per the TODO above.
  dirty = true;
}

const hashmap<SlaveID, Resources>& DRFSorter::allocation(const string& name)
{
  CHECK(contains(name));

  return allocations[name].resources;
}

const Resources& DRFSorter::allocationScalarQuantities(const string& name)
{
  CHECK(contains(name));

  return allocations[name].scalarQuantities;
}

hashmap<string, Resources> DRFSorter::allocation(const SlaveID& slaveId)
{
  // TODO(jmlvanre): We can index the allocation by slaveId to make this faster.
  // It is a tradeoff between speed vs. memory. For now we use existing data
  // structures.

  hashmap<string, Resources> result;

  foreachpair (const string& name, const Allocation& allocation, allocations) {
    if (allocation.resources.contains(slaveId)) {
      // It is safe to use `at()` here because we've just checked the existence
      // of the key. This avoid un-necessary copies.
      result.emplace(name, allocation.resources.at(slaveId));
    }
  }

  return result;
}

Resources DRFSorter::allocation(const string& name, const SlaveID& slaveId)
{
  CHECK(contains(name));

  if (allocations[name].resources.contains(slaveId)) {
    return allocations[name].resources[slaveId];
  }

  return Resources();
}

const hashmap<SlaveID, Resources>& DRFSorter::total() const
{
  return total_.resources;
}

const Resources& DRFSorter::totalScalarQuantities() const
{
  return total_.scalarQuantities;
}

void DRFSorter::unallocated(
    const string& name,
    const SlaveID& slaveId,
    const Resources& resources)
{
  allocations[name].resources[slaveId] -= resources;
  allocations[name].scalarQuantities -=
    resources.createStrippedScalarQuantity();

  if (allocations[name].resources[slaveId].empty()) {
    allocations[name].resources.erase(slaveId);
  }

  if (!dirty) {
    update(name);
  }
}

void DRFSorter::add(const SlaveID& slaveId, const Resources& resources)
{
  if (!resources.empty()) {
    total_.resources[slaveId] += resources;
    total_.scalarQuantities += resources.createStrippedScalarQuantity();

    // We have to recalculate all shares when the total resources
    // change, but we put it off until sort is called so that if
    // something else changes before the next allocation we don't
    // recalculate everything twice.
    dirty = true;
  }
}

void DRFSorter::remove(const SlaveID& slaveId, const Resources& resources)
{
  if (!resources.empty()) {
    CHECK(total_.resources.contains(slaveId));

    total_.resources[slaveId] -= resources;
    total_.scalarQuantities -= resources.createStrippedScalarQuantity();

    if (total_.resources[slaveId].empty()) {
      total_.resources.erase(slaveId);
    }

    dirty = true;
  }
}

void DRFSorter::update(const SlaveID& slaveId, const Resources& resources)
{
  const Resources oldSlaveQuantity =
    total_.resources[slaveId].createStrippedScalarQuantity();

  CHECK(total_.scalarQuantities.contains(oldSlaveQuantity));

  total_.scalarQuantities -= oldSlaveQuantity;
  total_.scalarQuantities += resources.createStrippedScalarQuantity();

  total_.resources[slaveId] = resources;

  if (total_.resources[slaveId].empty()) {
    total_.resources.erase(slaveId);
  }

  dirty = true;
}

list<string> DRFSorter::sort()
{
  if (dirty) {
    set<Client, DRFComparator> temp;

    set<Client, DRFComparator>::iterator it;
    for (it = clients.begin(); it != clients.end(); it++) {
      Client client(*it);

      // Update the 'share' to get proper sorting.
      client.share = calculateShare(client.name);

      temp.insert(client);
    }

    clients = temp;
  }

  list<string> result;

  set<Client, DRFComparator>::iterator it;
  for (it = clients.begin(); it != clients.end(); it++) {
    result.push_back((*it).name);
  }

  return result;
}

bool DRFSorter::contains(const string& name)
{
  return allocations.contains(name);
}

int DRFSorter::count()
{
  return allocations.size();
}

void DRFSorter::update(const string& name)
{
  set<Client, DRFComparator>::iterator it = find(name);

  if (it != clients.end()) {
    Client client(*it);

    // Update the 'share' to get proper sorting.
    client.share = calculateShare(client.name);

    // Remove and reinsert it to update the ordering appropriately.
    clients.erase(it);
    clients.insert(client);
  }
}

double DRFSorter::calculateShare(const string& name)
{
  double share = 0.0;

  // TODO(benh): This implementation of "dominant resource fairness"
  // currently does not take into account resources that are not
  // scalars.

  foreach (const string& scalar, total_.scalarQuantities.names()) {
    // We collect the scalar accumulated total value from the
    // `Resources` object.
    //
    // NOTE: Although in principle scalar resources may be spread
    // across multiple `Resource` objects (e.g., persistent volumes),
    // we currently strip persistence and reservation metadata from
    // the resources in `scalarQuantities`.
    Option<Value::Scalar> __total =
      total_.scalarQuantities.get<Value::Scalar>(scalar);

    CHECK_SOME(__total);
    const double _total = __total.get().value();

    if (_total > 0.0) {
      double allocation = 0.0;

      // We collect the scalar accumulated allocation value from the
      // `Resources` object.
      //
      // NOTE: Although in principle scalar resources may be spread
      // across multiple `Resource` objects (e.g., persistent volumes),
      // we currently strip persistence and reservation metadata from
      // the resources in `scalarQuantities`.
      Option<Value::Scalar> _allocation =
        allocations[name].scalarQuantities.get<Value::Scalar>(scalar);

      if (_allocation.isSome()) {
        allocation = _allocation.get().value();
      }

      share = std::max(share, allocation / _total);
    }
  }

  return share / weights[name];
}

set<Client, DRFComparator>::iterator DRFSorter::find(const string& name)
{
  set<Client, DRFComparator>::iterator it;
  for (it = clients.begin(); it != clients.end(); it++) {
    if (name == (*it).name) {
      break;
    }
  }

  return it;
}

} // namespace allocator {
} // namespace master {
} // namespace internal {
} // namespace mesos {
<commit_msg>CHECK if DRFSorter::add() would introduce a duplicate.<commit_after>// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "logging/logging.hpp"

#include "master/allocator/sorter/drf/sorter.hpp"

using std::list;
using std::set;
using std::string;

namespace mesos {
namespace internal {
namespace master {
namespace allocator {

bool DRFComparator::operator()(const Client& client1, const Client& client2)
{
  if (client1.share == client2.share) {
    if (client1.allocations == client2.allocations) {
      return client1.name < client2.name;
    }
    return client1.allocations < client2.allocations;
  }
  return client1.share < client2.share;
}

DRFSorter::DRFSorter(
    const process::UPID& allocator,
    const std::string& metricsPrefix)
  : metrics(Metrics(allocator, *this, metricsPrefix)) {}

void DRFSorter::add(const string& name, double weight)
{
  CHECK(!contains(name));

  Client client(name, 0, 0);
  clients.insert(client);

  allocations[name] = Allocation();
  weights[name] = weight;

  if (metrics.isSome()) {
    metrics->add(name);
  }
}

void DRFSorter::update(const string& name, double weight)
{
  CHECK(weights.contains(name));
  weights[name] = weight;
}

void DRFSorter::remove(const string& name)
{
  set<Client, DRFComparator>::iterator it = find(name);

  if (it != clients.end()) {
    clients.erase(it);
  }

  allocations.erase(name);
  weights.erase(name);

  if (metrics.isSome()) {
    metrics->remove(name);
  }
}

void DRFSorter::activate(const string& name)
{
  CHECK(allocations.contains(name));

  set<Client, DRFComparator>::iterator it = find(name);
  if (it == clients.end()) {
    Client client(name, calculateShare(name), 0);
    clients.insert(client);
  }
}

void DRFSorter::deactivate(const string& name)
{
  set<Client, DRFComparator>::iterator it = find(name);

  if (it != clients.end()) {
    // TODO(benh): Removing the client is an unfortunate strategy
    // because we lose information such as the number of allocations
    // for this client which means the fairness can be gamed by a
    // framework disconnecting and reconnecting.
    clients.erase(it);
  }
}

void DRFSorter::allocated(
    const string& name,
    const SlaveID& slaveId,
    const Resources& resources)
{
  set<Client, DRFComparator>::iterator it = find(name);

  if (it != clients.end()) { // TODO(benh): This should really be a CHECK.
    // TODO(benh): Refactor 'update' to be able to reuse it here.
    Client client(*it);

    // Update the 'allocations' to reflect the allocator decision.
    client.allocations++;

    // Remove and reinsert it to update the ordering appropriately.
    clients.erase(it);
    clients.insert(client);
  }

  allocations[name].resources[slaveId] += resources;
  allocations[name].scalarQuantities +=
    resources.createStrippedScalarQuantity();

  // If the total resources have changed, we're going to
  // recalculate all the shares, so don't bother just
  // updating this client.
  if (!dirty) {
    update(name);
  }
}

void DRFSorter::update(
    const string& name,
    const SlaveID& slaveId,
    const Resources& oldAllocation,
    const Resources& newAllocation)
{
  CHECK(contains(name));

  // TODO(bmahler): Check invariants between old and new allocations.
  // Namely, the roles and quantities of resources should be the same!
  // Otherwise, we need to ensure we re-calculate the shares, as
  // is being currently done, for safety.

  const Resources oldAllocationQuantity =
    oldAllocation.createStrippedScalarQuantity();
  const Resources newAllocationQuantity =
    newAllocation.createStrippedScalarQuantity();

  CHECK(total_.resources[slaveId].contains(oldAllocation));
  CHECK(total_.scalarQuantities.contains(oldAllocationQuantity));

  total_.resources[slaveId] -= oldAllocation;
  total_.resources[slaveId] += newAllocation;

  total_.scalarQuantities -= oldAllocationQuantity;
  total_.scalarQuantities += newAllocationQuantity;

  CHECK(allocations[name].resources[slaveId].contains(oldAllocation));
  CHECK(allocations[name].scalarQuantities.contains(oldAllocationQuantity));

  allocations[name].resources[slaveId] -= oldAllocation;
  allocations[name].resources[slaveId] += newAllocation;

  allocations[name].scalarQuantities -= oldAllocationQuantity;
  allocations[name].scalarQuantities += newAllocationQuantity;

  // Just assume the total has changed, per the TODO above.
  dirty = true;
}

const hashmap<SlaveID, Resources>& DRFSorter::allocation(const string& name)
{
  CHECK(contains(name));

  return allocations[name].resources;
}

const Resources& DRFSorter::allocationScalarQuantities(const string& name)
{
  CHECK(contains(name));

  return allocations[name].scalarQuantities;
}

hashmap<string, Resources> DRFSorter::allocation(const SlaveID& slaveId)
{
  // TODO(jmlvanre): We can index the allocation by slaveId to make this faster.
  // It is a tradeoff between speed vs. memory. For now we use existing data
  // structures.

  hashmap<string, Resources> result;

  foreachpair (const string& name, const Allocation& allocation, allocations) {
    if (allocation.resources.contains(slaveId)) {
      // It is safe to use `at()` here because we've just checked the existence
      // of the key. This avoid un-necessary copies.
      result.emplace(name, allocation.resources.at(slaveId));
    }
  }

  return result;
}

Resources DRFSorter::allocation(const string& name, const SlaveID& slaveId)
{
  CHECK(contains(name));

  if (allocations[name].resources.contains(slaveId)) {
    return allocations[name].resources[slaveId];
  }

  return Resources();
}

const hashmap<SlaveID, Resources>& DRFSorter::total() const
{
  return total_.resources;
}

const Resources& DRFSorter::totalScalarQuantities() const
{
  return total_.scalarQuantities;
}

void DRFSorter::unallocated(
    const string& name,
    const SlaveID& slaveId,
    const Resources& resources)
{
  allocations[name].resources[slaveId] -= resources;
  allocations[name].scalarQuantities -=
    resources.createStrippedScalarQuantity();

  if (allocations[name].resources[slaveId].empty()) {
    allocations[name].resources.erase(slaveId);
  }

  if (!dirty) {
    update(name);
  }
}

void DRFSorter::add(const SlaveID& slaveId, const Resources& resources)
{
  if (!resources.empty()) {
    total_.resources[slaveId] += resources;
    total_.scalarQuantities += resources.createStrippedScalarQuantity();

    // We have to recalculate all shares when the total resources
    // change, but we put it off until sort is called so that if
    // something else changes before the next allocation we don't
    // recalculate everything twice.
    dirty = true;
  }
}

void DRFSorter::remove(const SlaveID& slaveId, const Resources& resources)
{
  if (!resources.empty()) {
    CHECK(total_.resources.contains(slaveId));

    total_.resources[slaveId] -= resources;
    total_.scalarQuantities -= resources.createStrippedScalarQuantity();

    if (total_.resources[slaveId].empty()) {
      total_.resources.erase(slaveId);
    }

    dirty = true;
  }
}

void DRFSorter::update(const SlaveID& slaveId, const Resources& resources)
{
  const Resources oldSlaveQuantity =
    total_.resources[slaveId].createStrippedScalarQuantity();

  CHECK(total_.scalarQuantities.contains(oldSlaveQuantity));

  total_.scalarQuantities -= oldSlaveQuantity;
  total_.scalarQuantities += resources.createStrippedScalarQuantity();

  total_.resources[slaveId] = resources;

  if (total_.resources[slaveId].empty()) {
    total_.resources.erase(slaveId);
  }

  dirty = true;
}

list<string> DRFSorter::sort()
{
  if (dirty) {
    set<Client, DRFComparator> temp;

    set<Client, DRFComparator>::iterator it;
    for (it = clients.begin(); it != clients.end(); it++) {
      Client client(*it);

      // Update the 'share' to get proper sorting.
      client.share = calculateShare(client.name);

      temp.insert(client);
    }

    clients = temp;
  }

  list<string> result;

  set<Client, DRFComparator>::iterator it;
  for (it = clients.begin(); it != clients.end(); it++) {
    result.push_back((*it).name);
  }

  return result;
}

bool DRFSorter::contains(const string& name)
{
  return allocations.contains(name);
}

int DRFSorter::count()
{
  return allocations.size();
}

void DRFSorter::update(const string& name)
{
  set<Client, DRFComparator>::iterator it = find(name);

  if (it != clients.end()) {
    Client client(*it);

    // Update the 'share' to get proper sorting.
    client.share = calculateShare(client.name);

    // Remove and reinsert it to update the ordering appropriately.
    clients.erase(it);
    clients.insert(client);
  }
}

double DRFSorter::calculateShare(const string& name)
{
  double share = 0.0;

  // TODO(benh): This implementation of "dominant resource fairness"
  // currently does not take into account resources that are not
  // scalars.

  foreach (const string& scalar, total_.scalarQuantities.names()) {
    // We collect the scalar accumulated total value from the
    // `Resources` object.
    //
    // NOTE: Although in principle scalar resources may be spread
    // across multiple `Resource` objects (e.g., persistent volumes),
    // we currently strip persistence and reservation metadata from
    // the resources in `scalarQuantities`.
    Option<Value::Scalar> __total =
      total_.scalarQuantities.get<Value::Scalar>(scalar);

    CHECK_SOME(__total);
    const double _total = __total.get().value();

    if (_total > 0.0) {
      double allocation = 0.0;

      // We collect the scalar accumulated allocation value from the
      // `Resources` object.
      //
      // NOTE: Although in principle scalar resources may be spread
      // across multiple `Resource` objects (e.g., persistent volumes),
      // we currently strip persistence and reservation metadata from
      // the resources in `scalarQuantities`.
      Option<Value::Scalar> _allocation =
        allocations[name].scalarQuantities.get<Value::Scalar>(scalar);

      if (_allocation.isSome()) {
        allocation = _allocation.get().value();
      }

      share = std::max(share, allocation / _total);
    }
  }

  return share / weights[name];
}

set<Client, DRFComparator>::iterator DRFSorter::find(const string& name)
{
  set<Client, DRFComparator>::iterator it;
  for (it = clients.begin(); it != clients.end(); it++) {
    if (name == (*it).name) {
      break;
    }
  }

  return it;
}

} // namespace allocator {
} // namespace master {
} // namespace internal {
} // namespace mesos {
<|endoftext|>


<commit_before>// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef __MASTER_ALLOCATOR_SORTER_DRF_SORTER_HPP__
#define __MASTER_ALLOCATOR_SORTER_DRF_SORTER_HPP__

#include <set>
#include <string>

#include <mesos/resources.hpp>

#include <stout/hashmap.hpp>

#include "master/allocator/sorter/drf/metrics.hpp"

#include "master/allocator/sorter/sorter.hpp"

namespace mesos {
namespace internal {
namespace master {
namespace allocator {

struct Client
{
  Client(const std::string& _name, double _share, uint64_t _allocations)
    : name(_name), share(_share), allocations(_allocations) {}

  std::string name;
  double share;

  // We store the number of times this client has been chosen for
  // allocation so that we can fairly share the resources across
  // clients that have the same share. Note that this information is
  // not persisted across master failovers, but since the point is to
  // equalize the 'allocations' across clients of the same 'share'
  // having allocations restart at 0 after a master failover should be
  // sufficient (famous last words.)
  uint64_t allocations;
};

struct DRFComparator
{
  virtual ~DRFComparator() {}
  virtual bool operator()(const Client& client1, const Client& client2);
};

class DRFSorter : public Sorter
{
public:
  DRFSorter() = default;

  explicit DRFSorter(
      const process::UPID& allocator,
      const std::string& metricsPrefix);

  virtual ~DRFSorter() {}

  virtual void add(const std::string& name, double weight = 1);

  virtual void update(const std::string& name, double weight);

  virtual void remove(const std::string& name);

  virtual void activate(const std::string& name);

  virtual void deactivate(const std::string& name);

  virtual void allocated(
      const std::string& name,
      const SlaveID& slaveId,
      const Resources& resources);

  virtual void update(
      const std::string& name,
      const SlaveID& slaveId,
      const Resources& oldAllocation,
      const Resources& newAllocation);

  virtual void unallocated(
      const std::string& name,
      const SlaveID& slaveId,
      const Resources& resources);

  virtual const hashmap<SlaveID, Resources>& allocation(
      const std::string& name);

  virtual const Resources& allocationScalarQuantities(const std::string& name);

  virtual hashmap<std::string, Resources> allocation(const SlaveID& slaveId);

  virtual Resources allocation(const std::string& name, const SlaveID& slaveId);

  virtual const hashmap<SlaveID, Resources>& total() const;

  virtual const Resources& totalScalarQuantities() const;

  virtual void add(const SlaveID& slaveId, const Resources& resources);

  virtual void remove(const SlaveID& slaveId, const Resources& resources);

  virtual void update(const SlaveID& slaveId, const Resources& resources);

  virtual std::list<std::string> sort();

  virtual bool contains(const std::string& name);

  virtual int count();

private:
  // Recalculates the share for the client and moves
  // it in 'clients' accordingly.
  void update(const std::string& name);

  // Returns the dominant resource share for the client.
  double calculateShare(const std::string& name);

  // Returns an iterator to the specified client, if
  // it exists in this Sorter.
  std::set<Client, DRFComparator>::iterator find(const std::string& name);

  // If true, sort() will recalculate all shares.
  bool dirty;

  // A set of Clients (names and shares) sorted by share.
  std::set<Client, DRFComparator> clients;

  // Maps client names to the weights that should be applied to their shares.
  hashmap<std::string, double> weights;

  // Total resources.
  struct Total {
    hashmap<SlaveID, Resources> resources;

    // NOTE: Scalars can be safely aggregated across slaves. We keep
    // that to speed up the calculation of shares. See MESOS-2891 for
    // the reasons why we want to do that.
    //
    // NOTE: We omit information about dynamic reservations and persistent
    // volumes here to enable resources to be aggregated across slaves
    // more effectively. See MESOS-4833 for more information.
    Resources scalarQuantities;
  } total_;

  // Allocation for a client.
  struct Allocation {
    hashmap<SlaveID, Resources> resources;

    // Similarly, we aggregate scalars across slaves and omit information
    // about dynamic reservations and persistent volumes. See notes above.
    Resources scalarQuantities;
  };

  // Maps client names to the resources they have been allocated.
  hashmap<std::string, Allocation> allocations;

  // Metrics are optionally exposed by the sorter.
  friend Metrics;
  Option<Metrics> metrics;
};

} // namespace allocator {
} // namespace master {
} // namespace internal {
} // namespace mesos {

#endif // __MASTER_ALLOCATOR_SORTER_DRF_SORTER_HPP__
<commit_msg>Fixed an unassigned member variable in DRF sorter.<commit_after>// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef __MASTER_ALLOCATOR_SORTER_DRF_SORTER_HPP__
#define __MASTER_ALLOCATOR_SORTER_DRF_SORTER_HPP__

#include <set>
#include <string>

#include <mesos/resources.hpp>

#include <stout/hashmap.hpp>

#include "master/allocator/sorter/drf/metrics.hpp"

#include "master/allocator/sorter/sorter.hpp"

namespace mesos {
namespace internal {
namespace master {
namespace allocator {

struct Client
{
  Client(const std::string& _name, double _share, uint64_t _allocations)
    : name(_name), share(_share), allocations(_allocations) {}

  std::string name;
  double share;

  // We store the number of times this client has been chosen for
  // allocation so that we can fairly share the resources across
  // clients that have the same share. Note that this information is
  // not persisted across master failovers, but since the point is to
  // equalize the 'allocations' across clients of the same 'share'
  // having allocations restart at 0 after a master failover should be
  // sufficient (famous last words.)
  uint64_t allocations;
};

struct DRFComparator
{
  virtual ~DRFComparator() {}
  virtual bool operator()(const Client& client1, const Client& client2);
};

class DRFSorter : public Sorter
{
public:
  DRFSorter() = default;

  explicit DRFSorter(
      const process::UPID& allocator,
      const std::string& metricsPrefix);

  virtual ~DRFSorter() {}

  virtual void add(const std::string& name, double weight = 1);

  virtual void update(const std::string& name, double weight);

  virtual void remove(const std::string& name);

  virtual void activate(const std::string& name);

  virtual void deactivate(const std::string& name);

  virtual void allocated(
      const std::string& name,
      const SlaveID& slaveId,
      const Resources& resources);

  virtual void update(
      const std::string& name,
      const SlaveID& slaveId,
      const Resources& oldAllocation,
      const Resources& newAllocation);

  virtual void unallocated(
      const std::string& name,
      const SlaveID& slaveId,
      const Resources& resources);

  virtual const hashmap<SlaveID, Resources>& allocation(
      const std::string& name);

  virtual const Resources& allocationScalarQuantities(const std::string& name);

  virtual hashmap<std::string, Resources> allocation(const SlaveID& slaveId);

  virtual Resources allocation(const std::string& name, const SlaveID& slaveId);

  virtual const hashmap<SlaveID, Resources>& total() const;

  virtual const Resources& totalScalarQuantities() const;

  virtual void add(const SlaveID& slaveId, const Resources& resources);

  virtual void remove(const SlaveID& slaveId, const Resources& resources);

  virtual void update(const SlaveID& slaveId, const Resources& resources);

  virtual std::list<std::string> sort();

  virtual bool contains(const std::string& name);

  virtual int count();

private:
  // Recalculates the share for the client and moves
  // it in 'clients' accordingly.
  void update(const std::string& name);

  // Returns the dominant resource share for the client.
  double calculateShare(const std::string& name);

  // Returns an iterator to the specified client, if
  // it exists in this Sorter.
  std::set<Client, DRFComparator>::iterator find(const std::string& name);

  // If true, sort() will recalculate all shares.
  bool dirty = false;

  // A set of Clients (names and shares) sorted by share.
  std::set<Client, DRFComparator> clients;

  // Maps client names to the weights that should be applied to their shares.
  hashmap<std::string, double> weights;

  // Total resources.
  struct Total {
    hashmap<SlaveID, Resources> resources;

    // NOTE: Scalars can be safely aggregated across slaves. We keep
    // that to speed up the calculation of shares. See MESOS-2891 for
    // the reasons why we want to do that.
    //
    // NOTE: We omit information about dynamic reservations and persistent
    // volumes here to enable resources to be aggregated across slaves
    // more effectively. See MESOS-4833 for more information.
    Resources scalarQuantities;
  } total_;

  // Allocation for a client.
  struct Allocation {
    hashmap<SlaveID, Resources> resources;

    // Similarly, we aggregate scalars across slaves and omit information
    // about dynamic reservations and persistent volumes. See notes above.
    Resources scalarQuantities;
  };

  // Maps client names to the resources they have been allocated.
  hashmap<std::string, Allocation> allocations;

  // Metrics are optionally exposed by the sorter.
  friend Metrics;
  Option<Metrics> metrics;
};

} // namespace allocator {
} // namespace master {
} // namespace internal {
} // namespace mesos {

#endif // __MASTER_ALLOCATOR_SORTER_DRF_SORTER_HPP__
<|endoftext|>


<commit_before>#include <StdAfx.h>
#include <UI/Controls/PaneHeader/PaneHeader.h>
#include <UI/UIFunctions.h>
#include <core/utility/strings.h>
#include <UI/DoubleBuffer.h>
#include <core/utility/output.h>
#include <core/utility/registry.h>

namespace controls
{
	static std::wstring CLASS = L"PaneHeader";

	PaneHeader::~PaneHeader()
	{
		TRACE_DESTRUCTOR(CLASS);
		CWnd::DestroyWindow();
	}

	void PaneHeader::Initialize(HWND hWnd, _In_opt_ HDC hdc, _In_ UINT nid)
	{
		m_hWndParent = hWnd;

		// Assign a nID to the collapse button that is IDD_COLLAPSE more than the control's nID
		m_nID = nid;
		m_nIDCollapse = nid + IDD_COLLAPSE;

		WNDCLASSEX wc = {};
		const auto hInst = AfxGetInstanceHandle();
		if (!::GetClassInfoEx(hInst, _T("PaneHeader"), &wc)) // STRING_OK
		{
			wc.cbSize = sizeof wc;
			wc.style = 0; // not passing CS_VREDRAW | CS_HREDRAW fixes flicker
			wc.lpszClassName = _T("PaneHeader"); // STRING_OK
			wc.lpfnWndProc = ::DefWindowProc;
			wc.hbrBackground = GetSysBrush(ui::uiColor::Background); // helps spot flashing

			RegisterClassEx(&wc);
		}

		// WS_CLIPCHILDREN is used to reduce flicker
		EC_B_S(CreateEx(
			0,
			_T("PaneHeader"), // STRING_OK
			_T("PaneHeader"), // STRING_OK
			WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_VISIBLE,
			0,
			0,
			0,
			0,
			m_hWndParent,
			reinterpret_cast<HMENU>(static_cast<INT_PTR>(IDC_PANE_HEADER)),
			nullptr));

		// We hang if we do this:
		// Necessary for TAB to work. Without this, all TABS get stuck on the control
		// instead of passing to the children.
		//EC_B_S(ModifyStyleEx(0, WS_EX_CONTROLPARENT));

		const auto sizeText = ui::GetTextExtentPoint32(hdc, m_szLabel);
		m_iLabelWidth = sizeText.cx;
		output::DebugPrint(
			output::dbgLevel::Draw,
			L"PaneHeader::Initialize m_iLabelWidth:%d \"%ws\"\n",
			m_iLabelWidth,
			m_szLabel.c_str());
	}

	BEGIN_MESSAGE_MAP(PaneHeader, CWnd)
	ON_WM_CREATE()
	END_MESSAGE_MAP()

	int PaneHeader::OnCreate(LPCREATESTRUCT /*lpCreateStruct*/)
	{
		EC_B_S(m_leftLabel.Create(
			WS_CHILD | WS_CLIPSIBLINGS | ES_READONLY | WS_VISIBLE | WS_TABSTOP, CRect(0, 0, 0, 0), this, m_nID));
		::SetWindowTextW(m_leftLabel.m_hWnd, m_szLabel.c_str());
		ui::SubclassLabel(m_leftLabel.m_hWnd);
		if (m_bCollapsible)
		{
			StyleLabel(m_leftLabel.m_hWnd, ui::uiLabelStyle::PaneHeaderLabel);
		}

		EC_B_S(m_rightLabel.Create(
			WS_CHILD | WS_CLIPSIBLINGS | ES_READONLY | WS_VISIBLE | WS_TABSTOP,
			CRect(0, 0, 0, 0),
			this,
			IDD_COUNTLABEL));
		ui::SubclassLabel(m_rightLabel.m_hWnd);
		StyleLabel(m_rightLabel.m_hWnd, ui::uiLabelStyle::PaneHeaderText);

		if (m_bCollapsible)
		{
			EC_B_S(m_CollapseButton.Create(
				nullptr, WS_TABSTOP | WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE, CRect(0, 0, 0, 0), this, m_nIDCollapse));
			StyleButton(
				m_CollapseButton.m_hWnd, m_bCollapsed ? ui::uiButtonStyle::UpArrow : ui::uiButtonStyle::DownArrow);
		}

		// If we need an action button, go ahead and create it
		if (m_nIDAction)
		{
			EC_B_S(m_actionButton.Create(
				strings::wstringTotstring(m_szActionButton).c_str(),
				WS_TABSTOP | WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE,
				CRect(0, 0, 0, 0),
				this,
				m_nIDAction));
			StyleButton(m_actionButton.m_hWnd, ui::uiButtonStyle::Unstyled);
		}

		m_bInitialized = true;
		return 0;
	}

	LRESULT PaneHeader::WindowProc(const UINT message, const WPARAM wParam, const LPARAM lParam)
	{
		LRESULT lRes = 0;
		if (ui::HandleControlUI(message, wParam, lParam, &lRes)) return lRes;

		switch (message)
		{
		case WM_PAINT:
			auto ps = PAINTSTRUCT{};
			::BeginPaint(m_hWnd, &ps);
			if (ps.hdc)
			{
				auto rcWin = RECT{};
				::GetClientRect(m_hWnd, &rcWin);
				const auto background = registry::uiDiag ? ui::GetSysBrush(ui::uiColor::TestGreen)
														 : ui::GetSysBrush(ui::uiColor::Background);
				FillRect(ps.hdc, &rcWin, background);
			}

			::EndPaint(m_hWnd, &ps);
			return 0;
		case WM_WINDOWPOSCHANGED:
			RecalcLayout();
			return 0;
		case WM_CLOSE:
			::SendMessage(m_hWndParent, message, wParam, lParam);
			return true;
		case WM_HELP:
			return true;
		case WM_COMMAND:
		{
			const auto nCode = HIWORD(wParam);
			// Pass button clicks up to parent
			if (BN_CLICKED == nCode)
			{
				::SendMessage(m_hWndParent, message, wParam, lParam);
			}

			break;
		}
		}

		return CWnd::WindowProc(message, wParam, lParam);
	}

	// Position collapse button, labels, action button.
	void PaneHeader::RecalcLayout()
	{
		if (!m_bInitialized) return;
		auto hWinPosInfo = WC_D(HDWP, BeginDeferWindowPos(4));
		if (hWinPosInfo)
		{
			auto rcWin = RECT{};
			::GetClientRect(m_hWnd, &rcWin);
			const auto width = rcWin.right - rcWin.left;
			const auto height = rcWin.bottom - rcWin.top;
			output::DebugPrint(
				output::dbgLevel::Draw,
				L"PaneHeader::DeferWindowPos width:%d height:%d v:%d\n",
				width,
				height,
				IsWindowVisible());
			auto curX = 0;
			const auto actionButtonWidth = m_actionButtonWidth ? m_actionButtonWidth + 2 * m_iMargin : 0;
			const auto actionButtonAndGutterWidth = actionButtonWidth ? actionButtonWidth + m_iSideMargin : 0;
			if (m_bCollapsible)
			{
				hWinPosInfo = ui::DeferWindowPos(
					hWinPosInfo,
					m_CollapseButton.GetSafeHwnd(),
					curX,
					0,
					width - actionButtonAndGutterWidth,
					height,
					L"PaneHeader::DeferWindowPos::collapseButton");
				curX += m_iButtonHeight;
			}

			hWinPosInfo = ui::DeferWindowPos(
				hWinPosInfo,
				m_leftLabel.GetSafeHwnd(),
				curX,
				0,
				m_iLabelWidth,
				height,
				L"PaneHeader::DeferWindowPos::leftLabel");

			if (!m_bCollapsed)
			{
				// Drop the count on top of the label we drew above
				if (m_rightLabel.GetSafeHwnd())
				{
					hWinPosInfo = ui::DeferWindowPos(
						hWinPosInfo,
						m_rightLabel.GetSafeHwnd(),
						width - m_rightLabelWidth - actionButtonAndGutterWidth,
						0,
						m_rightLabelWidth,
						height,
						L"PaneHeader::DeferWindowPos::rightLabel");
				}
			}

			if (m_nIDAction)
			{
				// Drop the action button next to the label we drew above
				hWinPosInfo = ui::DeferWindowPos(
					hWinPosInfo,
					m_actionButton.GetSafeHwnd(),
					width - actionButtonWidth,
					0,
					actionButtonWidth,
					height,
					L"PaneHeader::DeferWindowPos::actionButton");
			}

			output::DebugPrint(output::dbgLevel::Draw, L"PaneHeader::DeferWindowPos end\n");
			EC_B_S(EndDeferWindowPos(hWinPosInfo));
		}
	}

	int PaneHeader::GetMinWidth()
	{
		auto cx = m_iLabelWidth;
		if (m_bCollapsible) cx += m_iButtonHeight;
		if (m_rightLabelWidth)
		{
			cx += m_iSideMargin;
			cx += m_rightLabelWidth;
		}

		if (m_actionButtonWidth)
		{
			cx += m_iSideMargin;
			cx += m_actionButtonWidth + 2 * m_iMargin;
		}

		return cx;
	}

	void PaneHeader::SetRightLabel(const std::wstring szLabel)
	{
		if (!m_bInitialized) return;
		EC_B_S(::SetWindowTextW(m_rightLabel.m_hWnd, szLabel.c_str()));

		const auto hdc = ::GetDC(m_rightLabel.GetSafeHwnd());
		const auto hfontOld = SelectObject(hdc, ui::GetSegoeFont());
		const auto sizeText = ui::GetTextExtentPoint32(hdc, szLabel);
		static_cast<void>(SelectObject(hdc, hfontOld));
		::ReleaseDC(m_rightLabel.GetSafeHwnd(), hdc);
		m_rightLabelWidth = sizeText.cx;

		RecalcLayout();
	}

	void PaneHeader::SetActionButton(const std::wstring szActionButton)
	{
		// Don't bother if we never enabled the button
		if (m_nIDAction == 0) return;

		EC_B_S(::SetWindowTextW(m_actionButton.GetSafeHwnd(), szActionButton.c_str()));

		m_szActionButton = szActionButton;
		const auto hdc = ::GetDC(m_actionButton.GetSafeHwnd());
		const auto hfontOld = SelectObject(hdc, ui::GetSegoeFont());
		const auto sizeText = ui::GetTextExtentPoint32(hdc, szActionButton);
		static_cast<void>(SelectObject(hdc, hfontOld));
		::ReleaseDC(m_actionButton.GetSafeHwnd(), hdc);
		m_actionButtonWidth = sizeText.cx;

		RecalcLayout();
	}

	bool PaneHeader::HandleChange(UINT nID)
	{
		// Collapse buttons have a nID IDD_COLLAPSE higher than nID of the pane they toggle.
		// So if we get asked about one that matches, we can assume it's time to toggle our collapse.
		if (m_nIDCollapse == nID)
		{
			OnToggleCollapse();
			return true;
		}

		return false;
	}

	void PaneHeader::OnToggleCollapse()
	{
		m_bCollapsed = !m_bCollapsed;

		StyleButton(m_CollapseButton.m_hWnd, m_bCollapsed ? ui::uiButtonStyle::UpArrow : ui::uiButtonStyle::DownArrow);
		WC_B_S(m_rightLabel.ShowWindow(m_bCollapsed ? SW_HIDE : SW_SHOW));

		// Trigger a redraw
		::PostMessage(m_hWndParent, WM_COMMAND, IDD_RECALCLAYOUT, NULL);
	}

	void PaneHeader::SetMargins(
		int iMargin,
		int iSideMargin,
		int iLabelHeight, // Height of the label
		int iButtonHeight) // Height of button
	{
		m_iMargin = iMargin;
		m_iSideMargin = iSideMargin;
		m_iLabelHeight = iLabelHeight;
		m_iButtonHeight = iButtonHeight;
	}
} // namespace controls<commit_msg>reenable tabs on collapsible headers<commit_after>#include <StdAfx.h>
#include <UI/Controls/PaneHeader/PaneHeader.h>
#include <UI/UIFunctions.h>
#include <core/utility/strings.h>
#include <UI/DoubleBuffer.h>
#include <core/utility/output.h>
#include <core/utility/registry.h>

namespace controls
{
	static std::wstring CLASS = L"PaneHeader";

	PaneHeader::~PaneHeader()
	{
		TRACE_DESTRUCTOR(CLASS);
		CWnd::DestroyWindow();
	}

	void PaneHeader::Initialize(HWND hWnd, _In_opt_ HDC hdc, _In_ UINT nid)
	{
		m_hWndParent = hWnd;

		// Assign a nID to the collapse button that is IDD_COLLAPSE more than the control's nID
		m_nID = nid;
		m_nIDCollapse = nid + IDD_COLLAPSE;

		WNDCLASSEX wc = {};
		const auto hInst = AfxGetInstanceHandle();
		if (!::GetClassInfoEx(hInst, _T("PaneHeader"), &wc)) // STRING_OK
		{
			wc.cbSize = sizeof wc;
			wc.style = 0; // not passing CS_VREDRAW | CS_HREDRAW fixes flicker
			wc.lpszClassName = _T("PaneHeader"); // STRING_OK
			wc.lpfnWndProc = ::DefWindowProc;
			wc.hbrBackground = GetSysBrush(ui::uiColor::Background); // helps spot flashing

			RegisterClassEx(&wc);
		}

		// WS_CLIPCHILDREN is used to reduce flicker
		EC_B_S(CreateEx(
			0,
			_T("PaneHeader"), // STRING_OK
			_T("PaneHeader"), // STRING_OK
			WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_VISIBLE,
			0,
			0,
			0,
			0,
			m_hWndParent,
			reinterpret_cast<HMENU>(static_cast<INT_PTR>(IDC_PANE_HEADER)),
			nullptr));

		// Necessary for TAB to work. Without this, all TABS get stuck on the control
		// instead of passing to the children. Only needed on collapsible headers.
		if (this->m_bCollapsible)
		{
			EC_B_S(ModifyStyleEx(0, WS_EX_CONTROLPARENT));
		}

		const auto sizeText = ui::GetTextExtentPoint32(hdc, m_szLabel);
		m_iLabelWidth = sizeText.cx;
		output::DebugPrint(
			output::dbgLevel::Draw,
			L"PaneHeader::Initialize m_iLabelWidth:%d \"%ws\"\n",
			m_iLabelWidth,
			m_szLabel.c_str());
	}

	BEGIN_MESSAGE_MAP(PaneHeader, CWnd)
	ON_WM_CREATE()
	END_MESSAGE_MAP()

	int PaneHeader::OnCreate(LPCREATESTRUCT /*lpCreateStruct*/)
	{
		EC_B_S(m_leftLabel.Create(
			WS_CHILD | WS_CLIPSIBLINGS | ES_READONLY | WS_VISIBLE | WS_TABSTOP, CRect(0, 0, 0, 0), this, m_nID));
		::SetWindowTextW(m_leftLabel.m_hWnd, m_szLabel.c_str());
		ui::SubclassLabel(m_leftLabel.m_hWnd);
		if (m_bCollapsible)
		{
			StyleLabel(m_leftLabel.m_hWnd, ui::uiLabelStyle::PaneHeaderLabel);
		}

		EC_B_S(m_rightLabel.Create(
			WS_CHILD | WS_CLIPSIBLINGS | ES_READONLY | WS_VISIBLE | WS_TABSTOP,
			CRect(0, 0, 0, 0),
			this,
			IDD_COUNTLABEL));
		ui::SubclassLabel(m_rightLabel.m_hWnd);
		StyleLabel(m_rightLabel.m_hWnd, ui::uiLabelStyle::PaneHeaderText);

		if (m_bCollapsible)
		{
			EC_B_S(m_CollapseButton.Create(
				nullptr, WS_TABSTOP | WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE, CRect(0, 0, 0, 0), this, m_nIDCollapse));
			StyleButton(
				m_CollapseButton.m_hWnd, m_bCollapsed ? ui::uiButtonStyle::UpArrow : ui::uiButtonStyle::DownArrow);
		}

		// If we need an action button, go ahead and create it
		if (m_nIDAction)
		{
			EC_B_S(m_actionButton.Create(
				strings::wstringTotstring(m_szActionButton).c_str(),
				WS_TABSTOP | WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE,
				CRect(0, 0, 0, 0),
				this,
				m_nIDAction));
			StyleButton(m_actionButton.m_hWnd, ui::uiButtonStyle::Unstyled);
		}

		m_bInitialized = true;
		return 0;
	}

	LRESULT PaneHeader::WindowProc(const UINT message, const WPARAM wParam, const LPARAM lParam)
	{
		LRESULT lRes = 0;
		if (ui::HandleControlUI(message, wParam, lParam, &lRes)) return lRes;

		switch (message)
		{
		case WM_PAINT:
			auto ps = PAINTSTRUCT{};
			::BeginPaint(m_hWnd, &ps);
			if (ps.hdc)
			{
				auto rcWin = RECT{};
				::GetClientRect(m_hWnd, &rcWin);
				const auto background = registry::uiDiag ? ui::GetSysBrush(ui::uiColor::TestGreen)
														 : ui::GetSysBrush(ui::uiColor::Background);
				FillRect(ps.hdc, &rcWin, background);
			}

			::EndPaint(m_hWnd, &ps);
			return 0;
		case WM_WINDOWPOSCHANGED:
			RecalcLayout();
			return 0;
		case WM_CLOSE:
			::SendMessage(m_hWndParent, message, wParam, lParam);
			return true;
		case WM_HELP:
			return true;
		case WM_COMMAND:
		{
			const auto nCode = HIWORD(wParam);
			// Pass button clicks up to parent
			if (BN_CLICKED == nCode)
			{
				::SendMessage(m_hWndParent, message, wParam, lParam);
			}

			break;
		}
		}

		return CWnd::WindowProc(message, wParam, lParam);
	}

	// Position collapse button, labels, action button.
	void PaneHeader::RecalcLayout()
	{
		if (!m_bInitialized) return;
		auto hWinPosInfo = WC_D(HDWP, BeginDeferWindowPos(4));
		if (hWinPosInfo)
		{
			auto rcWin = RECT{};
			::GetClientRect(m_hWnd, &rcWin);
			const auto width = rcWin.right - rcWin.left;
			const auto height = rcWin.bottom - rcWin.top;
			output::DebugPrint(
				output::dbgLevel::Draw,
				L"PaneHeader::DeferWindowPos width:%d height:%d v:%d\n",
				width,
				height,
				IsWindowVisible());
			auto curX = 0;
			const auto actionButtonWidth = m_actionButtonWidth ? m_actionButtonWidth + 2 * m_iMargin : 0;
			const auto actionButtonAndGutterWidth = actionButtonWidth ? actionButtonWidth + m_iSideMargin : 0;
			if (m_bCollapsible)
			{
				hWinPosInfo = ui::DeferWindowPos(
					hWinPosInfo,
					m_CollapseButton.GetSafeHwnd(),
					curX,
					0,
					width - actionButtonAndGutterWidth,
					height,
					L"PaneHeader::DeferWindowPos::collapseButton");
				curX += m_iButtonHeight;
			}

			hWinPosInfo = ui::DeferWindowPos(
				hWinPosInfo,
				m_leftLabel.GetSafeHwnd(),
				curX,
				0,
				m_iLabelWidth,
				height,
				L"PaneHeader::DeferWindowPos::leftLabel");

			if (!m_bCollapsed)
			{
				// Drop the count on top of the label we drew above
				if (m_rightLabel.GetSafeHwnd())
				{
					hWinPosInfo = ui::DeferWindowPos(
						hWinPosInfo,
						m_rightLabel.GetSafeHwnd(),
						width - m_rightLabelWidth - actionButtonAndGutterWidth,
						0,
						m_rightLabelWidth,
						height,
						L"PaneHeader::DeferWindowPos::rightLabel");
				}
			}

			if (m_nIDAction)
			{
				// Drop the action button next to the label we drew above
				hWinPosInfo = ui::DeferWindowPos(
					hWinPosInfo,
					m_actionButton.GetSafeHwnd(),
					width - actionButtonWidth,
					0,
					actionButtonWidth,
					height,
					L"PaneHeader::DeferWindowPos::actionButton");
			}

			output::DebugPrint(output::dbgLevel::Draw, L"PaneHeader::DeferWindowPos end\n");
			EC_B_S(EndDeferWindowPos(hWinPosInfo));
		}
	}

	int PaneHeader::GetMinWidth()
	{
		auto cx = m_iLabelWidth;
		if (m_bCollapsible) cx += m_iButtonHeight;
		if (m_rightLabelWidth)
		{
			cx += m_iSideMargin;
			cx += m_rightLabelWidth;
		}

		if (m_actionButtonWidth)
		{
			cx += m_iSideMargin;
			cx += m_actionButtonWidth + 2 * m_iMargin;
		}

		return cx;
	}

	void PaneHeader::SetRightLabel(const std::wstring szLabel)
	{
		if (!m_bInitialized) return;
		EC_B_S(::SetWindowTextW(m_rightLabel.m_hWnd, szLabel.c_str()));

		const auto hdc = ::GetDC(m_rightLabel.GetSafeHwnd());
		const auto hfontOld = SelectObject(hdc, ui::GetSegoeFont());
		const auto sizeText = ui::GetTextExtentPoint32(hdc, szLabel);
		static_cast<void>(SelectObject(hdc, hfontOld));
		::ReleaseDC(m_rightLabel.GetSafeHwnd(), hdc);
		m_rightLabelWidth = sizeText.cx;

		RecalcLayout();
	}

	void PaneHeader::SetActionButton(const std::wstring szActionButton)
	{
		// Don't bother if we never enabled the button
		if (m_nIDAction == 0) return;

		EC_B_S(::SetWindowTextW(m_actionButton.GetSafeHwnd(), szActionButton.c_str()));

		m_szActionButton = szActionButton;
		const auto hdc = ::GetDC(m_actionButton.GetSafeHwnd());
		const auto hfontOld = SelectObject(hdc, ui::GetSegoeFont());
		const auto sizeText = ui::GetTextExtentPoint32(hdc, szActionButton);
		static_cast<void>(SelectObject(hdc, hfontOld));
		::ReleaseDC(m_actionButton.GetSafeHwnd(), hdc);
		m_actionButtonWidth = sizeText.cx;

		RecalcLayout();
	}

	bool PaneHeader::HandleChange(UINT nID)
	{
		// Collapse buttons have a nID IDD_COLLAPSE higher than nID of the pane they toggle.
		// So if we get asked about one that matches, we can assume it's time to toggle our collapse.
		if (m_nIDCollapse == nID)
		{
			OnToggleCollapse();
			return true;
		}

		return false;
	}

	void PaneHeader::OnToggleCollapse()
	{
		m_bCollapsed = !m_bCollapsed;

		StyleButton(m_CollapseButton.m_hWnd, m_bCollapsed ? ui::uiButtonStyle::UpArrow : ui::uiButtonStyle::DownArrow);
		WC_B_S(m_rightLabel.ShowWindow(m_bCollapsed ? SW_HIDE : SW_SHOW));

		// Trigger a redraw
		::PostMessage(m_hWndParent, WM_COMMAND, IDD_RECALCLAYOUT, NULL);
	}

	void PaneHeader::SetMargins(
		int iMargin,
		int iSideMargin,
		int iLabelHeight, // Height of the label
		int iButtonHeight) // Height of button
	{
		m_iMargin = iMargin;
		m_iSideMargin = iSideMargin;
		m_iLabelHeight = iLabelHeight;
		m_iButtonHeight = iButtonHeight;
	}
} // namespace controls<|endoftext|>


<commit_before>#ifndef BUW_SDFLOADER_HPP
#define BUW_SDFLOADER_HPP

#include <string>
#include <scene.hpp>
#include <iostream>
#include <fstream>
#include <vector>
#include <regex>

void sdf_splitString(std::string const& input, std::vector<std::string>& output) {
	std::istringstream tmp{input};
	do {
		std::string sub{""};
		tmp >> sub;
		output.push_back(sub);
	} while(tmp);
}

bool sdf_isComment(std::string const& input) {
	std::regex rgx_comment{"^#.*"};
	if(std::regex_match(input, rgx_comment)) {
		return true;
	}
	return false;
}

bool sdf_isCamera(std::string const& input) {
	std::regex rgx_camera{"define[\\s\\t]+camera[\\s\\t]+\\S+"};
	return true;
}

bool sdf_isMaterial(std::string const& input) {
	std::regex rgx_material{"define[\\s\\t]+material[\\s\\t]+\\S+[\\s\\t]+([0-9]+[\\s\\t]){9}[0-9]+.*"};
	if(std::regex_match(input,rgx_material)) {
		return true;
	}
	return false;
}

void sdf_parseMaterial(std::string const& input, std::map<std::string,Material>& materials) {
	std::vector<std::string> parsed;
	sdf_splitString(input,parsed);
	Color ka = {std::stof(parsed[3]),std::stof(parsed[4]),std::stof(parsed[5])};
	Color kd = {std::stof(parsed[6]),std::stof(parsed[7]),std::stof(parsed[8])};
	Color ks = {std::stof(parsed[9]),std::stof(parsed[10]),std::stof(parsed[11])};
	materials.insert(materials.end(),std::pair<std::string,Material>(parsed[2],{parsed[2],ka,kd,ks,std::stof(parsed[12])}));
}

void sdf_loadScene(std::ifstream& file, Scene& scene) {
	std::string line{""};
	int lineCount{0};
	while(std::getline(file,line)) {
		lineCount++;
		//Comment
		if(sdf_isComment(line)) {
			continue;
		}
		//Camera
		if(sdf_isCamera(line)) {

		}
		// Material
		if(sdf_isMaterial(line)) {
			sdf_parseMaterial(line,scene.materials);
			//scene.materials.push_back(tmp_material);
			continue;
		}
		std::cout << "ERROR: Line " << lineCount << " could not be parsed" << std::endl;
		break;
	}
}

#endif
<commit_msg>Stable, fix regex of material<commit_after>#ifndef BUW_SDFLOADER_HPP
#define BUW_SDFLOADER_HPP

#include <string>
#include <scene.hpp>
#include <iostream>
#include <fstream>
#include <vector>
#include <regex>

void sdf_splitString(std::string const& input, std::vector<std::string>& output) {
	std::istringstream tmp{input};
	do {
		std::string sub{""};
		tmp >> sub;
		output.push_back(sub);
	} while(tmp);
}

bool sdf_isComment(std::string const& input) {
	std::regex rgx_comment{"^#.*"};
	if(std::regex_match(input, rgx_comment)) {
		return true;
	}
	return false;
}

bool sdf_isCamera(std::string const& input) {
	std::regex rgx_camera{"define[\\s\\t]+camera[\\s\\t]+\\S+"};
	return true;
}

bool sdf_isMaterial(std::string const& input) {
	std::regex rgx_material{"define[\\s\\t]+material[\\s\\t]+\\S+[\\s\\t]+([0-9]+[\\s\\t]+){9}[0-9]+.*"};
	if(std::regex_match(input,rgx_material)) {
		return true;
	}
	return false;
}

void sdf_parseMaterial(std::string const& input, std::map<std::string,Material>& materials) {
	std::vector<std::string> parsed;
	sdf_splitString(input,parsed);
	Color ka = {std::stof(parsed[3]),std::stof(parsed[4]),std::stof(parsed[5])};
	Color kd = {std::stof(parsed[6]),std::stof(parsed[7]),std::stof(parsed[8])};
	Color ks = {std::stof(parsed[9]),std::stof(parsed[10]),std::stof(parsed[11])};
	materials.insert(materials.end(),std::pair<std::string,Material>(parsed[2],{parsed[2],ka,kd,ks,std::stof(parsed[12])}));
}

void sdf_loadScene(std::ifstream& file, Scene& scene) {
	std::string line{""};
	int lineCount{0};
	while(std::getline(file,line)) {
		lineCount++;
		//Comment
		if(sdf_isComment(line)) {
			continue;
		}
		//Camera
		if(sdf_isCamera(line)) {

		}
		// Material
		if(sdf_isMaterial(line)) {
			sdf_parseMaterial(line,scene.materials);
			//scene.materials.push_back(tmp_material);
			continue;
		}
		std::cout << "ERROR: Line " << lineCount << " could not be parsed" << std::endl;
		break;
	}
}

#endif
<|endoftext|>


<commit_before>#include "audiooutputdevice.h"

#include "filter.h"
#include "statisticsinterface.h"
#include "speexdsp.h"

#include "common.h"
#include "global.h"

#include <QDateTime>

#include <QDebug>

uint8_t MAX_SAMPLE_REPEATS = 5;
uint8_t MAX_BUFFER_SAMPLES = 4;

AudioOutputDevice::AudioOutputDevice(StatisticsInterface *stats):
  QIODevice(),
  stats_(stats),
  device_(QAudioDeviceInfo::defaultOutputDevice()),
  audioOutput_(nullptr),
  output_(nullptr),
  format_(),
  sampleMutex_(),
  outputBuffer_(),
  sampleSize_(0),
  partialSample_(nullptr),
  partialSampleSize_(0),
  inputs_(0),
  mixedSample_(false),
  outputRepeats_(0)
{}

AudioOutputDevice::~AudioOutputDevice()
{
  deleteBuffers();
  audioOutput_->stop();
}

void AudioOutputDevice::init(QAudioFormat format,
                             std::shared_ptr<SpeexDSP> AEC)
{
  aec_ = AEC;

  QAudioDeviceInfo info(device_);
  if (!info.isFormatSupported(format)) {
    printDebug(DEBUG_WARNING, this,
               "Default format not supported - trying to use nearest.");
    format_ = info.nearestFormat(format);
  }
  else
  {
    format_ = format;
  }

  createAudioOutput();
}

void AudioOutputDevice::createAudioOutput()
{
  if (!aec_)
  {
    printProgramError(this, "AEC not set");
  }

  if(audioOutput_)
  {
    delete audioOutput_;
  }
  audioOutput_ = new QAudioOutput(device_, format_, this);

  //sampleSize_ = format_.sampleRate()*format_.bytesPerFrame()/AUDIO_FRAMES_PER_SECOND;

  open(QIODevice::ReadOnly);
  // pull mode

  sampleMutex_.lock();
  audioOutput_->start(this);

  resetBuffers(audioOutput_->periodSize());
  sampleMutex_.unlock();
}

void AudioOutputDevice::deviceChanged(int index)
{
  Q_UNUSED(index);
  printUnimplemented(this, "Audio output device change not implemented fully.");

  audioOutput_->stop();
  audioOutput_->disconnect(this);
  //initializeAudio(format_);
}

void AudioOutputDevice::volumeChanged(int value)
{
  if (audioOutput_)
    audioOutput_->setVolume(qreal(value/100.0f));
}

qint64 AudioOutputDevice::readData(char *data, qint64 maxlen)
{
  uint32_t read = 0;
  if (maxlen < sampleSize_)
  {
    printWarning(this, "Output wants smaller sample than what we use. "
                        "Part of the sample may be left unplayed",
                 {"Read vs available"},
                 {QString::number(maxlen) + " vs " + QString::number(sampleSize_)});
  }
  else
  {
    if (audioOutput_->periodSize() > sampleSize_)
    {
      printWarning(this, "We use smaller sample than what audio output wants. "
                         "Buffer output underflow.", {"PeriodSize vs frame size"}, {
                     QString::number(audioOutput_->periodSize()) + " vs " +
                     QString::number(sampleSize_)});
    }

    if (!aec_)
    {
      printProgramError(this, "AEC not set");
    }

    // if no new input has arrived, we play the last sample
    sampleMutex_.lock();

    // we keep track if this is a repeat so we can change it to silence.
    if (!mixedSample_)
    {
      ++outputRepeats_;
    }
    else
    {
      mixedSample_ = false;
    }

    // start playing silence if we played last frame three times.
    if (outputRepeats_ >= MAX_SAMPLE_REPEATS || outputBuffer_.empty())
    {
      printWarning(this, "Resetting audio buffers since there is not enough samples");
      resetBuffers(sampleSize_);
      outputRepeats_ = 0;
    }

    // take oldest sample in buffer
    uint8_t* sample = outputBuffer_.front();

    // send sample to AEC to use as a reference of echo
    aec_->processEchoFrame(sample, sampleSize_);

    // send sample to speakers
    memcpy(data, sample, sampleSize_);
    read = sampleSize_;

    // delete sample from our buffer if it was not the only sample we have
    if (outputBuffer_.size() > 1)
    {
      delete outputBuffer_.front();
      outputBuffer_.pop_front();
    }

    // if we have too may audio samples stored
    while (outputBuffer_.size() > MAX_BUFFER_SAMPLES)
    {
      printWarning(this, "Audio buffer has grown too large. "
                         "Deleting oldest samples to avoid latency");
      delete outputBuffer_.front();
      outputBuffer_.pop_front();
    }

    sampleMutex_.unlock();
  }

  return read;
}

qint64 AudioOutputDevice::writeData(const char *data, qint64 len)
{
  Q_UNUSED(data);
  Q_UNUSED(len);

  // output does not write data to device, only reads it.
  printProgramWarning(this, "Why is output writing to us");

  return 0;
}

qint64 AudioOutputDevice::bytesAvailable() const
{
  return sampleSize_ + QIODevice::bytesAvailable();
}

void AudioOutputDevice::takeInput(std::unique_ptr<Data> input, uint32_t sessionID)
{
  if (audioOutput_ && audioOutput_->state() != QAudio::StoppedState)
  {
    // Add audio delay to statistics
    int64_t delay = QDateTime::currentMSecsSinceEpoch() - input->presentationTime;

    stats_->receiveDelay(sessionID, "Audio", delay);

    int inputSize = input->data_size;

    // we record one sample to buffer in case there is a packet loss,
    // just so we don't have any breaks
    std::unique_ptr<uchar[]> outputFrame;

    if (inputs_ < 2)
    {
      outputFrame = std::move(input->data);
    }
    else
    {
      outputFrame = mixAudio(std::move(input), sessionID);

      // if we are still waiting for other samples to mix
      if (outputFrame == nullptr)
      {
        return;
      }
    }

    sampleMutex_.lock();
    mixedSample_ = true;

    if (sampleSize_ == inputSize) // input is exactly right
    {
      addSampleToBuffer(outputFrame.get(), sampleSize_);
    }
    else if (sampleSize_ > inputSize) // the input sample is smaller than what output wants
    {
      if (partialSampleSize_ + inputSize == sampleSize_)
      {
        memcpy(partialSample_ + partialSampleSize_, outputFrame.get(), inputSize);
        addSampleToBuffer(partialSample_, sampleSize_);
      }
      else if (partialSampleSize_ + inputSize < sampleSize_)
      {
        // we did not update the sample so no reset for output repeats
        // add this partial sample to temporary storage
        memcpy(partialSample_ + partialSampleSize_, outputFrame.get(), inputSize);
      }
      else // if we somehow have more audio data than one sample needs
      {
        uint32_t missingSize = sampleSize_ - partialSampleSize_;

        // add missing piece from new sample
        memcpy(partialSample_ + partialSampleSize_, outputFrame.get(), missingSize);

        // copy finished sample to output
        addSampleToBuffer(partialSample_, sampleSize_);

        // store remaining piece from new sample
        memcpy(partialSample_, outputFrame.get() + missingSize, inputSize - missingSize);
        partialSampleSize_ = inputSize - missingSize;
      }
    }
    // If the input frames are larger than output sample,
    // we will have to wait until output wants this much data.
    else if (sampleSize_ < inputSize)
    {
      printDebug(DEBUG_NORMAL, this, "Incoming audio sample is larger than what output wants",
                  {"Incoming sample", "What output wants"}, {
                   QString::number(inputSize), QString::number(sampleSize_)});

      // increase size of buffers
      resetBuffers(inputSize);

      // add new sample to bigger buffers
      addSampleToBuffer(outputFrame.get(), sampleSize_);
    }

    sampleMutex_.unlock();
  }
}

std::unique_ptr<uchar[]> AudioOutputDevice::mixAudio(std::unique_ptr<Data> input,
                                                     uint32_t sessionID)
{
  std::unique_ptr<uchar[]> outputFrame = nullptr;
  mixingMutex_.lock();
  // mix if there is already a sample for this stream in buffer
  if (mixingBuffer_.find(sessionID) != mixingBuffer_.end())
  {
    printWarning(this, "Mixing overflow. Missing samples.");
    outputFrame = doMixing(input->data_size);
    mixingBuffer_[sessionID] = std::move(input);
  }
  else
  {
    uint32_t size = input->data_size;
    mixingBuffer_[sessionID] = std::move(input);

    // mix if there is a sample for all streams
    if (mixingBuffer_.size() == inputs_)
    {
      outputFrame = doMixing(size);
    }
  }

  mixingMutex_.unlock();

  return outputFrame;
}

std::unique_ptr<uchar[]> AudioOutputDevice::doMixing(uint32_t frameSize)
{
  // don't do mixing if we have only one stream.
  if (mixingBuffer_.size() == 1)
  {
    std::unique_ptr<uchar[]> oneSample = std::move(mixingBuffer_.begin()->second->data);
    mixingBuffer_.clear();
    return oneSample;
  }

  std::unique_ptr<uchar[]> result = std::unique_ptr<uint8_t[]>(new uint8_t[frameSize]);
  int16_t * output_ptr = (int16_t*)result.get();

  for (unsigned int i = 0; i < frameSize/2; ++i)
  {
    int32_t sum = 0;

    // This is in my understanding the correct way to do audio mixing. Just add them up.
    for (auto& buffer : mixingBuffer_)
    {
      sum += *((int16_t*)buffer.second->data.get() + i);
    }

    // clipping is not desired, but occurs rarely
    // TODO: Replace this with dynamic range compression
    if (sum > INT16_MAX)
    {
      printWarning(this, "Clipping audio", "Value", QString::number(sum));
      sum = INT16_MAX;
    }
    else if (sum < INT16_MIN)
    {
      printWarning(this, "Boosting audio", "Value", QString::number(sum));
      sum = INT16_MIN;
    }

    *output_ptr = sum;
    ++output_ptr;
  }

  mixingBuffer_.clear();

  return result;
}

void AudioOutputDevice::start()
{
  open(QIODevice::ReadOnly);

  if(audioOutput_->state() == QAudio::SuspendedState
     || audioOutput_->state() == QAudio::StoppedState)
  {
#ifdef __linux__
    audioOutput_->start();
#else
    audioOutput_->resume();
#endif
  }
}

void AudioOutputDevice::stop()
{
  if(audioOutput_->state() == QAudio::ActiveState)
  {
#ifdef __linux__
    audioOutput_->stop();
#else
    audioOutput_->suspend();
#endif
  }

  close();
}

void AudioOutputDevice::deleteBuffers()
{
  for (auto& sample : outputBuffer_)
  {
    if (sample != nullptr)
    {
      delete[] sample;
      sample = nullptr;
    }
  }

  outputBuffer_.clear();
  sampleSize_ = 0;

  if (partialSample_ != nullptr)
  {
    delete[] partialSample_;
    partialSample_ = nullptr;
  }

  partialSampleSize_ = 0;
}

void AudioOutputDevice::resetBuffers(uint32_t newSize)
{
  deleteBuffers();

  sampleSize_ = newSize;
  partialSampleSize_ = 0;
  outputBuffer_.push_back(aec_->createEmptyFrame(sampleSize_));
  partialSample_ = aec_->createEmptyFrame(sampleSize_);
}

void AudioOutputDevice::addSampleToBuffer(uint8_t *sample, int sampleSize)
{
  uint8_t* new_sample = new uint8_t[sampleSize];
  memcpy(new_sample, sample, sampleSize);
  outputBuffer_.push_back(new_sample);

  outputRepeats_ = 0;
}
<commit_msg>feature(Processing): Divide large audio frames<commit_after>#include "audiooutputdevice.h"

#include "filter.h"
#include "statisticsinterface.h"
#include "speexdsp.h"

#include "common.h"
#include "global.h"

#include <QDateTime>

#include <QDebug>

uint8_t MAX_SAMPLE_REPEATS = 5;
uint8_t MAX_BUFFER_SAMPLES = 4;

AudioOutputDevice::AudioOutputDevice(StatisticsInterface *stats):
  QIODevice(),
  stats_(stats),
  device_(QAudioDeviceInfo::defaultOutputDevice()),
  audioOutput_(nullptr),
  output_(nullptr),
  format_(),
  sampleMutex_(),
  outputBuffer_(),
  sampleSize_(0),
  partialSample_(nullptr),
  partialSampleSize_(0),
  inputs_(0),
  mixedSample_(false),
  outputRepeats_(0)
{}

AudioOutputDevice::~AudioOutputDevice()
{
  deleteBuffers();
  audioOutput_->stop();
}

void AudioOutputDevice::init(QAudioFormat format,
                             std::shared_ptr<SpeexDSP> AEC)
{
  aec_ = AEC;

  QAudioDeviceInfo info(device_);
  if (!info.isFormatSupported(format)) {
    printDebug(DEBUG_WARNING, this,
               "Default format not supported - trying to use nearest.");
    format_ = info.nearestFormat(format);
  }
  else
  {
    format_ = format;
  }

  createAudioOutput();
}

void AudioOutputDevice::createAudioOutput()
{
  if (!aec_)
  {
    printProgramError(this, "AEC not set");
  }

  if(audioOutput_)
  {
    delete audioOutput_;
  }
  audioOutput_ = new QAudioOutput(device_, format_, this);

  //sampleSize_ = format_.sampleRate()*format_.bytesPerFrame()/AUDIO_FRAMES_PER_SECOND;

  open(QIODevice::ReadOnly);
  // pull mode

  sampleMutex_.lock();
  audioOutput_->start(this);

  resetBuffers(audioOutput_->periodSize());
  sampleMutex_.unlock();
}

void AudioOutputDevice::deviceChanged(int index)
{
  Q_UNUSED(index);
  printUnimplemented(this, "Audio output device change not implemented fully.");

  audioOutput_->stop();
  audioOutput_->disconnect(this);
  //initializeAudio(format_);
}

void AudioOutputDevice::volumeChanged(int value)
{
  if (audioOutput_)
    audioOutput_->setVolume(qreal(value/100.0f));
}

qint64 AudioOutputDevice::readData(char *data, qint64 maxlen)
{
  uint32_t read = 0;
  if (maxlen < sampleSize_)
  {
    printWarning(this, "Output wants smaller sample than what we use. "
                        "Part of the sample may be left unplayed",
                 {"Read vs available"},
                 {QString::number(maxlen) + " vs " + QString::number(sampleSize_)});
  }
  else
  {
    if (audioOutput_->periodSize() > sampleSize_)
    {
      printWarning(this, "We use smaller sample than what audio output wants. "
                         "Buffer output underflow.", {"PeriodSize vs frame size"}, {
                     QString::number(audioOutput_->periodSize()) + " vs " +
                     QString::number(sampleSize_)});
    }

    if (!aec_)
    {
      printProgramError(this, "AEC not set");
    }

    // if no new input has arrived, we play the last sample
    sampleMutex_.lock();

    // we keep track if this is a repeat so we can change it to silence.
    if (!mixedSample_)
    {
      ++outputRepeats_;
    }
    else
    {
      mixedSample_ = false;
    }

    // start playing silence if we played last frame three times.
    if (outputRepeats_ >= MAX_SAMPLE_REPEATS || outputBuffer_.empty())
    {
      printWarning(this, "Resetting audio buffers since there is not enough samples");
      resetBuffers(sampleSize_);
      outputRepeats_ = 0;
    }

    // take oldest sample in buffer
    uint8_t* sample = outputBuffer_.front();

    // send sample to AEC to use as a reference of echo
    aec_->processEchoFrame(sample, sampleSize_);

    // send sample to speakers
    memcpy(data, sample, sampleSize_);
    read = sampleSize_;

    // delete sample from our buffer if it was not the only sample we have
    if (outputBuffer_.size() > 1)
    {
      delete outputBuffer_.front();
      outputBuffer_.pop_front();
    }

    // if we have too may audio samples stored
    while (outputBuffer_.size() > MAX_BUFFER_SAMPLES)
    {
      printWarning(this, "Audio buffer has grown too large. "
                         "Deleting oldest samples to avoid latency");
      delete outputBuffer_.front();
      outputBuffer_.pop_front();
    }

    sampleMutex_.unlock();
  }

  return read;
}

qint64 AudioOutputDevice::writeData(const char *data, qint64 len)
{
  Q_UNUSED(data);
  Q_UNUSED(len);

  // output does not write data to device, only reads it.
  printProgramWarning(this, "Why is output writing to us");

  return 0;
}

qint64 AudioOutputDevice::bytesAvailable() const
{
  return sampleSize_ + QIODevice::bytesAvailable();
}

void AudioOutputDevice::takeInput(std::unique_ptr<Data> input, uint32_t sessionID)
{
  if (audioOutput_ && audioOutput_->state() != QAudio::StoppedState)
  {
    // Add audio delay to statistics
    int64_t delay = QDateTime::currentMSecsSinceEpoch() - input->presentationTime;

    stats_->receiveDelay(sessionID, "Audio", delay);

    int inputSize = input->data_size;

    // we record one sample to buffer in case there is a packet loss,
    // just so we don't have any breaks
    std::unique_ptr<uchar[]> outputFrame;

    if (inputs_ < 2)
    {
      outputFrame = std::move(input->data);
    }
    else
    {
      outputFrame = mixAudio(std::move(input), sessionID);

      // if we are still waiting for other samples to mix
      if (outputFrame == nullptr)
      {
        return;
      }
    }

    sampleMutex_.lock();
    mixedSample_ = true;

    if (sampleSize_ == inputSize) // input is exactly right
    {
      addSampleToBuffer(outputFrame.get(), sampleSize_);
    }
    else if (sampleSize_ > inputSize) // the input sample is smaller than what output wants
    {
      if (partialSampleSize_ + inputSize == sampleSize_)
      {
        memcpy(partialSample_ + partialSampleSize_, outputFrame.get(), inputSize);
        addSampleToBuffer(partialSample_, sampleSize_);
      }
      else if (partialSampleSize_ + inputSize < sampleSize_)
      {
        // we did not update the sample so no reset for output repeats
        // add this partial sample to temporary storage
        memcpy(partialSample_ + partialSampleSize_, outputFrame.get(), inputSize);
      }
      else // if we somehow have more audio data than one sample needs
      {
        uint32_t missingSize = sampleSize_ - partialSampleSize_;

        // add missing piece from new sample
        memcpy(partialSample_ + partialSampleSize_, outputFrame.get(), missingSize);

        // copy finished sample to output
        addSampleToBuffer(partialSample_, sampleSize_);

        // store remaining piece from new sample
        memcpy(partialSample_, outputFrame.get() + missingSize, inputSize - missingSize);
        partialSampleSize_ = inputSize - missingSize;
      }
    }
    else if (inputSize%sampleSize_ == 0)
    {
      for (int i = 0; i < inputSize; i += sampleSize_)
      {
        addSampleToBuffer(outputFrame.get(), sampleSize_);
      }
    }
    else // if input is not an exact multitude of sample size
    {
      // If the input frames are larger than output sample,
      // we will have to wait until output wants this much data.

      printDebug(DEBUG_NORMAL, this, "Incoming audio sample is larger than what output wants",
                  {"Incoming sample", "What output wants"}, {
                   QString::number(inputSize), QString::number(sampleSize_)});

      // increase size of buffers
      resetBuffers(inputSize);

      // add new sample to bigger buffers
      addSampleToBuffer(outputFrame.get(), sampleSize_);
    }

    sampleMutex_.unlock();
  }
}

std::unique_ptr<uchar[]> AudioOutputDevice::mixAudio(std::unique_ptr<Data> input,
                                                     uint32_t sessionID)
{
  std::unique_ptr<uchar[]> outputFrame = nullptr;
  mixingMutex_.lock();
  // mix if there is already a sample for this stream in buffer
  if (mixingBuffer_.find(sessionID) != mixingBuffer_.end())
  {
    printWarning(this, "Mixing overflow. Missing samples.");
    outputFrame = doMixing(input->data_size);
    mixingBuffer_[sessionID] = std::move(input);
  }
  else
  {
    uint32_t size = input->data_size;
    mixingBuffer_[sessionID] = std::move(input);

    // mix if there is a sample for all streams
    if (mixingBuffer_.size() == inputs_)
    {
      outputFrame = doMixing(size);
    }
  }

  mixingMutex_.unlock();

  return outputFrame;
}

std::unique_ptr<uchar[]> AudioOutputDevice::doMixing(uint32_t frameSize)
{
  // don't do mixing if we have only one stream.
  if (mixingBuffer_.size() == 1)
  {
    std::unique_ptr<uchar[]> oneSample = std::move(mixingBuffer_.begin()->second->data);
    mixingBuffer_.clear();
    return oneSample;
  }

  std::unique_ptr<uchar[]> result = std::unique_ptr<uint8_t[]>(new uint8_t[frameSize]);
  int16_t * output_ptr = (int16_t*)result.get();

  for (unsigned int i = 0; i < frameSize/2; ++i)
  {
    int32_t sum = 0;

    // This is in my understanding the correct way to do audio mixing. Just add them up.
    for (auto& buffer : mixingBuffer_)
    {
      sum += *((int16_t*)buffer.second->data.get() + i);
    }

    // clipping is not desired, but occurs rarely
    // TODO: Replace this with dynamic range compression
    if (sum > INT16_MAX)
    {
      printWarning(this, "Clipping audio", "Value", QString::number(sum));
      sum = INT16_MAX;
    }
    else if (sum < INT16_MIN)
    {
      printWarning(this, "Boosting audio", "Value", QString::number(sum));
      sum = INT16_MIN;
    }

    *output_ptr = sum;
    ++output_ptr;
  }

  mixingBuffer_.clear();

  return result;
}

void AudioOutputDevice::start()
{
  open(QIODevice::ReadOnly);

  if(audioOutput_->state() == QAudio::SuspendedState
     || audioOutput_->state() == QAudio::StoppedState)
  {
#ifdef __linux__
    audioOutput_->start();
#else
    audioOutput_->resume();
#endif
  }
}

void AudioOutputDevice::stop()
{
  if(audioOutput_->state() == QAudio::ActiveState)
  {
#ifdef __linux__
    audioOutput_->stop();
#else
    audioOutput_->suspend();
#endif
  }

  close();
}

void AudioOutputDevice::deleteBuffers()
{
  for (auto& sample : outputBuffer_)
  {
    if (sample != nullptr)
    {
      delete[] sample;
      sample = nullptr;
    }
  }

  outputBuffer_.clear();
  sampleSize_ = 0;

  if (partialSample_ != nullptr)
  {
    delete[] partialSample_;
    partialSample_ = nullptr;
  }

  partialSampleSize_ = 0;
}

void AudioOutputDevice::resetBuffers(uint32_t newSize)
{
  deleteBuffers();

  sampleSize_ = newSize;
  partialSampleSize_ = 0;
  outputBuffer_.push_back(aec_->createEmptyFrame(sampleSize_));
  partialSample_ = aec_->createEmptyFrame(sampleSize_);
}

void AudioOutputDevice::addSampleToBuffer(uint8_t *sample, int sampleSize)
{
  uint8_t* new_sample = new uint8_t[sampleSize];
  memcpy(new_sample, sample, sampleSize);
  outputBuffer_.push_back(new_sample);

  outputRepeats_ = 0;
}
<|endoftext|>